ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [소켓프로그래밍] 4. 고정길이, 가변길이 데이터 TCP 통신 / 데이터 전송 후 종료
    윈도우 소켓 프로그래밍 2024. 8. 24. 17:51

    이 게시글은 'TCP/IP 윈도우 소켓 프로그래밍 (김선우 저, 한빛아카데미)'를 공부한 내용을 기반으로 작성됨


    • TCP Server-client의 기본 구조는 직전 게시글에서 다룬 구조로 정해져 있다.
    • 따라서 직전 게시글의 코드 구조를 한 번 이해하고 나면 이를 마스터 코드로 두고 그대로 재사용 할 수 있다.
    • 그렇다면, 마스터 코드의 어느 부분을 바꾸어야 또 다른 응용 프로그램을 만들 수 있는 것일까?
    • 바로 '데이터 처리' 부분이다. 어떤 데이터를 어떤 형식으로 주고 받으며 처리할지를 결정하는 것이 바로 네트워크 응용 프로그램 개발자의 역할이다.

     

     

    이번 게시글에서는 직전 게시글의 마스터 코드를 기반으로 고정길이 데이터 전송가변길이 데이터 전송을 수행해보도록 한다.

    고정길이 데이터 전송은 Server와 Client가 모두 같은 크기의 Buffer을 정의해두고 데이터를 주고 받으며 수행된다. 소스 코드를 살펴보자.

     

     

    고정길이 데이터 전송 Server

    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #pragma comment(lib, "ws2_32")
    #include <winsock2.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    #define SERVERPORT 9000
    #define BUFSIZE 50		// 중요
    
    
    // 소켓 함수 오류 출력 후 종료
    void err_quit(const char* msg)
    {
    	LPVOID lpMsgBuf;
    	FormatMessageA(
    		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    		NULL, WSAGetLastError(),
    		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    		(LPSTR)&lpMsgBuf, 0, NULL);
    	MessageBoxA(NULL, (LPCSTR)lpMsgBuf, msg, MB_ICONERROR);
    	LocalFree(lpMsgBuf);
    	exit(1);
    }
    
    
    // 소켓 함수 오류 출력
    void err_display(const 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, (char *)lpMsgBuf);
    	LocalFree(lpMsgBuf);
    }
    
    
    // 사용자 정의 데이터 수신 함수
    int recvn(SOCKET s, char* buf, int len, int flags)
    {
    	int received;
    	char* ptr = buf;
    	int left = len;
    
    	while (left > 0) {
    		received = recv(s, ptr, left, flags);
    		if (received == SOCKET_ERROR)
    			return SOCKET_ERROR;
    		else if (received == 0)
    			break;
    		left -= received;
    		ptr += received;
    	}
    
    	return (len - left);
    }
    
    
    
    
    
    int main(int argc, char* argv[])
    {
    	int retval;
    
    
    	// 윈속 초기화
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    		return 1;
    
    	// 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_addr.s_addr = htonl(INADDR_ANY);
    	serveraddr.sin_port = htons(SERVERPORT);
    	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()");
    
    	// 데이터 통신에 사용할 변수
    	SOCKET client_sock;
    	SOCKADDR_IN clientaddr;
    	int addrlen;
    	char buf[BUFSIZE + 1];
    
    
    	while (1) {
    		// accept()
    		addrlen = sizeof(clientaddr);
    		client_sock = accept(listen_sock, (SOCKADDR *)&clientaddr, &addrlen);
    		if (client_sock == INVALID_SOCKET) {
    			err_display("accept()");
    			break;
    		}
    
    		// 접속한 클라이언트 정보 출력
    		printf("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    
    		//클라이언트와 데이터 통신
    		while (1) {
    			// 데이터 받기
    			retval = recvn(client_sock, buf, BUFSIZE, 0);
    			if (retval == SOCKET_ERROR) {
    				err_display("recv() 여기임");
    				break;
    			}
    
    			else if (retval == 0)
    				break;
    
    			// 받은 데이터 출력
    			buf[retval] = '\0';
    			printf("[TCP/%s:%d] %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buf);
    
    
    		}
    
    		// closesocket()
    		closesocket(client_sock);
    		printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    
    	}
    
    	// closesocket()
    	closesocket(listen_sock);
    
    	// 윈속 종료
    	WSACleanup();
    	return 0;
    
    }

     

     

    • Server 소스 코드에서 중요한 점은 바로 윈속에서 제공되는 recv()를 사용하는 것이 아닌, recvn()이라는 함수를 새롭게 정의하여 사용하였다는 점이다.
    • recvn()을 이용하여 데이터를 항상 BUFSIZE(50)만큼만 읽도록 한다.

     

     

     

     

    고정길이 데이터 전송 Client

    #define _CRT_SECURE_NO_WARNINGS
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #pragma comment(lib, "ws2_32")
    #include <winsock2.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    #define SERVERIP "127.0.0.1"
    #define SERVERPORT 9000
    #define BUFSIZE 50
    
    
    // 소켓 함수 오류 출력 후 종료
    void err_quit(const char* msg)
    {
    	LPVOID lpMsgBuf;
    	FormatMessageA(
    		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    		NULL, WSAGetLastError(),
    		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    		(LPSTR)&lpMsgBuf, 0, NULL);
    	MessageBoxA(NULL, (LPCSTR)lpMsgBuf, msg, MB_ICONERROR);
    	LocalFree(lpMsgBuf);
    	exit(1);
    }
    
    
    // 소켓 함수 오류 출력
    void err_display(const 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, (char*)lpMsgBuf);
    	LocalFree(lpMsgBuf);
    }
    
    
    // 사용자 정의 데이터 수신 함수
    int recvn(SOCKET s, char* buf, int len, int flags)
    {
    	int received;
    	char* ptr = buf;
    	int left = len;
    
    	while (left > 0) {
    		received = recv(s, ptr, left, flags);
    		if (received == SOCKET_ERROR)
    			return SOCKET_ERROR;
    		else if (received == 0)
    			break;
    		left -= received;
    		ptr += received;
    	}
    
    	return (len - left);
    
    }
    
    
    
    
    int main(int argc, char* argv[])
    {
    	int retval;
    
    
    	// 윈속 초기화
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    		return 1;
    
    	// socket()
    	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    	if (sock == INVALID_SOCKET) err_quit("socket()");
    
    
    	// connet()
    	SOCKADDR_IN serveraddr;
    	ZeroMemory(&serveraddr, sizeof(serveraddr));
    	serveraddr.sin_family = AF_INET;
    	serveraddr.sin_addr.s_addr = inet_addr(SERVERIP);
    	serveraddr.sin_port = htons(SERVERPORT);
    	retval = connect(sock, (SOCKADDR*)&serveraddr, sizeof(serveraddr));
    	if (retval == SOCKET_ERROR) err_quit("connect()");
    
    
    	// 데이터 통신에 사용할 변수
    	char buf[BUFSIZE];
    	const char* testdata[] = { "hihi", "hellooo", "Good morning!", "Nighty night" };
    	int len;
    
    
    	// 서버와 데이터 통신
    	for (int i = 0; i < 4; i++) {
    		// 데이터 입력
    		memset(buf, '#', sizeof(buf));
    		strncpy(buf, testdata[i], strlen(testdata[i]));
    
    
    		// 데이터 보내기
    		retval = send(sock, buf, BUFSIZE, 0);
    		if (retval == SOCKET_ERROR) {
    			err_display("send()");
    			break;
    		}
    		printf("[TCP 클라이언트] %d바이트를 보냈습니다. \n", retval);
    
    	
    
    	}
    
    	// closesocket()
    	closesocket(sock);
    
    
    	// 윈속 종료
    	WSACleanup();
    	return 0;
    
    }

     

     

    • 고정 길이 데이터 전송이 잘 수행되고 있는지 확인하기 위해 buffer의 남은 공간을 '#' 문자로 채운다.
    • 문자열 길이와 상관 없이 데이터를 항상 BUFSIZE(50)만큼 전송한다.

     

     

     

     

    실행 결과는 위와 같다. TCP Client는 문자열 길이와 상관 없이 항상 한 번에 50bytes를 전송한다.

    여기서는 문자열이 50bytes 이하이면 나머지 공간을 '#' 문자로 채워넣었기 때문에 쉽게 확인할 수 있다.

    고정 길이 데이터 전송을 위해 수행한 것은 'client의 send()를 BUF만큼 전송하기',  'server에서 recnv()라는 새로운 함수를 정의하고 항상 BUFSIZE 만큼 일겅오기' 이다.

     

     

     

     


    이번에는 가변길이 데이터 전송에 대해서 살펴보도록 하자.

     

    가변길이 데이터 전송에서는 EOR(End Of Record)가 필요하다. Server에서 데이터들을 수신받을 때, EOR이 도착하면 데이터 전송이 끝났음을 알 수 있는 것이다. 책의 예제에서는 '\n'을 EOR로 정의하여 구현한다. Server의 'recvline' 함수에서 정의된다.

     

     

     

    가변길이 데이터 전송 Server

    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #pragma comment(lib, "ws2_32")
    #include <winsock2.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    #define SERVERPORT 9000
    #define BUFSIZE 512
    
    
    // 소켓 함수 오류 출력 후 종료
    void err_quit(const char* msg)
    {
    	LPVOID lpMsgBuf;
    	FormatMessageA(
    		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    		NULL, WSAGetLastError(),
    		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    		(LPSTR)&lpMsgBuf, 0, NULL);
    	MessageBoxA(NULL, (LPCSTR)lpMsgBuf, msg, MB_ICONERROR);
    	LocalFree(lpMsgBuf);
    	exit(1);
    }
    
    
    // 소켓 함수 오류 출력
    void err_display(const 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, (char *)lpMsgBuf");
    	LocalFree(lpMsgBuf);
    }
    
    
    // 내부 구현용 함수
    int _recv_ahead(SOCKET s, char* p)
    {
    	__declspec(thread) static int nbytes = 0;
    	__declspec(thread) static char buf[1024];
    	__declspec(thread) static char* ptr;
    
    	if (nbytes == 0 || nbytes == SOCKET_ERROR) {
    		nbytes = recv(s, buf, sizeof(buf), 0);
    		if (nbytes == SOCKET_ERROR) {
    			return SOCKET_ERROR;
    		}
    
    		else if (nbytes == 0)
    			return 0;
    		ptr = buf;
    	}
    	--nbytes;
    	*p = *ptr++;
    	return 1;
    
    }
    
    
    // 사용자 정의 데이터 수신 함수
    int recvline(SOCKET s, char* buf, int maxlen)
    {
    	int n, nbytes;
    	char c, * ptr = buf;
    
    	for (n = 1; n < maxlen; n++) {
    		nbytes = _recv_ahead(s, &c);
    		if (nbytes == 1) {
    			*ptr++ = c;
    			if (c == '\n')
    				break;
    		}
    		else if (nbytes == 0) {
    			*ptr = 0;
    			return n - 1;
    		}
    		else
    			return SOCKET_ERROR;
    
    	}
    	*ptr = 0;
    	return n;
    }
    
    
    
    
    int main(int argc, char* argv[])
    {
    	int retval;
    
    
    	// 윈속 초기화
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    		return 1;
    
    	// 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_addr.s_addr = htonl(INADDR_ANY);
    	serveraddr.sin_port = htons(SERVERPORT);
    	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()");
    
    	// 데이터 통신에 사용할 변수
    	SOCKET client_sock;
    	SOCKADDR_IN clientaddr;
    	int addrlen;
    	char buf[BUFSIZE + 1];
    
    
    	while (1) {
    		// accept()
    		addrlen = sizeof(clientaddr);
    		client_sock = accept(listen_sock, (SOCKADDR*)&clientaddr, &addrlen);
    		if (client_sock == INVALID_SOCKET) {
    			err_display("accept()");
    			break;
    		}
    
    		// 접속한 클라이언트 정보 출력
    		printf("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    
    		//클라이언트와 데이터 통신
    		while (1) {
    			// 데이터 받기
    			retval = recvline(client_sock, buf, BUFSIZE+1);
    			if (retval == SOCKET_ERROR) {
    				err_display("recv()");
    				break;
    			}
    
    			else if (retval == 0)
    				break;
    
    			// 받은 데이터 출력
    			buf[retval] = '\0';
    			printf("[TCP/%s:%d] %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buf);
    
    			
    		}
    
    		// closesocket()
    		closesocket(client_sock);
    		printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    
    	}
    
    	// closesocket()
    	closesocket(listen_sock);
    
    	// 윈속 종료
    	WSACleanup();
    	return 0;
    
    }

     

    • BUFSIZE를 512로 설정했다. 이 server는 최대 512bytes의 데이터를 수신할 수 있다.
    • 내부 구현용 함수 _recv_ahead는 소켓 수신 버퍼에서 데이터를 한 번에 많이 읽은 후 1byte 씩 리턴해주는 사용자 정의 함수이다.
      • 멀티스레드 환경에서도 함수가 정상 작동하도록 __declspec(thread)를 작성해 주었다. (멀티스레드는 차후에 학습할 것이므로 여기서는 일단 그렇구나 하고 넘어간다.)
    • '\n'을 EOR로 사용하기 위해 recvline이라는 함수를 정의했다. for 반복문을 통해 소켓 s에서 데이터를 1byte씩 읽어서 buf가 가리키는 메모리 영역에 저장한다. 이때 EOR인 '\n'이 등장하거나 최대길이(maxlen-1)에 도달하면 '\0'을 붙여 return하도록 하여 데이터 수신이 끝났음을 표시한다.

     

     

     

     

     

    가변길이 데이터 전송 Client

     

    #define _CRT_SECURE_NO_WARNINGS
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #pragma comment(lib, "ws2_32")
    #include <winsock2.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    #define SERVERIP "127.0.0.1"
    #define SERVERPORT 9000
    #define BUFSIZE 50
    
    
    // 소켓 함수 오류 출력 후 종료
    void err_quit(const char* msg)
    {
    	LPVOID lpMsgBuf;
    	FormatMessageA(
    		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    		NULL, WSAGetLastError(),
    		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    		(LPSTR)&lpMsgBuf, 0, NULL);
    	MessageBoxA(NULL, (LPCSTR)lpMsgBuf, msg, MB_ICONERROR);
    	LocalFree(lpMsgBuf);
    	exit(1);
    }
    
    
    // 소켓 함수 오류 출력
    void err_display(const 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, (char *)lpMsgBuf");
    	LocalFree(lpMsgBuf);
    }
    
    
    
    
    int main(int argc, char* argv[])
    {
    	int retval;
    
    
    	// 윈속 초기화
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    		return 1;
    
    	// socket()
    	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    	if (sock == INVALID_SOCKET) err_quit("socket()");
    
    
    	// connet()
    	SOCKADDR_IN serveraddr;
    	ZeroMemory(&serveraddr, sizeof(serveraddr));
    	serveraddr.sin_family = AF_INET;
    	serveraddr.sin_addr.s_addr = inet_addr(SERVERIP);
    	serveraddr.sin_port = htons(SERVERPORT);
    	retval = connect(sock, (SOCKADDR*)&serveraddr, sizeof(serveraddr));
    	if (retval == SOCKET_ERROR) err_quit("connect()");
    
    
    	// 데이터 통신에 사용할 변수
    	char buf[BUFSIZE];
    	const char* testdata[] = {"hi hello", "nice tooo meet u", "happy day", "bless you"};
    	int len;
    
    
    	// 서버와 데이터 통신
    	for (int i = 0; i < 4; i++) {
    		// 데이터 입력
    		len = strlen(testdata[i]);
    		strncpy(buf, testdata[i], len);
    		buf[len++] = '\n';
    
    		// 데이터 보내기
    		retval = send(sock, buf, len, 0);
    		if (retval == SOCKET_ERROR) {
    			err_display("send()");
    			break;
    		}
    		printf("[TCP 클라이언트] %d바이트를 보냈습니다. \n", retval);
    
    	}
    
    	// closesocket()
    	closesocket(sock);
    
    
    	// 윈속 종료
    	WSACleanup();
    	return 0;
    
    }

     

     

     

    위 소스코드를 이용하여 TCP Server-Client 통신을 수행해보면 다음과 같은 결과를 얻는다.

     

     

    매 전송마다 '\n'을 이용하여 EOR가 작동하도록 하였고, Server에서는 의도대로 한 번에 한 문장씩 수신한 것을 볼 수 있다.

     

     

     


     

    마지막으로 Client에서 Server에 데이터를 전송 할 때마다 접속 종료를 수행하는 예제를 만들어보자.

     

     

     

    데이터 전송 후 종료 Server

     

    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #pragma comment(lib, "ws2_32")
    #include <winsock2.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    #define SERVERPORT 9000
    #define BUFSIZE 1024
    
    
    // 소켓 함수 오류 출력 후 종료
    void err_quit(const char* msg)
    {
    	LPVOID lpMsgBuf;
    	FormatMessageA(
    		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    		NULL, WSAGetLastError(),
    		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    		(LPSTR)&lpMsgBuf, 0, NULL);
    	MessageBoxA(NULL, (LPCSTR)lpMsgBuf, msg, MB_ICONERROR);
    	LocalFree(lpMsgBuf);
    	exit(1);
    }
    
    
    // 소켓 함수 오류 출력
    void err_display(const 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, (char *)lpMsgBuf);
    	LocalFree(lpMsgBuf);
    }
    
    
    // 사용자 정의 데이터 수신 함수
    int recvn(SOCKET s, char* buf, int len, int flags)
    {
    	int received;
    	char* ptr = buf;
    	int left = len;
    
    	while (left > 0) {
    		received = recv(s, ptr, left, flags);
    		if (received == SOCKET_ERROR)
    			return SOCKET_ERROR;
    		else if (received == 0)
    			break;
    		left -= received;
    		ptr += received;
    	}
    
    	return (len - left);
    
    }
    
    
    int main(int argc, char* argv[])
    {
    	int retval;
    
    
    	// 윈속 초기화
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    		return 1;
    
    	// 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_addr.s_addr = htonl(INADDR_ANY);
    	serveraddr.sin_port = htons(SERVERPORT);
    	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()");
    
    	// 데이터 통신에 사용할 변수
    	SOCKET client_sock;
    	SOCKADDR_IN clientaddr;
    	int addrlen;
    	char buf[BUFSIZE + 1];
    
    
    	while (1) {
    		// accept()
    		addrlen = sizeof(clientaddr);
    		client_sock = accept(listen_sock, (SOCKADDR*)&clientaddr, &addrlen);
    		if (client_sock == INVALID_SOCKET) {
    			err_display("accept()");
    			break;
    		}
    
    		// 접속한 클라이언트 정보 출력
    		printf("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    
    		//클라이언트와 데이터 통신
    		while (1) {
    			// 데이터 받기
    			retval = recv(client_sock, buf, BUFSIZE, 0);
    			if (retval == SOCKET_ERROR) {
    				err_display("recv()");
    				break;
    			}
    
    			else if (retval == 0)
    				break;
    
    			// 받은 데이터 출력
    			buf[retval] = '\0';
    			printf("[TCP/%s:%d] %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buf);
    
    		
    		}
    
    		// closesocket()
    		closesocket(client_sock);
    		printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    
    	}
    
    	// closesocket()
    	closesocket(listen_sock);
    
    	// 윈속 종료
    	WSACleanup();
    	return 0;
    
    }

     

     

    • BUFSIZE를 1024로 설정하였다. 실제 client로부터 수신하는 데이터는 이보다 작은 가변 데이터라고 가정한다. (1024 bytes보다 더 크면 데이터 손실이 발생한다.)
    • recvn() 함수를 정의하여 Client가 전송하는 데이터를 접속이 끊길 때까지 전부 수신하도록 한다. 

     

     

     

     

     

    데이터 전송 후 종료 Client

     

    #define _CRT_SECURE_NO_WARNINGS
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #pragma comment(lib, "ws2_32")
    #include <winsock2.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    #define SERVERIP "127.0.0.1"
    #define SERVERPORT 9000
    #define BUFSIZE 50
    
    
    // 소켓 함수 오류 출력 후 종료
    void err_quit(const char* msg)
    {
    	LPVOID lpMsgBuf;
    	FormatMessageA(
    		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    		NULL, WSAGetLastError(),
    		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    		(LPSTR)&lpMsgBuf, 0, NULL);
    	MessageBoxA(NULL, (LPCSTR)lpMsgBuf, msg, MB_ICONERROR);
    	LocalFree(lpMsgBuf);
    	exit(1);
    }
    
    
    // 소켓 함수 오류 출력
    void err_display(const 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, (char*)lpMsgBuf);
    	LocalFree(lpMsgBuf);
    }
    
    
    
    
    int main(int argc, char* argv[])
    {
    	int retval;
    
    
    	// 윈속 초기화
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    		return 1;
    
    
    	// connet()
    	SOCKADDR_IN serveraddr;
    	ZeroMemory(&serveraddr, sizeof(serveraddr));
    	serveraddr.sin_family = AF_INET;
    	serveraddr.sin_addr.s_addr = inet_addr(SERVERIP);
    	serveraddr.sin_port = htons(SERVERPORT);
    
    
    	// 데이터 통신에 사용할 변수
    	char buf[BUFSIZE];
    	const char* testdata[] = { "hihi", "hellooo", "Good morning!", "Nighty night" };
    	int len;
    
    
    	// 서버와 데이터 통신
    	for (int i = 0; i < 4; i++) {
    		
    		// socket()
    		SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    		if (sock == INVALID_SOCKET) err_quit("socket()");
    
    		// connect()
    		retval = connect(sock, (SOCKADDR*)&serveraddr, sizeof(serveraddr));
    		if (retval == SOCKET_ERROR) err_quit("connect()");
    
    		// 데이터 입력
    		len = strlen(testdata[i]);
    		strncpy(buf, testdata[i], len);
    
    
    		// 데이터 보내기
    		retval = send(sock, buf, len, 0);
    		if (retval == SOCKET_ERROR) {
    			err_display("send()");
    			break;
    		}
    		printf("[TCP 클라이언트] %d바이트를 보냈습니다. \n", retval);
    
    		// closesocket()
    		closesocket(sock);
    
    
    	}
    
    
    
    	// 윈속 종료
    	WSACleanup();
    	return 0;
    
    }

     

     

    • 기존에는 반복문 밖에 socket(), closesocket()을 수행함으로써 한 번의 접속을 통해 여러 메시지를 전송하는 형태로 이루어졌었다.
    • 여기서는 for 문 내부에 SOCKET sock을 통해 client 소켓을 설정하고, 데이터 전송을 마치고 난 후에 closesocket(sock)을 수행함으로써 한 번 데이터를 전송 할 때마다 socket 연결을 재 설정하는 형태로 만들었다.

     

     

     

     

    실행 결과는 위와 같다. 한 번 데이터를 전송할 때마다 클라이언트 접속이 종료되고, 다음번 전송에서 재접속 되는 것을 볼 수 있다.

Designed by Tistory.