3 모델 코드 정리

Hyun·2024년 12월 6일
0

1. Select 모델

  1. 헤더와 상수 정의
#include "../../Common.h" // 에러 처리 및 공통 함수 정의 파일

#define SERVERPORT	9000 // 서버가 사용할 포트 번호
#define BUFSIZE		512 // 버퍼 사이즈
  1. 소켓 정보 구조체와 전역 변수
struct SOCKETINFO {
	SOCKET sock, // 클라이언트 소켓
    char buf[BUFSIZE + 1]; // 송수신 데이터 버퍼
    int recvbytes; // 수신된 바이트 수
    int sendbytes; // 송신된 바이트 수
};

int nTotalSockets = 0; // 현재 활성화된 클라이언트 수
SOCKETINFO *SocketInfoArray[FD_SETSIZE]; // 클라이언트 소켓 정보 배열

FD_SETSIZE = 최대 클라이언트 수 (기본 64개)

  1. 함수 선언
bool AddSocketInfo(SOCKET sock); // 클라이언트 소켓 정보를 추가
void RemoveSocketInfo(int nIndex); // 클라이언트 소켓 정보를 제거

AddSocketInfo: 새 클라이언트를 소켓 배열에 추가
RemoveSocketInfo: 연결이 종료된 클라이언트를 소켓 배열에 추가

  1. main 함수: 서버 설정과 실행
    4.1. WSA 초기화
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)) {
       // 데이터 송신
       }
    }

    읽기 그룹: 데이터 수신 처리
    쓰기 그룹: 데이터 송신 처리

  1. 클라이언트 소켓 관리
    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;
    }

    연결 종료된 클라이언트를 배열에서 제거하고 소켓 닫기

    2. WSAAsyncSelect() 모델

  2. 주요 헤더 파일과 매크로 정의

    #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: 각 클라이언트와 관련된 소켓 정보를 저장하는 구조체

  • sock: 소켓 핸들
  • buf: 클라이언트와의 송수신 데이터 저장 버퍼
  • recvbytes: 수신한 데이터 크기
  • sendbytes: 송신 완료한 데이터 크기
  • recvdelayed: 지연된 수신 여부
  • next: 다음 노드를 가리키는 포인터 (연결 리스트로 구현)
  1. 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: 윈도우가 종료될 때 메세지 루프를 종료

  2. 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(연결 종료)

  3. 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);

    연결된 클라이언트 소켓을 리스트에서 제거하고 소켓을 닫음.

  4. 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)
       

0개의 댓글

관련 채용 정보