C++/C++
[C++] TCP socket 통신 구현
sihyeong
2022. 12. 19. 01:35
소켓
- 소켓은 컴퓨터가 통신을 하기 위한 도구이다.
- 컴퓨터 네트워크ㅡ를 경유히 종착점이다.
- 오늘날 대부분의 통신은 인터넷 프로토콜을 기반으로 하고 있으며 대부분의 네트워크 소켓은 인터넷 소켓이다.
- 한마디로 대부분이 소켓을 사용한다.
- 인터넷의 웹이 동작하는 방식도 소켓을 기반으로 동작한다.
서버 - 클라이언트 환경은 아래의 그림의 작업을 통해 진행된다.
소켓 통신 사용 헤더
#include <WinSock2.h>
#include <WS2tcpip.h>
// 윈도우에서의 소켓을 사용하기 위해 초기화가 필요하고
// 초기화 함수가 ws2_32.lib의 WSCStartup()를 통해 진행된다.
#pragma comment(lib, "ws2_32.lib")
WSADATA 구조체
- Windows 소켓 초기화 정보를 저장하기 위한 구조체
WSAStartup(소켓버전, WSADATA 구조체 주소)
- Windows에 어떤 소켓을 사용할 것인지를 알려준다.
- 버전을 알려줘야 하는데 WORD 타입으로 받기 때문에 2.2 버전을 사용할 경우, 실수를 정수값으로 변환해 넣어줘야 하고 MAKEWORD 매크로가 이를 도와준다.
WSACleanup()
- 소켓 사용이 끝났음을 알려주는 부분이다.
- 실제 소켓 코드는 WSAStartup()과 WSACleanup() 사이에 작성해야 한다.
socket(주소형식, 통신형식, 프로토콜)
- socket은 핸들이다. 커널오브젝트를 접근을 위해 선언
- AF_INET은 IPv4 사용을 지정
- TCP통신을 위해선 SOCK_STREAM과 IPPROTO_TCP을 사용하고 둘은 쌍이다.
- UDP통신은 SOCK_DGRAM과 IPPROTO_UDP 쌍이다.
socket 함수(winsock2.h) - Win32 apps
소켓 함수는 특정 전송 서비스 공급자에 바인딩된 소켓을 만듭니다.
learn.microsoft.com
SOCKADDR_IN
- sockaddr_in을 통해 주소형식, ip, port 설정해준다.
- 엔디안 문제가 있기 때문에 네트워크에서 사용하는 빅엔디안으로 변환하여 ip와 port를 지정한다.
connect(socket, sockaddr*, sockaddrSize)
- servAddr을 통해 지정한 ip와 port를 이용한 서버에 연결을 원합니다 연결해주세요 라고 연결을 요청한다.
- 연결이 될때까지 일부 대기하고, 서버가 닫혀있다면 리턴되며 종료된다.
recv(socket, cbuffer, PACKET_SIZE, flag);
- 연결이 된 후, recv를 호출하면 서버가 send해준 값을 recv로 받을 수 있다.
- 데이터를 보내고 받을 때 byte단위로 작업되기 때문에 1byte인 char을 사용했다.
- 보통 패킷의 형태로 보내고 받게되고, 패킷은 패킷크기, 메시지형식, 데이터1, 데이터2 ..... 이런식으로 보낸다.
클라이언트
int main()
{
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN servAddr;
// 1. 윈속 초기화
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
cout << "Failed WSAStartup()" << endl;
// 2. socket 생성
// 물리적인 모양이 다르다, sw는 함수의 인자로 모양을 결정
// ip 형식, tcp, udp 등의 통신 환경 세팅
// SOCK_STREAM : tcp
// SOCK_DGRAM : udp
hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 3. 서버 연결
// 3-1. 연결할 서버 주소 셋팅
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET; // IPv4 사용 세팅
inet_pton(AF_INET, "127.0.0.1", &servAddr.sin_addr);
servAddr.sin_port = htons(30002);
// 3-2 서버에 연결 시도
// blocking 함수, 완료까지 대기
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
cout << "Failed connect()" << endl;
// 4. 서버로 부터 통신 대기
int recvSize;
char recvData[255];
// 값이 들어올 때 까지 대기
recvSize = recv(hSocket, recvData, sizeof(recvData), 0);
if (recvSize == -1)
cout << "recv() Error" << endl;
cout << "recv " << recvSize << " message : " << recvData << endl;
// 5. 소켓 종료 -> 윈속 종료
closesocket(hSocket);
WSACleanup();
system("pause");
return 0;
}
bind(listenSocket, &sockaddr, sockaddrSize)
- 서버의 부분에서는 몇가지 추가 작업이 필요로 한다. 그 중 하나가 bind이다.
- 소켓에 servAddr에서 초기화 한 ip, port 등을 묶어준다.
- 한마디로 이러한 ip와 port를 socket에서 사용할거에요 알려주는 형식
listen(listenSocket, 최대 접속승인 수)
- listenSocket을 이용해 접속을 승인할 수 있는 접속 대기 상태로 만들어주는 역할을 한다.
- 한마디로 상대방에서 연결을 주면 해당 연결을 받을 수 있는 서버 상태로 만들어 주는 역할
accept(listenSocket, sockaddr&, sockaddrSize)
- 클라이언트에서 connect()를 호출했다면 서버에서 accept()를 통해 연결 요청을 승인하는 부분이다.
- 연결 요청이 들어올 때 까지 대기한다.
- 연결이 완료된다면 연결이 된 소켓을 반환하게 된다.
- 이 반환된 소켓을 통해 클라이언트와 상호작용을 하게 된다.
send()
- 수신과 같은 맥락으로 데이터를 보내는 부분이다.
- clientsocket과 연결을 끊고(종료)하고, 연결을 받는 소켓 listensocket도 종료하고
- WSACleanup()을 통해 소켓 통신을 종료한다.
서버
int main()
{
WSADATA wsaData;
SOCKET listenSocket; // listen 전용 소켓
SOCKADDR_IN servAddr;
// 1. 윈속 초기화
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
cout << "Failed WSAStartup()" << endl;
// 2. socket 생성
listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket == INVALID_SOCKET)
cout << "Failed socket()" << endl;
// 3. 서버 자신의 주소를 셋팅
// 3-1. 서버 주소 셋팅
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servAddr.sin_addr);
servAddr.sin_port = htons(30002);
// socket과 local address를 연결시켜주는 함수
// socket을 만들고, listen 할 주소를 socket에 연결시켜주는 함수
if (bind(listenSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
cout << "Binding Error" << endl;
// 연결을 수신하는 상태로 소켓의 상태 변경
// 소켓을 접속 대기 상태로 만들어준다.
// 두번째 인자는 한꺼번에 요청 가능한 최대 접속승인 수를 의미
if (listen(listenSocket, 5) == SOCKET_ERROR)
cout << "listen Error" << endl;
// 3-2. 클라이언트 연결 기다리기
SOCKADDR_IN clientAddr;
SOCKET clientSocket;
int sizeClientAddr = sizeof(clientAddr);
clientSocket = accept(listenSocket, (SOCKADDR*)&clientAddr, &sizeClientAddr);
if (clientSocket == SOCKET_ERROR)
cout << "Failed accept()" << endl;
// 4. 클라이언트에게 send
char sendData[255] = "안녕";
send(clientSocket, sendData, sizeof(sendData)+1, 0);
// 5. 소켓 종료 -> 윈속 종료
closesocket(clientSocket);
closesocket(listenSocket);
WSACleanup();
return 0;
}