일본에 있으면서.. 슬슬 어학과정도 끝나고해서, 이전의 프로그래밍 감각을 살려볼까 하는 취지에
만들었습니다.

이전 학교에서 턴프로젝트로 만들었던 리눅스 epoll 소스를 사용해서 (덕분에 DB는 MySQL을 사용했습니다)
윈도우 IOCP 서버 구축에 1주일,
클라이언트 제작에 3일.. 서로 데이터 동기화에 3일
총 2주 걸렸네요 orz

일단 결과물 입니다..
<서버 화면>

별볼일 없는 콘솔 화면 입니다..
현재 서버 시간을 1초 마다 갱신하게 만들었습니다.
위의 /last 같은것은 명령어 입니다.


다음 클라이언트 화면 입니다.
3D를 하는것이 좋긴 하지만, 그런거 빠지다간 끝이 없기때문에,
간단하게 현재 서버가 잘 작동되는지 눈으로 확인 가능한 2D GUI 클라이언트 입니다.
DirectX 2D 조차 쓰지 않은 순전 MFC 입니다.. @_@
맵도 원래는 바둑판처럼 가이드선을 그을려고 했는데
재미 없을꺼 같아서 소프트맥스의 창세기 외전2 템페스트의 세계 지도를 가져와 멋대로 붙였습니다.

밑에 서버 IP, port 입력하고 (디폴트 127.0.0.1:9000) connect 하면 서버에 연결됩니다.

ID와 암호를 채팅창으로 입력하면
플레이어 (마리오), 주변의 적(쿠퍼) 들이 뜹니다.

월드의 전체 크기는 5000x3200 으로 적은 렌덤으로 1000개가 뿌려지게 했습니다.
그리고 내 주변의 적들의 위치(거리 약 400), 방향, 타입, 등등의 위치를 recv 받아서 근처에 뿌려줍니다.

물론 다른 플레이어(클라이언트)도 마리오 형상으로 띄어줍니다.
단, 마리오 머리 위에 해당 클라이언트의 ID를 띄어놓게 했습니다.

채팅서버가 기본이므로. 채팅이 제대로 되는지 확인하기 위해 3개의 클라이언트를 띄었습니다.
클라이언트의 채팅은, 자기 주변 (약 200)안의 것들만 통신이 되게 했습니다.

1번 클라이언트 Miss.Ninja의 화면


2번 클라이언트의  Miss.Kim의 화면


3번 클라이언트 Miss.Rin의 화면


채팅내용을 잘 살펴보시면
가운데 닌자 클라이언트는 Kim과 Rin의 이야기를 다 듣지만, 다른 클라이언트는 닌자 클라이언트만 통신이 됩니다.


#include <winsock2.h>
#include <stdlib.h>
#include <stdio.h>

#define BUFSIZE 512

// 소켓정보를저장하기위한구조체
struct SOCKETINFO

    OVERLAPPED overlapped; 
    SOCKET sock; 
    char buf[BUFSIZE+1]; 
    int recvbytes; 
    int sendbytes; 
    WSABUF wsabuf;
};

 // 소켓입출력함수
DWORD WINAPI WorkerThread(LPVOID arg);

// 오류출력함수
void err_quit(char *msg);
void err_display(char *msg); 
 
int accept_proc(SOCKET *listen_sock, HANDLE *hcp)

    int retval; 
    // accept() 
    SOCKADDR_IN clientaddr; 
    int addrlen = sizeof(clientaddr); 
    SOCKET client_sock = accept(*listen_sock, (SOCKADDR *)&clientaddr, &addrlen); 
    if(client_sock == INVALID_SOCKET) 
    { 
        err_display("accept()"); 
        return -1; 
    } 
   printf("[TCP 서버] 클라이언트접속: IP 주소= %s, 포트번호= %d \n"
        inet_ntoa(clientaddr.sin_addr), 
        ntohs(clientaddr.sin_port)); 
 
    // 소켓과입출력완료포트연결 
    HANDLE hResult = CreateIoCompletionPort((HANDLE)client_sock, 
        *hcp, (DWORD)client_sock, 0); 
    if(hResult == NULL) 
    { 
        err_quit("create iocp"); 
    } 

     // 소켓정보구조체할당 
    SOCKETINFO *ptr = new SOCKETINFO; 
    if(ptr == NULL) 
    { 
        err_quit("[오류] 메모리부족!!\n"); 
    } 
    ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped)); 
    ptr->sock = client_sock; 
    ptr->recvbytes = 0; 
    ptr->sendbytes = 0; 
    ptr->wsabuf.buf = ptr->buf; 
    ptr->wsabuf.len = BUFSIZE; 
 
    // 비동기입출력시작 
    DWORD recvbytes; 
    DWORD flags = 0; 
    retval = WSARecv(client_sock, &(ptr->wsabuf), 1, &recvbytes, &flags, &(ptr->
overlapped), NULL);

    if(retval == SOCKET_ERROR) 
    { 
        if(WSAGetLastError() != ERROR_IO_PENDING) 
        { 
            err_display("WSARecv()"); 
        } 
        return -1; 
    } 
 
    return 0;

 
int is_log_out(DWORD *cbTransferred, SOCKET *client_sock, SOCKETINFO *ptr, SOCKADDR_IN *clientaddr, int retval)
{         
    // 비동기입출력결과확인 
    if(retval == 0 || *cbTransferred == 0) 
    { 
        if(retval == 0) 
        { 
            DWORD temp1, temp2; 
            WSAGetOverlappedResult(ptr->sock, &(ptr->overlapped), 
                &temp1, FALSE, &temp2); 
            err_display("WSAGetOverlappedResult()"); 
        } 
        closesocket(ptr->sock); 
        printf("[TCP 서버] 클라이언트종료: IP 주소= %s, 포트번호= %d \n"
            inet_ntoa(clientaddr->sin_addr), 
            ntohs(clientaddr->sin_port)); 
 
        delete ptr; 
        return 1; 
    } 
 
    return 0;

 
int send_proc(SOCKETINFO *ptr)

    int retval; 
 
    // 데이터보내기 
    ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped)); 
    ptr->wsabuf.buf = ptr->buf + ptr->sendbytes; 
    ptr->wsabuf.len = ptr->recvbytes - ptr->sendbytes; 
 
    DWORD sendbytes; 
    retval = WSASend(ptr->sock, &(ptr->wsabuf), 1, &sendbytes, 0, &(ptr->overlapped),
NULL); 
    if(retval == SOCKET_ERROR) 
    { 
        if(WSAGetLastError() != WSA_IO_PENDING) 
        { 
            err_display("WSASend()"); 
        } 
        return -1; 
    } 
    return 0;


int recv_proc(SOCKETINFO *ptr)

    int retval; 
 
    ptr->recvbytes = 0; 
 
    // 데이터받기 
    ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped)); 
    ptr->wsabuf.buf = ptr->buf; 
    ptr->wsabuf.len = BUFSIZE; 
 
    DWORD recvbytes; 
    DWORD flags = 0; 
    retval = WSARecv(ptr->sock, &(ptr->wsabuf), 1, 
        &recvbytes, &flags, &(ptr->overlapped), NULL); 
    if(retval == SOCKET_ERROR) 
    { 
        if(WSAGetLastError() != WSA_IO_PENDING) 
        { 
            err_display("WSARecv()"); 
        } 
        return -1; 
    } 
    return 0;

 
int main()

    int retval; 

    // 윈속초기화 
    WSADATA wsa; 
    if(WSAStartup(MAKEWORD(2,2), &wsa) != 0) 
        return -1; 
 
    // 입출력완료포트생성 
    HANDLE hcp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); 
    if(hcp == NULL) 
        return -1; 
 
    // CPU 갯수확인 
    SYSTEM_INFO si; 
    GetSystemInfo(&si); 
 
    // (CPU 개수* 2)개의작업자스레드생성 
    HANDLE hThread; 
    DWORD ThreadId; 
    for(int i=0; i < (int)si.dwNumberOfProcessors*2; ++i) 
    { 
        hThread = CreateThread(NULL, 0, WorkerThread, hcp, 0, &ThreadId); 
        if(hThread == NULL) 
            return -1; 
        CloseHandle(hThread); 
    } 
 
    // socket() 
    SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0); 
    if(listen_sock == INVALID_SOCKET) 
        err_quit("socket()"); 
 
    // bind() 
    SOCKADDR_IN serveraddr; 
    ZeroMemory(&serveraddr, sizeof(serveraddr)); 
    serveraddr.sin_family = AF_INET; 
    serveraddr.sin_port = htons(9000); 
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    retval = bind(listen_sock, (SOCKADDR *)&serveraddr, sizeof(serveraddr)); 
    if(retval == SOCKET_ERROR) 
        err_quit("bind()"); 
 
    // listen() 
    retval = listen(listen_sock, SOMAXCONN); 
    if(retval == SOCKET_ERROR) 
        err_quit("listen()"); 
 
    while(1) 
    { 
        if(accept_proc(&listen_sock, &hcp) == -1) 
            continue
    } 
 
    // 윈속종료 
    WSACleanup(); 
    return 0;

 
DWORD WINAPI WorkerThread(LPVOID arg)

    HANDLE hcp = (HANDLE)arg; 
    int retval; 
 
    while(1) 
    { 
        // 비동기입출력완료기다리기 
        DWORD cbTransferred; 
        SOCKET client_sock; 
        SOCKETINFO *ptr; 
        retval = GetQueuedCompletionStatus(hcp, &cbTransferred, 
            (LPDWORD)&client_sock, (LPOVERLAPPED*)&ptr, INFINITE); 
 
        // 클라이언트정보얻기 
        SOCKADDR_IN clientaddr; 
        int addrlen = sizeof(clientaddr); 
        getpeername(ptr->sock, (SOCKADDR*)&clientaddr, &addrlen); 
     
        if(is_log_out(&cbTransferred, &client_sock, ptr, &clientaddr, retval) == 1) 
            continue
 
        // 데이터전송량갱신 
        if(ptr->recvbytes == 0) 
        { 
            ptr->recvbytes = cbTransferred; 
            ptr->sendbytes = 0; 
 
            // 받은데이터출력 
            ptr->buf[ptr->recvbytes] = '\0'
            printf("[TCP/%s:%d]%s\n"
                    inet_ntoa(clientaddr.sin_addr), 
                    ntohs(clientaddr.sin_port), ptr->buf); 
        } 
        else 
        { 
            ptr->sendbytes += cbTransferred; 
        } 
 
        if(ptr->recvbytes > ptr->sendbytes) 
        { 
            if(send_proc(ptr) == -1) 
                continue
        } 
        else 
        { 
            if(recv_proc(ptr) == -1) 
                continue
        } 
    } 
     
    return 0;

 

// 소켓함수오류출력후종료
void err_quit(char *msg)

    LPVOID lpMsgBuf; 
    FormatMessage( 
        FORMAT_MESSAGE_ALLOCATE_BUFFER| 
        FORMAT_MESSAGE_FROM_SYSTEM, 
        NULL, WSAGetLastError(), 
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
        (LPTSTR)&lpMsgBuf, 0, NULL); 
    MessageBox(NULL, (LPCTSTR)lpMsgBuf, msg, MB_ICONERROR); 
    LocalFree(lpMsgBuf); 
    exit(-1);
}

// 소켓함수오류출력
void err_display(char *msg)

    LPVOID lpMsgBuf; 
    FormatMessage( 
        FORMAT_MESSAGE_ALLOCATE_BUFFER| 
        FORMAT_MESSAGE_FROM_SYSTEM, 
        NULL, WSAGetLastError(), 
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
        (LPTSTR)&lpMsgBuf, 0, NULL); 
    printf("[%s] %s", msg, (LPCTSTR)lpMsgBuf); 
    LocalFree(lpMsgBuf);
}

 인터넷에 떠도는 IOCP 에코 서버를 나름대로 잘라 보았습니다.
(그래봤자.. 어디서 logout, accept, send, recv 하는걸 함수채로 자른 것 밖에 없습니다;; )

Epoll은 기존 select 모델의 한계를 뛰어넘는 새로운 스타일의 서버입니다.

기존 select의 경우 일정 비트열을 두고 그곳에 0 이냐 1이냐로 클라이언트의 응답을 기다리는 방식이라 약 300~500 클라이언트 이상 동접시 속도저하 문제가 나타난다고 합니다.

(이 말은 즉, 동접 300 가량 정도는 select 으로 써도 상관은 없다는 얘기죠;;)
 

그래서 이를 해결하기 위해 아예 하드웨어적으로 클라이언트의 접속 변화를 감지해서 recv send냐 를 판별해 주는 함수가 epoll 이라 합니다.

 

문제는 이게.. 리눅스에서 만든거라 윈도우에선 사용이 불가능합니다.

그리고 리눅스도 커널 2.6 이상에서만 된다고 합니다.

 

하여간 소스 들어갑니다.

소스가.. 일단 숙제용으로서.. 게임서버 시간의 숙제를 하다가 이것저것 조사하고 찾은걸
(인터넷과 man 메뉴얼)로 짜집기 한겁니다.. orz
 

chatServer.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <sys/epoll.h>                     // 이게 중요

#include <arpa/inet.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <netinet/tcp.h>

#include <fcntl.h>

#include <sys/ioctl.h>

#include <errno.h>

 

#define EPOLL_SIZE 60

#define EPOLL_EVENT_SIZE 100

#define OPENPORT 9000                       // open port 번호


// main

int

main (int argc, char **argv)

{

        struct epoll_event *events;          // epoll 관련

        struct epoll_event ev;        

        struct sockaddr_in addr, clientaddr;

        int clilen;

        int sfd, efd, cfd;                   // server 소켓, epoll 소켓, client 소켓

        int i,j;                             // for문을 위해

        int max_got_events;

        int result;

        int readn;

        int sendflags = 0;

        char buf_in[256] = { '\0' };         // 버퍼

        int count = 0;

 
        // epoll 소켓 생성

        if ((efd = epoll_create (EPOLL_EVENT_SIZE)) < 0)

        {

                perror ("epoll_create (1) error");

                return -1;

        }

 

        // init pool

        events = (struct epoll_event *) malloc (sizeof (*events) * EPOLL_SIZE);

        if (NULL == events)

        {

                perror ("epoll_create (0) error");

                return -1;

        }

 
        // 서버 소켓 생성

        clilen = sizeof (clientaddr);

        sfd = socket (AF_INET, SOCK_STREAM, 0);

        if (sfd == -1)

        {

                perror ("socket error :");

                close (sfd);

                return -1;

        }


        // 소켓 정보 넣고 bind, listen 처리 

        addr.sin_family = AF_INET;

        addr.sin_port = htons (OPENPORT);

        addr.sin_addr.s_addr = htonl (INADDR_ANY);

        if (bind (sfd, (struct sockaddr *) &addr, sizeof (addr)) == -1)

        {

                close (sfd);

                return -2;

        }

        listen (sfd, 5);

 

        if (sfd < 0)

        {

                perror ("init_acceptsock error");

                return 1;

        }


        // 서버시작, epoll 이벤트 설정 및 컨트롤 함수로 활성화 

        printf("Running Server port %d\n",OPENPORT);

        ev.events = EPOLLIN;

        ev.data.fd = sfd;

        result = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &ev);

        if (result < 0)

        {

                perror ("epoll_ctl error");

                return 1;

        }

 

        // 서버 실제 루트
       
while (1)

        {

                // epoll 이벤트 대기 (클라이언트의 반응(접속 or send) 감지)
               
max_got_events = epoll_wait (efd, events, EPOLL_SIZE, -1);

             
                // epoll 이벤트 받은만큼의 숫자만큼 for
               
for (i = 0; i < max_got_events; i++)

                {

                        // 이벤트 들어온 소켓이 연결 요청이면
                       
if (events[i].data.fd == sfd)

                        {

                                cfd = accept (sfd, (struct sockaddr *)

                                                &clientaddr, &clilen);

                                if (cfd < 0)

                                {

                                        perror ("Accept error");

                                        return -1;

                                }

 

                                ev.events = EPOLLIN;

                                ev.data.fd = cfd;

                                epoll_ctl (efd, EPOLL_CTL_ADD, cfd, &ev);

 

                                printf("Accepted socket : %d\n",cfd);

                                count++;

                        }

                        // 그게 아니면,
                       
else

                        {

                                cfd = events[i].data.fd;

 

                                memset (buf_in, 0x00, 256);

                                readn = read (cfd, buf_in, 255);

                                // if it occured ploblems with reading,

                                // delete from epoll event pool and close socket                                                                                                                        // readn 값이 0 이하이면 recv 처리

                                if (readn <= 0)

                                {

                                        epoll_ctl(efd, EPOLL_CTL_DEL, cfd, &ev);

                                        close (cfd);

                                        printf ("Close fd %d\n", cfd);

 

                                        count--;

                                }

                                else

                                {

                                // 아니면 send 처리
                                //      printf ("%s", buf_in);

                                        for(j=5; j<count+5; j++)

                                        {

                                          // 단 이벤트로온 client엔 send 하지말고
                                          // 다른 모든 클라이언트에 send 해서 채팅

                                               
if(cfd != j)

                                                        send (j, buf_in,

                                                         strlen (buf_in),

                                                         sendflags);

                                        }

                                }

 

                        }

                }

        }

        return 1;

}

 

 

맨 아래쪽의

For j=5 라고 했는데, 리눅스에선 소켓 번호가 윈도우와 달리 5번부터인가 시작되서 그렇습니다.

윈도우는 OS가 자기 맘대로 할당하지만 리눅스에선 0~4번이 정해져 있고 그 이후 차례대로 count 됩니다..
물론 저 소스는 야메로 짠거라 문제 발생 요지가 많습니다. Orz

 

참고로 클라이언트는

그냥 telnet으로 telnet 127.0.0.1 9000 으로 접속하면 됩니다.

아래 소스는 인터넷에 떠돌고 있는 리눅스용 select 서버를 가져와서 윈도우 용으로 수정하였습니다.

출처는 : http://vessel.tistory.com/95 입니다.

 

리눅스에선 서버만 만들어 놓고 클라이언트는 telnet으로 접속이 가능합니다만.

저는 이걸 윈도우 용으로 전환하면서 클라이언트 프로그램도 그냥 만들었습니다… orz

윈도우도 telnet 이 있긴하지만.. 이건 좀

 

하여간 소스 들어갑니다.

 

Server.cpp

#include <winsock2.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

 

#define MAXLINE 512

#define MAX_SOCK 64

char *escapechar = "exit";

int maxfdp1;

int num_chat = 0;                                                                               // 채팅참가자수

int client_s[MAX_SOCK];

 

// 채팅탈퇴처리

void removeClient(int)                              // i번째참가자탈퇴

{

        closesocket(client_s[i]);                   // 해당소켓닫기

        if(i !=num_chat -1)

               client_s[i] = client_s[num_chat -1]; 

        num_chat --;                                // 채팅참가자수1명줄이기

        printf("채팅참가자1명탈퇴. 현재참가자수= %d\n",num_chat);

}

 

// client_s[]내의최대소켓번호얻기(k는초기치)

int getmax(int k)

{

        int max = k;

        int r;

        for(r = 0; r < num_chat ; r++)            // 채팅참가자수만큼소켓배열탐색

               if(client_s[r] > max)

                       max = client_s[r];

        return max;

}

 

int main()

{

        char rline[MAXLINE], my_msg[MAXLINE];               // buffer 생성

        char *start = "Connetctd to chat_server\n";         // 최초연결시보내는메세지

        int i,j,n;

        SOCKET s, client_fd, clilen;

        fd_set read_fds;

        struct sockaddr_in client_addr, server_addr;        // 소켓주소구조체선언

        WSADATA wsa;                                        // 윈소켓

 

        // 윈속초기화

        if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)

               exit(1);

 

        // 초기소켓생성

        if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)

        {

               printf("Server: Can't open stream socket.");

               exit(0);

        }

        // server_addr 구조체의내용세팅

        ZeroMemory(&server_addr, sizeof(server_addr));       //초기화(0으로채운다)

        server_addr.sin_family = AF_INET;

        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        server_addr.sin_port = htons(9000);

         
        // bind()
       
if(bind(s,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0 )     
        {

               printf("Server: Can't bind local address.\n");

               exit(0);

        }

 

        //클라이언트로부터연결요청을기다림

        listen(s,SOMAXCONN);                    // listen 접속대기(큐크기5)

        maxfdp1 = s + 1;                        // 최대소켓번호+1

        while(1)

        {

               FD_ZERO(&read_fds);              // fd_set  초기화(0으로세팅) 

               FD_SET(s,&read_fds);       // 소켓에해당하는file discripter1로세팅

               for(i=0; i<num_chat; i++)        // ""

                       FD_SET(client_s[i], &read_fds);

               maxfdp1 = getmax(s) +1;         // maxfdp1 재계산

 

               if(select(maxfdp1,&read_fds, (fd_set  *)0, (fd_set *) 0, (struct timeval *) 0) < 0)     // select setting

               {

                       printf("select error =< 0 \n");

                       exit(0);

               }

               if(FD_ISSET(s, &read_fds))  // read_fds s에해당하는비트가세팅되있다면

               {              

                       int addrlen;        // 의미없음ㅡㅡ.. 단순주소길이저장변수

                       addrlen = sizeof(client_addr);

                      

                       // == 연결요청이있다면

                       clilen = sizeof(client_addr);

                       client_fd = accept(s, (struct sockaddr *)&client_addr, &addrlen);                 
                       
//  accept()

                       if(client_fd == -1)

                       {

                              printf("accept error \n");

                              exit(0);

                       }

 

                       // 채팅클라이언트목록에추가

                       client_s[num_chat] = client_fd;

                       num_chat++;                       // 채팅참가자수1 증가

                   
    // "Connetctd to chat_server" 를접속한클라이언트에보냄                    
                       
send(client_fd,start,strlen(start),0);     
                   
   printf("%d번째사용자추가,\n",num_chat);

               }

 

               //클라이언트가보낸메시지를모든클라이언트에게방송

               for(i = 0; i<num_chat; i++)              // 모든클라이언트검색

               {

                       memset(rline,'\0',MAXLINE);      // buffer 초기화

                       if(FD_ISSET(client_s[i],&read_fds)) 
                     
// read
해당소켓에서read 할것이있는지확인

                       {

                              if((n = recv(client_s[i],rline,MAXLINE, 0 )) <= 0)

                              {

                                      removeClient(i);

                                      continue;

                              }

                              // 종료문자처리

                              if(strstr(rline,escapechar) != NULL)
                              //"exit"
가입력되면종료시키기

                              {

                                      removeClient(i);

                                      continue;

                              }

                              // 모든채팅참가자에게메시지방송

                              rline[n] = '\0';

                              for(j = 0; j < num_chat; j++)

                                      send(client_s[j],rline,n,0);

                              printf("%s\n",rline);

                       }

               }

        }

        closesocket(s);                                                                                                                                                        // closesocket()

        WSACleanup();                                                                                                                                                  // 윈속종료

        return 0;

}

 

 

아래는 클라이언트 소스입니다.

Client.cpp

/***********************************************

        스레드를사용한채팅클라이언트

   원래루트는recv 전용이고

   스레드를생성해서사용자입력대기하다가,

   입력받게되면send 해주는클라이언트임.

***********************************************/

 

#include <winsock2.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

 

#define MAXLINE     512

 

DWORD WINAPI ProcessInputSend(LPVOID arg);

 

char *escapechar = "exit";

char name[10]; // 채팅에서사용할이름

char line[MAXLINE], message[MAXLINE+1];

struct    sockaddr_in   server_addr;

SOCKET s;      // 서버와연결된소켓번호

 

int main()

{

        WSADATA wsa;                          // 윈속

 

        // 채팅참가자이름구조체초기화

        printf("채팅ID 입력: ");

        scanf("%s",name);

       

        // 윈속초기화

        if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)

               exit(1);

 

        // 소켓생성

        if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)

        {

               printf("Client : Can't open stream socket.\n");

               exit(0);

        }

 

        // 채팅서버의소켓주소구조체server_addr 초기화

        ZeroMemory(&server_addr, sizeof(server_addr));

        server_addr.sin_family = AF_INET;

        server_addr.sin_port = htons(9000);

        server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

       

        // 연결요청

        if(connect(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)

        {

               printf("Client : Can't connect to server.\n");

               exit(0);

        }

        else

        {

               printf("서버에접속되었습니다. \n");

        }

 

        // 스레드생성

        HANDLE hThread;

        DWORD ThreadId;

        hThread = CreateThread(NULL, 0, ProcessInputSend, 0, 0, &ThreadId);

        if(hThread == NULL)

               printf("[오류] 스레드생성실패!\n");

        else

               CloseHandle(hThread);

 

        while(1)

        {

               ZeroMemory(message, sizeof(message));

               int size;

               if((size = recv(s, message, MAXLINE, 0)) == SOCKET_ERROR)

               {

                       printf("recv()");

                       exit(0);

               }

               else

               {

                       message[size] = '\0';

                       printf("%s \n", message);

               }

        }

        closesocket(s);                       // closesocket()

        WSACleanup();                 // 윈속종료

}

 

 

// 사용자입력부분

DWORD WINAPI ProcessInputSend(LPVOID arg)

{

        while(true)

        {

               if(fgets(message, MAXLINE, stdin))

               {

                       sprintf(line, "[%s] %s", name, message);

                       if (send(s, line, strlen(line), 0) < 0)

                              printf("Error : Write error on socket.\n");

                       if (strstr(message, escapechar) != NULL )

                       {

                              printf("Good bye.\n");

                              closesocket(s);

                              exit(0);

                       }

               }

        }

        return 0;

}

 

아래의 클래스를 사용해서 뻘짓을 하는 쓰레드 서버보단 이쪽이 훨씬 직관적이지 않습니까?

>_<..

여튼 이것도 좀 여러군데 손을 봐야 하긴 하지만..

이걸로 간단한 채팅이 되는 소스입니다.

 

클라이언트에서 쓰레드를 나눈이유는
recv처리와 send 처리를 동시에 하기 위해서 랍니다. 쓰레드를 나눔으로서
윈도우 프로그램으로 보면, 언제던지 server로부터 메시지를 받을수 있는 recv 프로세서와
언제던지 사용자가 키보드 입력을 받을수 있는 send 프로세서가 동시에 실행이 되는셈이죠

자세한 설명은.. 네트워크 프로그래밍 책의 대부분 뒷부분에 있는 select 설명을 보시는게 낳으실껍니다 >_<;;

+ Recent posts