#include "../../Common.h" // 에러 처리 및 공통 함수 정의 파일
#define SERVERPORT 9000 // 서버가 사용할 포트 번호
#define BUFSIZE 512 // 버퍼 사이즈
struct SOCKETINFO {
SOCKET sock, // 클라이언트 소켓
char buf[BUFSIZE + 1]; // 송수신 데이터 버퍼
int recvbytes; // 수신된 바이트 수
int sendbytes; // 송신된 바이트 수
};
int nTotalSockets = 0; // 현재 활성화된 클라이언트 수
SOCKETINFO *SocketInfoArray[FD_SETSIZE]; // 클라이언트 소켓 정보 배열
FD_SETSIZE = 최대 클라이언트 수 (기본 64개)
bool AddSocketInfo(SOCKET sock); // 클라이언트 소켓 정보를 추가
void RemoveSocketInfo(int nIndex); // 클라이언트 소켓 정보를 제거
AddSocketInfo: 새 클라이언트를 소켓 배열에 추가
RemoveSocketInfo: 연결이 종료된 클라이언트를 소켓 배열에 추가
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return 1;
WSAStartup: 윈도우 소켓 라이브러리를 초기화, MAKEWORD(2, 2)는 버전 2.2를 요청
4.2. 소켓 생성
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET) err_quit("socket()");
socket: TCP 소켓 생성
AF_INET: IPv4 사용
SOCK_STREAM: 연결 지향 TCP 프로토콜
4.3. 소켓 바인딩
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(SERVERPORT);
retval = bind(listen_sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
if (retval == SOCKET_ERROR) qerr_quit("bind()");
bind: 서버 소켓에 IP 주소와 포트 할당
INADDR_ANY: 서버가 모든 IP 주소에서 연결을 수락
htons: 포트를 네트워크 바이트 순서로 변환
4.4. 연결 대기
retval = listen(listen_sock, SOMAXCONN)
if (retval == SOCKET_ERROR) err_quit("listen()");
listen: 클라이언트 연결 요청 대기
SOMAXCONN: 최대 허용 대기 큐 크기
4.5. 비동기 I/O 설정
u_long on = 1;
retval = ioctlsocket(listen_sock, FIONBIO, &on);
if (retval == SOCKET_ERROR) err_display("ioctlsocket()");
ioctlsocket: 소켓을 비동기 (논블로킹) 모드로 전환
4.6. select 준비
fd_set rset, wset
int nready;
SOCKET client_sock;
struct sockaddr_in clientaddr;
int addrlen;
fd_set: 읽기와 쓰기를 감시할 소켓 그룹
4.7. 메인 루프: 이벤트 처리
4.7.1. 소켓 그룹 초기화
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_SET(listen_sock, &rset);
for (int i = 0; i < nTotalSockets; i++) {
if (SocketInfoArray[i]->recvbytes > SocketInfoArray[i]->sendbytes)
FD_SET(SocketInfoArray[i]->sock, &wset); // 전송 필요
else
(SocketInfoArray[i]->sock, &rset); // 수신 필요
}
리슨 소켓: 클라이언트 요청 수락을 위해 항상 읽기 그룹에 추가
클라이언트 소켓: 데이터 상태에 따라 읽기 또는 쓰기 그룹에 추가
4.7.2. 이벤트 감시
nready = select(0, &rset, &wset, NULL, NULL);
if (nready == SOCKET_ERROR) err_quit("select()");
select: 소켓 상태를 감시하여 이벤트 발생 여부 확인
4.7.3. 새로운 연결 처리
if (FD_ISSET(listen_sock, &rset)) {
addrlen = sizeof(clientaddr);
client_sock = accept(listen_sock, (struct sockaddr *)&clientaddr, &addrlen);
if (client_sock == INVALID_SOCKET) {
err_display("accept()");
break;
}
else {
// 클라이언트 연결 처리
char addr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr)
accept: 클라이언트 연결 요청 수락
AddSocketInfo: 클라이언트를 소켓 배열에 추가
4.7.4. 데이터 수신 및 송신 처리
for (int i = 0; i < nTotalSockets; i++) {
SOCKETINFO *ptr = SocketInfoArray[i];
if (FD_ISSET(ptr->sock, &rset)) {
// 데이터 수신
} else if (FD_ISSET(ptr->sock, &wset)) {
// 데이터 송신
}
}
읽기 그룹: 데이터 수신 처리
쓰기 그룹: 데이터 송신 처리
클라이언트 소켓 관리
5.1. 클라이언트 추가
bool AddSocketInfo(SOCKET sock) {
if (nTotalSockets >= FD_SETSIZE) {
printf("[오류] 최대 소켓 수 초과\n");
return false;
}
SOCKETINFO *ptr = new SOCKETINFO;
if (ptr == NULL) {
printf("[오류] 메모리 부족\n");
return false;
}
ptr->sock = sock;
ptr->recvbytes = 0;
ptr->sendbytes = 0;
SocketInfoArray[nTotalSockets++] = ptr;
return true;
}
새로운 클라이언트를 배열에 추가하고 데이터 초기화
5.2. 클라이언트 제거
void RemoveSocketInfo(int nIndex) {
SOCKETINFO *ptr = SocketInfoArray[nIndex];
struct sockaddr_in clientaddr;
int addrlen = sizeof(clientaddr);
getpeername(ptr->sock, (struct sockaddr *)&clientaddr, &addrlen;
char addr{INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));
printf("[TCP 종료] 클라이언트 연결: IP 주소=%s, 포트 번호="%d\n",
addr, ntohs(clientaddr.sin_port));
closesocket(ptr->sock);
delete ptr;
if (nIndex != (nTotalSockets - 1))
SocketInfoArray[nIndex] = SocketInfoArray[nTotalSockets - 1];
--nTotalSockets;
}
연결 종료된 클라이언트를 배열에서 제거하고 소켓 닫기
주요 헤더 파일과 매크로 정의
#include "..\..\Common.h"
#define SERVERPORT 9000
#define BUFSIZE 512
#define WM_SOCKET (WM_USER+1)
2. SOCKETINFO 구조체
```c
struct SOCKETINFO {
SOCKET sock;
char buf[BUFSIZE + 1];
int recvbytes;
int sendbytes;
bool recvdelayed;
SOCKETINFO *next;
}
SOCKETINFO: 각 클라이언트와 관련된 소켓 정보를 저장하는 구조체
WndProc: 윈도우 메시지 처리
LRESUlT CALLBACK WndProc(HWND hwnd, UNIT uMsg, WPARAM wParam, LPARAM lParam)
WM_SOCKET: 소켓 이벤트 (FD_ACCEPT, FD_READ, FD_WRITE, FD_CLOSE)를 처리
실제 작업은 ProcessSocketMessage()에서 수행
WM_DESTROY: 윈도우가 종료될 때 메세지 루프를 종료
main 함수
4.1. 윈도우 생성
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfWndProc = WndProc;
RegisterClass(&wndclass);
HWND hWnd = CreateWindow(_T("MyWndClass"), _T("TCP 서버"), ...);
윈도우 클래스와 윈도우를 생성하여 서버 GUI 역할을 수행.
4.2. WSAStartup 초기화
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return 1;
윈도우 소켓 라이브러리 초기화
4.3. 소켓 생성 및 바인드
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(SERVERPORT);
bind(listen_sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
서버 소켓 생성 후 bind()로 서버 주소와 포트 번호를 설정
4.4. 소켓 이벤트 설정
retval = WSAAsyncSelect(listen_sock, hWnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE);
소켓 이벤트를 비동기적으로 처리하기 위해 WSAAsyncSelect() 사용.
이벤트: FD_ACCEPT(클라이언트 연결), FD_CLOSE(연결 종료)
ProcessSocketMessage 함수
5.1. FD_ACCEPT (클라이언트 연결 수락)
client_sock = accept(wParam, (struct sockaddr *)&clientaddr, &addrlen);
printf("[TCP 서버] 클라이언트 연결: IP 주소=%s, 포트 번호=%d\n",
inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
클라이언트 연결을 수락하고 새로운 소켓을 생성.
소켓 정보는 AddSocketInfo()를 통해 리스트에 추가.
5.2. FD_READ (데이터 수신)
retval = recv(ptr->sock, ptr->buf, BUFSIZE, 0);
ptr->buf[retval] = '\0';
printf("[TCP\%s:%d] %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), ptr->buf);
클라이언트로부터 데이터를 수신, 버퍼에 저장
수신한 데이터를 출력
5-3. FD_WRITE (데이터 송신)
retval = send(ptr->sock, ptr->buf + ptr->sendbytes, ptr->recvbytes - ptr->sendbytes, 0);
ptr->sendbytes += retval;
if (ptr->recvbytes == ptr->sendbytes) {
ptr->recvbytes = ptr->sendbytes = 0;
}
클라이언트로 데이터를 전송하고, 전송된 만큼 송신 크기를 업데이트.
5.4. FD_CLOSE (연결 종료)
RemoveSocketInfo(wParam);
연결된 클라이언트 소켓을 리스트에서 제거하고 소켓을 닫음.
AddSocketInfo, GetSocketInfo, RemoveSocketInfo 함수
6.1. AddSocketInfo
SOCKETINFO *ptr = new SOCKETINFO;
ptr->sock = sock;
// 생략
ptr->next = SocketInfoList;
SocketInfoList = ptr;
새로운 클라이언트 소켓 정보를 SocketInfoList에 추가.
6.2. GetSocketInfo
SOCKETINFO *ptr = SocketInfoList;
while (ptr) {
if (ptr->sock == sock)
return ptr;
ptr = ptr->next;
}
return NULL;
특정 소켓 핸들에 해당하는 정보를 리스트에서 검색.
6.3. RemoveSocketInfo
if (curr->sock == sock)
if (prev)