개발 서적 리뷰/게임서버 프로그래머 책
[게임서버프로그래밍#6] 클라이언트-서버 구현을 위한 소켓 기초 정리
거북이의 기술블로그
2025. 4. 29. 00:56
이 글에서는 소켓 통신의 기초부터, Blocking/Non-Blocking 구조, Winsock2 함수(ioctlsocket, getsockopt, select) 사용법, 클라이언트-서버 프로그램 기본 흐름까지 실제 코드 예제와 함께 설명한다.
소켓(Socket)이란 무엇인가
- 소켓은 네트워크 연결을 제어하는 핸들이다.
- 파일 핸들처럼 읽고 쓰기를 담당하지만 대상이 네트워크다.
Blocking 통신 구조
Blocking 통신은 소켓 함수 호출 시 완료될 때까지 대기하는 구조를 의미한다.
* 클라이언트 Blocking 흐름
- socket() : 소켓 핸들 생성
- bind() : 클라이언트 포트 설정
- connect() : 서버 연결 (연결 완료될 때까지 대기)
- send() : 송신 버퍼에 데이터 저장 (버퍼가 꽉 차면 Blocking)
- close() : 소켓 닫기
* 서버 Blocking 흐름
- socket() : 소켓 핸들 생성
- bind() : 서버 포트 설정
- listen() : 연결 요청 대기
- accept() : 클라이언트 연결 수락 (연결 전까지 Blocking)
- recv() : 데이터 수신 (버퍼에 데이터 올 때까지 대기)
- close() : 소켓 닫기
비동기(Non-Blocking) 통신 구조
Non-Blocking 통신은 소켓 함수 호출이 즉시 반환되어 다른 작업을 할 수 있도록 한다.
- ioctlsocket()을 사용하여 소켓을 비동기 모드로 설정한다.
- select()를 통해 소켓의 I/O 가능 여부를 확인한다.
* Non-Blocking 클라이언트 흐름
- socket() : 소켓 핸들 생성
- ioctlsocket(sock, FIONBIO, &mode) : 비동기 모드 설정
- connect() 호출 → Would Block 오류 무시
- select()로 연결 완료 여부 확인
- send()/recv() 전에 select()로 I/O 가능 상태를 확인
* Non-Blocking 서버 흐름
- socket() : 소켓 핸들 생성
- ioctlsocket(sock, FIONBIO, &mode) : 비동기 모드 설정
- listen()으로 연결 요청 대기
- select()를 통해 accept() 가능 여부 탐지
- 연결된 소켓에 대해 recv(), send() 진행
주요 함수 정리
1. ioctlsocket()
소켓 핸들에 명령을 내려 속성을 설정한다.
- FIONBIO : 동기/비동기 설정
- FIONREAD : 읽을 수 있는 데이터 양 확인
- SIO_KEEPALIVE_VALS : TCP Keep-Alive 시간 변경
- SIO_GET_EXTENSION_FUNCTION_POINTER : 확장 함수 포인터 획득
2. getsockopt()
소켓의 현재 상태나 설정값을 조회할 수 있다.
- SO_REUSEADDR : 주소 재사용 허용
- SO_KEEPALIVE : 유휴 연결 상태 유지
- SO_LINGER : 소켓 종료 시 데이터 전송 여부 설정
- SO_SNDBUF / SO_RCVBUF : 송수신 버퍼 크기 설정
- SO_ERROR : 소켓 오류 상태 확인
3. select()
복수 소켓의 상태를 감시할 수 있다.
FD_ZERO(), FD_SET() 매크로와 함께 사용한다.
fd_set 종류 | 감시 내용 |
readfds | 읽기(read) 가능 여부 확인 |
writefds | 쓰기(write) 가능 여부 확인 |
exceptfds | 오류 발생 여부 확인 |
FD_ZERO(&readSet);
FD_SET(sock, &readSet);
timeval timeout = {1, 0}; // 1초 대기
int result = select(0, &readSet, nullptr, nullptr, &timeout);
if (result > 0 && FD_ISSET(sock, &readSet)) {
recv(sock, buffer, size, 0);
}
Blocking vs Non-Blocking 비교
항목 | Blocking 구조 | Non-Blocking 구조 |
대기 여부 | 함수 호출 시 완료될 때까지 대기 | 함수 호출 즉시 반환 |
구현 난이도 | 비교적 간단 | select/poll 로직 필요 |
성능 | 적은 클라이언트 처리에 적합 | 대규모 동시 접속 처리에 적합 |
Non-Blocking에서 주의할 점
- Would Block 에러는 정상:
아직 소켓이 준비되지 않았다는 의미이며, select()로 대기 후 재시도해야 한다. - 0바이트 송신법:
Non-Blocking connect() 후, 연결 성공 여부를 확인할 때 0바이트를 송신해본다. - CPU 낭비 주의:
select()에 timeout을 설정하여 무한 루프에서 CPU를 소모하지 않도록 한다. - UDP 주의사항:
송신버퍼는 남아있더라도 패킷 크기가 맞지 않으면 보내지 못할 수 있다.
실제 코드 흐름 요약
* 클라이언트
Winsock winsock;
ClientSocket client("localhost", "27015");
client.send("Hello");
auto response = client.receive(512);
* 서버
Winsock winsock;
ServerSocket server;
server.accept_connection();
server.socket_recv("Hello from server");