Coding & Programming/Network(TCP, IP, UDP)

[C/C++] 윈도우 소켓(Winsock) 1:N 채팅/통신 프로그램 구현하기 #4:: TCP 클라이언트 구현(소스코드)

mainCodes 2021. 4. 19. 09:39

안녕하세요 JollyTree입니다 (•̀ᴗ•́)و

 

먼저 클라이언트 기능을 구현해 보았습니다. 클라이언트는 기본적으로 서버에 접속하여 채팅 메시지를 송·수신하는 기능을 포함하며 서버와 같이 여러 클라이언트들의 소켓을 관리할 필요가 없어 서버에 비해 비교적 소스코드가 간단합니다. 

 


클라이언트 주요 기능

 

  - 소켓 초기화 및 서버 접속
  - 소켓으로 부터 채팅 메시지와 서비스 종료 메시지를 수신하기 위한 FD_READ, FD_CLOSE 이벤트 등록
  - 소켓을 이용한 채팅 메시지 송신 및 수신  

FD_READ, FD_CLOSE 등의 네트워크 이벤트가 궁금하시면 WSAEventSelect functions(winsock2.h) MS 자료를 참고하세요.

 

main() 함수 내 코드를 위주로 설명을 하면,

먼저 프로그램의 명령행 인자로 접속할 서버 주소, 포트번호, 사용할 별명을 입력받습니다.

if (argv[1] != NULL && argv[2] != NULL && argv[3] != NULL)
{
    strcpy_s(ip_addr, argv[1]);  //서버 주소
    port_number = atoi(argv[2]); //포트 번호
    strcpy_s(nickname, argv[3]); //별명
}

정상적으로 인자가 전달되었다면 client_init() 함수에서 서버에 접속하기 위한 소켓을 초기화하고 연결(connect())을 시도합니다.

 

이후 서버로부터의 메시지 수신과 채팅 메시지 입력을 동시에 수행하기 위해 쓰레드(do_chat_service())를 생성합니다. do_chat_service() 함수는 쓰레드로 실행되면서 지속적으로 서버로부터 수신할 메시지가 있는지 이벤트를 모니터 합니다.

 

그리고 FD_READ 이벤트가 발생하면 소켓으로부터 수신한 메시지를 출력하고 서버가 종료되었음을 알리는 FD_CLOSE 이벤트가 발생하면 서버 서비스가 중단되었다는 메시지를 출력합니다.

HANDLE event = WSACreateEvent();

WSAEventSelect(s, event, FD_READ | FD_CLOSE);
while (1)
{
    index = WSAWaitForMultipleEvents(1, &event, false, INFINITE, false);

    WSAEnumNetworkEvents(s, event, &ev);
    if (ev.lNetworkEvents == FD_READ)
    {
        int len = recv(s, recv_message, MAXBYTE, 0);
        if (len > 0)
            printf("%s\n", recv_message);
    }
    else if (ev.lNetworkEvents == FD_CLOSE)
    {
        printf(" >> 서버 서비스가 중단되었습니다.(종료: \"/x\")\n");
        closesocket(s);
        break;
    }
} 


사용자가 입력하는 채팅 메시지는 main() 함수의 while 문에서 gets_s() 함수를 통해 입력되고 입력한 메시지는 send() 함수를 통해 서버로 전송됩니다. 그리고 서버는 수신받은 메시지를 각 클라이언트들에게 전달합니다.  

 

while (1)
{
    gets_s(input, MAXBYTE);
    sprintf_s(message, "[%s] %s", nickname, input);
    send(sock, message, sizeof(message), 0);
    pexit = strrchr(message, '/');
    if (pexit)
        if (strcmp(pexit, "/x") == 0)
            break;
}


클라이언트 전체 소스코드:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h> 
#include <WinSock2.h>
#include <process.h>
#include <string.h> 
 
int client_init(char* ip, int port);
unsigned int WINAPI do_chat_service(void* param);
 
int main(int argc, char* argv[])
 
{
    char ip_addr[256= "";
    int port_number = 9999;
    char nickname[50= "";
    unsigned int tid;
    int sock;
    char input[MAXBYTE] = "";
    char message[MAXBYTE] = "";
    char* pexit=NULL;
    HANDLE mainthread;
 
    if (argc < 3)
    {
        printf("\n사용법 : mcodes_client [서버주소] [포트번호] [닉네임]\n\n");
        printf("        ex) mcodes_client.exe 192.168.100.100 9999 mainCodes\n");
        exit(0);
    }
 
    if (argv[1!= NULL && argv[2!= NULL && argv[3!= NULL)
    {
        strcpy_s(ip_addr, argv[1]);  //서버 주소
        port_number = atoi(argv[2]); //포트 번호
        strcpy_s(nickname, argv[3]); //별명
    }
 
    sock = client_init(ip_addr, port_number);
    if (sock < 0)
    {
        printf("sock_init 에러\n");
        exit(0);
    }
 
    mainthread = (HANDLE)_beginthreadex(NULL0, do_chat_service, (void*)sock, 0&tid);
    if (mainthread)
    {
        while (1)
        {
            gets_s(input, MAXBYTE);
            sprintf_s(message, "[%s] %s", nickname, input);
            send(sock, message, sizeof(message), 0);
            pexit = strrchr(message, '/');
            if (pexit)
                if (strcmp(pexit, "/x"== 0)
                    break;
        }
 
        closesocket(sock);
        WSACleanup();
        CloseHandle(mainthread);
    }
 
    return 0;
 
}
 
int client_init(char* ip, int port)
{
    SOCKET server_socket;
    WSADATA wsadata;
    SOCKADDR_IN server_address = { 0 };
 
    if (WSAStartup(MAKEWORD(22), &wsadata) != 0)
    {
        printf("WSAStartup 에러\n");
        return -1;
    }
 
    if ((server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    {
        puts("socket 에러.");
        return -1;
    }
 
    memset(&server_address, 0sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr(ip);
    server_address.sin_port = htons(port);
 
    if ((connect(server_socket, (struct sockaddr*)&server_address, sizeof(server_address))) < 0)
    {
        puts("connect 에러.");
        return -1;
    }
 
    return server_socket;
}
 
unsigned int WINAPI do_chat_service(void* params)
{
    SOCKET s = (SOCKET)params;
    char recv_message[MAXBYTE];
    int len = 0;
    int index = 0;
    WSANETWORKEVENTS ev;
    HANDLE event = WSACreateEvent();
 
    WSAEventSelect(s, event, FD_READ | FD_CLOSE);
    while (1)
    {
        index = WSAWaitForMultipleEvents(1&event, false, INFINITE, false);
        if ((index != WSA_WAIT_FAILED) && (index != WSA_WAIT_TIMEOUT))
        {
            WSAEnumNetworkEvents(s, event, &ev);
            if (ev.lNetworkEvents == FD_READ)
            {
                int len = recv(s, recv_message, MAXBYTE, 0);
                if (len > 0)
                    printf("%s\n", recv_message);
            }
            else if (ev.lNetworkEvents == FD_CLOSE)
            {
                printf(" >> 서버 서비스가 중단되었습니다.(종료: \"/x\")\n");
                closesocket(s);
                break;
            }
        }
    }
    WSACleanup();
    _endthreadex(0);
    
    return 0;
}
 
 
cs


이상으로 윈속(Winsock)을 이용한 1:N 채팅 프로그램 구현에 대한 기록을 마칩니다. 이상 JollyTree였습니다 (•̀ᴗ•́)و