
소켓 프로그래밍은 사용법을 위주로 다루게 될 것 같습니다. 이때까지는 이론적인 이야기가 주를 이뤘지만, 소켓 프로그래밍에서는 이론적인 이야기는 많지 않을것 같습니다. (단,소켓 모델이랑 비동기 IO는 많겠네요.)
Windows를 사용하기 때문에, Winsock을 기준으로 글을 쓰도록 하겠습니다.
소켓은 L4와 L7사이의 인터페이스라고 생각하시면 좋습니다. 프로그래머가 작성하는건 결국 L7(Application)단이고, 통신은 L4이고, L4는 커널코드에서 돌아가게 됩니다. 따라서, TCP 통신이든, UDP든 통신은 소켓을 통해서 하게 될 것이기 때문에, 네트워크 프로그래밍에서 Socket API는 필수적입니다.
소켓은 src IP, src Port, dst IP, dst Port로 식별되는 하나의 연결단위로 봐도 무방합니다. 해당 4가지 정보를 소켓 주소라고 표현하기도 합니다. 소켓은 운영체제에서 커널 오브젝트(커널이 관리하는 오브젝트)기도 합니다. NetWork 또한 IO기 때문에 커널이 관여해서 직접 LAN카드에게 일을 시켜야합니다.
#pragma comment (lib, "Ws2_32.lib")
#define _WINSOCKAPI_
#include <WS2tcpip.h>
#include <windows.h>
#include <winSock2.h>
int main()
{
WSAData wsaData;
::WSAstartup(MAKEWORD(2,2), &wsaData);
::WSACleanup();
}
pragma comment(lib, "Ws2_32.lib") : winsock을 쓰기 위해서 포함시켜야하는 dll입니다. #define _WINSOCKAPI_ : windows와 winsock라이브러리 안에서 중복참조를 막기 위한 define입니다. WSAStartup : DLL 정보를 초기화 합니다.WSACleanup(): Winsock DLL사용을 종료하는 플래그입니다. 서버는 포트가 보통 정해져있고, IP 또한 고정적인 경우가 많습니다. 서버는 아래의 절차로 보통 소켓프로그래밍을 하게 됩니다.
#pragma comment (lib, "Ws2_32.lib")
#define _WINSOCKAPI_
#include <WS2tcpip.h>
#include <windows.h>
#include <winSock2.h>
int main()
{
WSAData wsaData;
USHORT port = 20000;
int flag;
::WSAstartup(MAKEWORD(2,2), &wsaData);
SOCKADDR_IN serverInfo;
::ZeroMemory(&serverInfo, sizeof(SOCKADDR_IN));
::InetPton(AF_INET, L"0.0.0.0", &serverInfo.sin_addr);
serverInfo.sin_port = htons(port);
serverInfo.sin_family = AF_INET;
//MAKE SOCKET
SOCKET listenSocket;
listenSocket = ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (listenSocket == INVALID_SOCKET)
{
::WSAGetLastError();
return 0;
}
flag = ::bind(listenSocket, reinterpret_cast<SOCKADDR*>(&serverInfo),sizeof(serverInfo));
if (flag == SOCKET_ERROR)
{
::WSAGetLastError();
return 0;
}
flag = ::listen(listenSocket, SOMAXCONN);
if (flag == SOCKET_ERROR)
{
::WSAGetLastError();
return 0;
}
while (true)
{
~~Accpet & Recv & Send
}
::closesocket(listenSocket);
::WSACleanup();
}
SOCKADDR_IN : 소켓 주소를 위한 구조체입니다. port,ip, 통신 종류(TCP,UDP 기타등등)의 정보를 담고 있습니다.
::ZeroMemory(&serverInfo, sizeof(SOCKADDR_IN)) : 초기화 하는 코드입니다. memset으로 바꿔도 상관없습니다. 애초에 똑같습니다. 그냥 define만 되있습니다.
::InetPton(AF_INET, L"0.0.0.0", &serverInfo.sin_addr), serverInfo.sin_port = htons(port) : 소켓 주소를 셋팅하는 부분입니다. 소켓은 네트워크 장비도 이용하기 때문에, 빅엔디안으로 바꿔줘야합니다. InetPton(Protocol to network) ,htons(host to network short) 등 많은 함수가 있습니다. 그 중에 하나 마음에 드는거 쓰시면 됩니다.
L"0.0.0.0"은 어떤 IP에서든 들어오는 통신을 받겠다입니다. 0.0.0.0은 모든 IP주소를 나타냅니다. 모든 랜카드를 통해 들어오는 통신을 받습니다. ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED) : Socket을 만드는 부분입니다. socket(AF_INET,SOCK_STREAM, IPPROTO_TCP)로 만들어도 상관없는데, 괜히 윈도우 전용함수로 만들어봤습니다.
::bind(listenSocket, reinterpret_cast<SOCKADDR*>(&serverInfo),sizeof(serverInfo)) : 소켓에 서버정보를 맵핑 시켜주는 단계입니다.
::listen(listenSocket, SOMAXCONN) : 소켓을 Listen상태로 셋팅합니다. Listen이되는 순간부터 연결이 가능합니다. 뒤의 SOMAXCONN은 백로그 큐의 크기입니다.
백로그 큐는 연결정보가 들어왔을 때, accpet가 바로바로 안될 수도 있기 때문에 연결정보를 담아놓는 공간입니다. 논페이지드 풀 공간을 잡아먹기 때문에, Accept를 하지 않으면 치명적인 오류가 될 수 있습니다.
나머지는 똑같고, connect만 해주면 됩니다.
bool DomainToIP(const WCHAR* domain, IN_ADDR* out)
{
ADDRINFOW* pAddrInfo;
SOCKADDR_IN* pSockAddr;
if (GetAddrInfoW(domain, L"0", NULL, &pAddrInfo) != 0)
{
wprintf_s(L"Convert Domain to IP is Failed!\n");
return false;
}
pSockAddr = reinterpret_cast<SOCKADDR_IN*>(pAddrInfo->ai_addr);
*out = pSockAddr->sin_addr;
FreeAddrInfo(pAddrInfo);
return true;
}