코딩/Network

[네트워크] TCP 서버-클라이언트

나야, 웅이 2023. 4. 15. 15:11
728x90
반응형
SMALL

TCP 서버-클라이언트 구조

 

 

 

웹서버-클라이언트 동작

▶사용자가 주소를 입력하면 웹 브라우저(클라이언트)가 접속 대기중인 웹 서버에 접속

▶HTTP를 이용하여 요청 메시지 전송

ex) 웹 페이지 경로

▶웹 서버는 요청 메시지에 맞는 응답 메시지 전송

ex) 웹 페이지 데이터

▶웹 브라우저는 수신한 데이터를 처리하여 화면에 출력해줌

 

 

 

TCP 서버-클라이언트의 핵심 동작

1) 서버가 클라이언트가 접속하기를 기다림 listen

2) 클라이언트가 서버에 접속 connect

3) 서버가 클라이언트 접속을 수용 accept

4) 클라이언트가 서버에 데이터를 보냄 send

5) 서버가 클라이언트가 보낸 데이터를 받아서 처리 recv

6) 서버가 처리한 데이터를 클라이언트에 보냄 send

7) 클라이언트는 서버가 보낸 데이터를 받아서 처리 recv

8) 데이터를 주고 받는 과정을 모두 마치면 접속을 끓는다 closesocket 또는 close 

 

 

 

 

 

서버란?

▶클라이언트에게 네트워크를 통해 정보나 서비스를 제공하는 컴퓨터 시스템

▶네트워크 프로그램 또는 장치를 의미

▶하드웨어적 의미만 있는 것이 아니다

ex) 게임 서버 개발자 : 컴퓨터 하드웨어 개발자 x, 서버 프로그램 개발자 o

 

 

 

 

 

TCP 서버-클라이언트 동작 원리

a) 서버는 소켓 생성 후 클라이언트의 접속을 기다린다.

- 이 때 서버는 특정 포트를 사용하므로, 이 포트 번호로 접속하는 클라이언트만 수용한다.

 

b) 클라이언트가 서버에 접속한다.

-  이 때 TCP 프로토콜 수준의 연결설정을 위한 패킷 교환이 일어난다. (SYN, SYN/ACK, ACK)

 

c) TCP 프로토콜 수준의 연결설정이 끝나면, 서버는 클라이언트와 통신할 수 있는 새로운 소켓을 생성한다.

-> 기존 소켓은 다시 새로운 클라리언트 접속을 기다린다.

 

d) 두 클라이언트가 접속한 후의 상태를 나타낸다.

-> 서버에는 총 3개의 소켓이 있고, 2개는 클라이언트와 통신하는 용도로 사용한다.

 

 

 

 

하나의 TCP 서버와 여러 클라이언트의 통신

▶서버 측 소켓과 클라이언트 측 소켓이 일대일로 대응

▶하나의 클라이언트가 둘 이상의 소켓을 사용하여 서버에 접속하는 것도 가능함

 

 

 

 

TCP 3-way handshaking

양쪽 모드 데이터를 전송할 준비가 되었다는 것을 보장하는 역할


서버-클라이언트 실습

▶서버 : 클라이언트가 보낸 데이터를 받아서 이를 문자열로 간주하여 무조건 화면에 출력 후, 수신 데이터를 그대로 서버로 전송 (에코 서버)

#include "../../common.h"

#define SERVERPORT 9000
#define BUFSIZE 512

int main(int argc, char* argv[])
{
	int retval;

	//윈속 초기화
	WSADATA wsa;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsa))
		return 1;

	//socket() 소켓을 생성함으로써 사용할 프로토콜을 결정
	SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock == INVALID_SOCKET)
		err_quit("socket()");

	//bind() 지역 IP주소와 지역 포트번호를 결정 (바인딩)
	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));
	//bind()에 오류가 발생하지 않으면 0을 반환
	if (retval == SOCKET_ERROR)
		err_quit("bind()");

	//listen() TCP를 Listening 상태로 변경
	retval = listen(listen_sock, SOMAXCONN);
	if (retval == SOCKET_ERROR)
		err_quit("listen()");

	//데이터 통신에 사용할 변수
	SOCKET client_sock;
	struct sockaddr_in clientaddr;
	int addrlen;
	char buf[BUFSIZE + 1];

	while (1) {
		// accept() 자신에게 접속한 클라이언트와 통신할 수 있는 새로운 소켓을 생성
		addrlen = sizeof(clientaddr);
		client_sock = accept(listen_sock, (struct sockaddr*)&clientaddr, &addrlen);
		if (client_sock == INVALID_SOCKET) 
		{
			err_display("accept()");
			break;
		}

		// 접속한 클라이언트 정보 출력
		char addr[INET_ADDRSTRLEN];
		inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));
		printf("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n", addr, ntohs(clientaddr.sin_port));

		// 클라이언트와 데이터 통신
		while (1) 
		{
			// recv() 데이터 받기
			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", addr, ntohs(clientaddr.sin_port), buf);

			// send() 데이터 보내기
			retval = send(client_sock, buf, retval, 0);
			if (retval == SOCKET_ERROR) 
			{
				err_display("send()");
				break;
			}
		}

		// 소켓 닫기
		closesocket(client_sock);
		printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n", addr, ntohs(clientaddr.sin_port));
	}

	// 소켓 닫기
	closesocket(listen_sock);

	// 윈속 종료
	WSACleanup();
	return 0;
}

▶클라이언트 : 사용자가 키보드로 입력한 문자열을 서버로 전송하고, 서버가 데이터를 돌려보내면 이를 화면에 출력

#include "../../common.h"

char* SERVERIP = (char*)"127.0.0.1";

#define SERVERPORT 9000
#define BUFSIZE 512

int main(int argc, char* argv[])
{
	int retval;

	//명령행 인수가 있으면 IP 주소로 사용
	if (argc > 1)
		SERVERIP = argv[1];

	//원속 초기화
	WSADATA wsa;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsa))
		return 1;

	//소켓 생성
	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == INVALID_SOCKET)
		err_quit("socket()");

	//connect()
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	inet_pton(AF_INET, SERVERIP, &serveraddr.sin_addr);
	serveraddr.sin_port = htons(SERVERPORT);
	retval = connect(sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
	if (retval == SOCKET_ERROR)
		err_quit("connect()");

	//데이터 통신에 사용할 변수
	char buf[BUFSIZE + 1] = { 0, };
	int len = 0;

	//서버와 데이터 통신
	while (1) 
	{
		//데이터 입력
		printf("\n[보낼 데이터] ");
		if (fgets(buf, BUFSIZE + 1, stdin) == NULL)
			break;

		//'\n' 문자 제거
		len = (int)strlen(buf);
		if (buf[len - 1] == '\n')
			buf[len - 1] = '\0';
		
		//no data to send
		if (0 == strlen(buf))
			break;

		//데이터 보내기
		retval = send(sock, buf, (int)strlen(buf), 0);
		if (retval == SOCKET_ERROR)
		{
			err_display("send()");
			break;
		}
		// no data received
		else if (0 == retval)
			break;
		
		//받은 데이터 출력
		buf[retval] = '\0';
		printf("[TCP 클라이언트] %d 바이트를 받았습니다.\n", retval);
		printf("[받은 데이터] %s\n", buf);

		//closing condition
		retval = strncmp(buf, "99", sizeof(buf));
		if (0 == retval)
			break;
	}

	//소켓 닫기
	closesocket(sock);

	//원속종료
	WSACleanup();
	return 0;
}

 

728x90
반응형
LIST