C로 작성된 Socket Programming 실습 코드이다.
client에서 루프백 어드레스(로컬 호스트)를 통해 소켓을 보내고 서버에서 수신하는 간단한 코드이다.
코드를 실습해보며 윈도우에서 제공하는 소켓이 어떻게 구성되었는지, 송수신과정에서 소켓이 어떤 역할을 하는지 알 수 있다. 소켓 프로그래밍에 필요한 함수, 라이브러리등은 microsoft docs에서 많은 정보를 얻을 수 있다.
https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup
소켓의 통신과정은 다음과 같다.
클라이언트 :
1. winsock 라이브러리 초기화
2. 소켓 생성
3. 서버주소로 연결 (connect 함수)
서버:
1. Listener 소켓 생성
2. Bind
3. Listen
4. Accept
*) Bind : 소켓에 주소, 프로토콜, 포트를 할당
Client Part
#include "pch.h"
#include <iostream>
#include <winsock2.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
// ws2_32 라이브러리 초기화
WSAData wsaData;
if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
return 0;
SOCKET clientSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket == INVALID_SOCKET)
{
int32 errCode = ::WSAGetLastError();
cout << "Socket ErrorCode : " << errCode << endl;
return 0;
}
SOCKADDR_IN serverAddr; // IPv4
::memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
::inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
serverAddr.sin_port = ::htons(7777);
if (::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
int32 errCode = ::WSAGetLastError();
cout << "Connect ErrorCode : " << errCode << endl;
return 0;
}
// ---------------------------
// 데이터 송수신 가능
cout << "Connected To Server!" << endl;
while (true)
{
// TODO
this_thread::sleep_for(1s);
}
::closesocket(clientSocket);
// 종료
::WSACleanup();
}
Server Part
#include <winsock2.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSAData wsaData;
if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
return 0;
// ad : Address Family (AF_INET = IPv4, AF_INET6 = IPv6)
// type : TCP(SOCK_STREAM) vs UDP(SOCK_DGRAM)
// protocol : 0
// return : descriptor
SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (listenSocket == INVALID_SOCKET)
{
int32 errCode = ::WSAGetLastError();
cout << "Socket ErrorCode : " << errCode << endl;
return 0;
}
SOCKADDR_IN serverAddr; // IPv4
::memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = ::htonl(INADDR_ANY);
serverAddr.sin_port = ::htons(7777);
// 소켓에 서버 주소를 바인딩
if (::bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
int32 errCode = ::WSAGetLastError();
cout << "Bind ErrorCode : " << errCode << endl;
return 0;
}
//
if (::listen(listenSocket, 10) == SOCKET_ERROR)
{
int32 errCode = ::WSAGetLastError();
cout << "Listen ErrorCode : " << errCode << endl;
return 0;
}
// -----------------------------
while (true)
{
SOCKADDR_IN clientAddr; // IPv4
::memset(&clientAddr, 0, sizeof(clientAddr));
int32 addrLen = sizeof(clientAddr);
SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
if (clientSocket == INVALID_SOCKET)
{
int32 errCode = ::WSAGetLastError();
cout << "Accept ErrorCode : " << errCode << endl;
return 0;
}
// 연결해서 소켓의 정보 받아옴
char ipAddress[16];
::inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(ipAddress));
cout << "Client Connected! IP = " << ipAddress << endl;
// TODO
}
// 종료
::WSACleanup();
}
서버쪽에서 중요한점은 listen socket과 client socket 두 개의 소켓을 이용한다는 점이다.
listen socket의 경우 실질적인 서버의 데이터교환에는 관여하지않고, 클라이언트의 연결에만 관여한다.
client socket은 listen socket을 통해 연결된 client와 실질적인 데이터 교환을 수행한다는 점.
공식적인 명칭은 아니지만 두 소켓의 이용방안에 대해 착각해선 안된다.
+) UDP 기반의 통신에서는 한개의 소켓 사용
타 프로젝트와 달리 해당 솔루션내에서는
Client와 Server 프로젝트를 분리하여 작성하고, 위와 같이 동시실행하는 환경을 구성해야 정상 작동한다.
실행결과
'Network' 카테고리의 다른 글
Non-Blocking 에 대한 정의 (0) | 2021.12.29 |
---|