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

[C언어/C++] Winsock 멀티스레드 1:N 대용량 파일 전송(송수신) 서버/클라이언트 구현하기(TCP/IP Windows socket) #3/3

mainCodes 2021. 4. 28. 09:13

[C언어/C++] Winsock 멀티스레드 1:N 대용량 파일 전송(송수신) 서버/클라이언트 구현하기(TCP/IP Windows socket) #3/3 - 서버 편

 

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

 

지난번 클라이언트 설명과 소스코드를 포스팅한데 이어 이번에는 1:N 파일 수신 서버 프로그램에 대한 설명과 소스코드입니다. 서버/클라이언트에 대한 전반적인 개념, 실행 절차, 주요 기능 등은 아래 링크를 참고하세요.

 

Winsock 멀티스레드 1:N 대용량 파일 전송 서버/클라이언트 구현하기 - 프로그램 주요 기능, 개념, 절차

Winsock 멀티스레드 1:N 대용량 파일 전송 클라이언트 구현하기(클라이언트 소스코드)

서버는 N개의 클라이언트로부터 파일을 수신해야 하기 때문에 1개의 파일만 전송하는 클라이언트에 비해 조금 복잡한 구조를 가지고 있습니다. 서버는 기본적으로 멀티스레드를 지원하고 네트워크 이벤트를 감지하여 N개의 클라이언트로부터 파일을 수신합니다.

서버는 do_file_service() 스레드 함수를 시작으로 서비스를 시작합니다. init_fileserver() 함수 내에서 소켓 초기화를 수행하고 클라이언트 접속(accept) 이벤트가 발생하면 fileserver_main() 스레드 함수 내 recv_file() 함수에서 접속한 클라이언트로부터 패킷 헤더(5바이트)와 패킷 데이터(n바이트)를 순차적으로 수신합니다.

typedef struct {
  char type;    
  int len;
PACKET_HEADER;

...

header_len = 5;
memset(&trans_header, 0, sizeof(PACKET_HEADER));
while (read_len = recv_socket(socket, (char*)header, header_len))
...

 

data_len = filelen;
for(;;)
{
    read_len = recv_socket(socket, (char*)data_buf, data_len);

...

}
...
save_file(file_buf, filelen);

recv_socket()는 select()과 매크로 함수인 FD_ISSET() 함수를 이용하여 소켓 상태를 체크하여 수신받을 데이터가 있다면 데이터를 수신합니다. 패킷 헤더의 패킷 길이(len)만큼 수신했다면 모두 수신한 것으로 판단하여 패킷 데이터에서 파일명을 분리하고 파일 데이터를 저장합니다.

서버 전체 소스코드(Server Source Code):
※ 소스코드는 Visual Studio 2019 환경에서 컴파일되었습니다.

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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#pragma warning(disable : 4996)
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
#include <process.h> 
#include <WinSock2.h>
 
#define        PACKET_SIZE        1024
#define        PACKET_TYPE        0x11
#define        FILE_DELIMITER    '&'
 
typedef struct {
    char type;        
    int len;
} PACKET_HEADER;
 
char* save_folder = NULL;
int server_port = 1234;
 
 
int write_file(char* filename, void* data, int data_len)
{
    FILE* fp;
    int len = 0;
 
    if ((data != NULL&& (data_len > 0))
    {
        if ((fp = fopen(filename, "wb")) == NULL) {
            puts("fopen 에러\n");
            return -1;
        }
 
        if ((fwrite(data, sizeof(char), data_len, fp)) < data_len)
        {
            puts("fwrite 에러\n");
            fclose(fp);
        }
        fclose(fp);
    }
 
    return 0;
}
 
int save_file(char* file_buf, int filelen)
{
    char* split_pos = NULL;
    char fullpath[MAX_PATH] = "", filename[MAX_PATH] = "";
    int filename_len = 0, filecontent_len = 0;
 
    split_pos = strchr(file_buf, FILE_DELIMITER);
 
    if (save_folder[strlen(save_folder) - 1== '\\')
        strcpy(fullpath, save_folder);
    else
        sprintf(fullpath, "%s\\", save_folder);
 
    strncpy(filename, file_buf, split_pos - file_buf);
    strcat(fullpath, filename);
 
    filename_len = strlen(filename);
    filecontent_len = filelen - (filename_len + sizeof(FILE_DELIMITER));
 
    if (write_file(fullpath, file_buf + strlen(filename) + sizeof(FILE_DELIMITER), filecontent_len) != 0)
    {
        puts("write_file 에러\n");
        return -1;
    }
 
    printf(" >> 수신한 파일명 : %s, 파일크기 : %d바이트\n", fullpath, filecontent_len);
 
    return 0;
}
 
 
int recv_socket(int sock, char* recv_buf, int recv_len)
{
    fd_set rset;
    struct timeval tvalue;
    int signal_flag, len;
 
    for (;;)
    {
        tvalue.tv_sec = 3600;
        tvalue.tv_usec = 0;
 
        FD_ZERO(&rset);
        FD_SET(sock, &rset);
 
        if ((signal_flag = select(0&rset, NULLNULL&tvalue)) <= 0){
            if (signal_flag == 0) {
                if (send(sock, (char*)&signal_flag, 40< 0)
                    return -1;
            }
            break;
        }
        if (FD_ISSET(sock, &rset)){
            if ((len = recv(sock, recv_buf, recv_len, 0)) > 0)
                return len;
            else break;
        }
        else break;
    }
 
    return -1;
}
 
int recv_file(int socket)
{
    PACKET_HEADER trans_header;
    char* header = (char*)&trans_header;
    char *file_buf=NULL*data_buf=NULL;
    int header_len = 0, filelen = 0;
    int read_len = 0, data_len = 0;
 
    header_len = 5;
    memset(&trans_header, 0sizeof(PACKET_HEADER));
    while (read_len = recv_socket(socket, (char*)header, header_len))
    {
        if (read_len == -1)
            return -1;
        else if (read_len == header_len)
            break;
 
        header_len -= read_len;
        header += read_len;
    }
    memcpy(&trans_header.len, &header[1], 4);
    filelen = ntohl(trans_header.len);
 
    if ( (filelen <= 0|| (trans_header.type != PACKET_TYPE) )
        return -1;
 
    if ((file_buf = (char*)calloc(1, filelen + 1)) == NULL)
        return -1;
 
    read_len = 0;
    data_buf = file_buf;
    data_len = filelen;
    for(;;)
    {
        read_len = recv_socket(socket, (char*)data_buf, data_len);
        if (read_len == -1) {
            puts("recv_socket 에러");
            return -1;
        }
        else if (read_len == data_len)
            break;
 
        data_len -= read_len;
        data_buf += read_len;
    }
 
    save_file(file_buf, filelen);
 
    free(file_buf);
    return 0;
}
 
unsigned int WINAPI fileserver_main(void* arg)
{
    int sock = (int)arg;
    int rv = 0;
 
    if(recv_file(sock)< 0)
        puts("fileserver_recv 에러");
 
    closesocket(sock);
    _endthreadex(0);
 
    return NULL;
}
 
int get_client_ip(int socket)
{
    struct sockaddr_in    sock;
    int    len;
 
    len = sizeof(sock);
    if (getpeername(socket, (struct sockaddr*& sock, &len) < 0)
        return -1;
 
    printf(" >> 연결된 클라이언트 IP 주소 : [%s]\n", inet_ntoa(sock.sin_addr));
    return 0;
}
 
int init_fileserver(int port_num)
{
    struct sockaddr_in server_addr, client_addr;
    int server_socket, client_socket;
    int len, reuseflag = TRUE;
    unsigned int thread_id;
    WSADATA wsadata;
 
    if (WSAStartup(MAKEWORD(22), &wsadata) != 0) {
        puts("WSAStartup 에러.");
        return -1;
    }
 
    if ((server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        puts("socket 에러.");
        return -1;
    }
 
    memset((char*)&server_addr, 0sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons((unsigned short)port_num);
 
    setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&reuseflag, sizeof(reuseflag));
    while (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0
    {
        puts("bind 에러");
        Sleep(120*1000);
    }
 
    if (listen(server_socket, SOMAXCONN) < 0) {
        puts("listen 에러");
        return -1;
    }
 
    for(;;)
    {
        len = sizeof(client_addr);
        if ((client_socket = accept(server_socket, (struct sockaddr*& client_addr, &len)) < 0)
            continue;
 
        HANDLE thread = (HANDLE)_beginthreadex(NULL0, fileserver_main, (void*)client_socket, 0&thread_id);
        if (thread==NULL)
        {
            puts(" >> _beginthreadex 에러\n");
            closesocket(client_socket);
        }
        printf(" >> 쓰레드 생성 완료(socket=%d, tid=%d)\n", client_socket, thread_id);
        CloseHandle(thread);
 
        get_client_ip(client_socket);
    }
 
    return 0;
}
 
unsigned int WINAPI do_file_service(void* params)
{
    init_fileserver(server_port);
 
    _endthreadex(0);
    return NULL;
}
 
int main(int argc, char* argv[])
{
    unsigned int tid;
    HANDLE mainthread;
 
    if (argc != 3)
    {
        puts("사용법 : fileserver [포트번호][파일저장경로]\n");
        puts("     ex) fileserver 1234 c:/temp\n\n");
        exit(0);
    }
 
    server_port = atoi(argv[1]);
    save_folder = argv[2];
 
    mainthread = (HANDLE)_beginthreadex(NULL0, do_file_service, (void*)00&tid);
    if (mainthread)
    {
        printf(" >> 서버 초기화 완료!!\n");
 
        WaitForSingleObject(mainthread, INFINITE);
        CloseHandle(mainthread);
    }
    return 0
}
 
cs

 

서버/클라이언트 실행화면(Output):

 

서버가 N개의 클라이언트로부터 정상적으로 파일을 수신하여 지정된 폴더에 저장하는지 테스트하였습니다. 클라이언트별로 전송하는 파일은 다음과 같습니다. 테스트를 위해 클라이언트별로 전송하는 파일의 크기를 모두 다르게 하였습니다.

 

- 클라이언트 1의 전송파일 크기 : 약 365MB

- 클라이언트 2의 전송파일 크기 : 약  28MB

- 클라이언트 3의 전송파일 크기 : 약 776KB

 

클라이언트 1은 가장 큰 파일을 전송하기 때문에 가장 먼저 파일 전송을 시작했고 그다음 클라이언트 2, 클라이언트 3의 순으로 파일을 전송하였습니다. 전송 테스트 결과 클라이언트 2, 3이 전송한 파일이 작기 때문에 먼저 수신되었고 클라이언트 1이 보낸 파일이 가장 마지막에 전송되었습니다. 결과적으로 3개의 클라이언트가 보낸 모든 파일이 정상적으로 수신되어 지정된 폴더에 저장되는 것을 확인할 수 있었습니다.


여기까지 Winsock 멀티스레드 1:N 대용량 파일 송/수신 서버/클라이언트 구현하기에 대한 연재를 마칩니다.
상 JollyTree였습니다. (•̀ᴗ•́)و