본문 바로가기

개인 공부/네트워크

네트워크P-005(TCP Server, TCP Client )

TCP Server - Client 

  • 웹 클라이언트(브라우저)는 사용자가 입력한 주소를 접속 대기 중인 웹 서버에 접속 후 HTTP를 이용해 요청 
  • 웹 서버는 웹 클라이언트에게 요청받은 메시지를 분석 후 HTTP를 이용해 응답 메시지를 만든다

- 데이터 교환 과정 

  • 두 프로그램 간의 연결을 위해 연결 설정을 해야 한다
  • 서버 프로그램 : 연결을 위해 포트를 열어놓고 연결 요청이 오기를 기다린다
  • 클라이언트 프로그램 : 서버 포트로 연결 요청을 보낸다

TCP Server <-> Client

  1. 서버는 먼저 실행되어 클라이언트가 접속하기를 기다린다 --> listen()
  2. 클라이언트는 서버에 접속--> connect(), 데이터를 보낸다 --> send()
  3. 서버는 클라이언트 접속을 응답 --> accept(), 클라이언트 보낸 데이터를 받아서 처리 --> recv()
  4. 서버는 처리한 데이터를 클라이언트에게 보낸다 --> send()
  5. 클라이언트는 서버가 보낸 데이터를 받아서 처리 --> recv()
  6. 접속을 마치면 접속을 끊는다 --> closesocket() 

Tcp Server Function


※Socket

SOCKET socket(
	int af,		// 주소 체계
	int type,	// 소켓 타입
	int protocol	// 프로토콜
)

// 성공 : 새로운 소켓
// 실패 : INVALD_SOCKET

bind() 함수

- 소켓의 지역 IP주소와 지역 포트를 결정하는 함수

/* ========== Windows ========== */
#include <winsock2.h>
int bind(
	SOCKET sock,
	const struct sockaddr * addr,
	int addrlen
);

// 성공 : 0
// 실패 : SOCKET_ERROR
/* ========== Linux ========== */
#include <sys/types.h>
#include <sys/socket.h>

int bind(
	int sock,
	const struct sockaddr * addr,
	socklen_t addrlen
);

// 성공 : 0
// 실패 : -1
  • sock : 클라이언트 접속을 수용할 목적으로 만든 소켓, 지역 IP주소와 지역 포트번호가 아직 결정되지 않은 상태
  • addr : IP주소와 PORT번호를 지정한 sockaddr 구조체
  • addrlen : 주소 정보를 담은 변수의 길이

listen() 함수

- 소켓의 TCP 포트 상태를 LISTENING으로 변경

/* ========== Windows ========== */
#include <winsock2.h>
int listen(
	SOCKET sock,
	int backlog
)

// 성공 : 0
// 실패 : SOCKET_ERROR
/* ========== Windows ========== */
#include <sys/types.h>
#include <sys/socket.h>

int listen(
	int sock,
	int backlog
);

// 성공 : 0
// 실패 : -1
  • sock : 클라이언트 접소을 수용할 목적으로 만든 소켓, bind() 함수로 지역 IP와 포트번호가 설정된 상태
  • backlog : 서버가 당장 처리하지 않더라도 접속 가능한 클라이언트 개수 
    •  클라이언트의 접었고 정보는 연결 큐에 저장
    •  Backlog는 연결 큐의 길이를 나타냄 -> SOMAXCONN(최댓값)으로 대입

accept() 함수

- 접속한 클라이언트와 통신할 수 있도록 새로운 소켓을 생성해서 리턴

- 접속한 클라이언트의 IP주소와 포트 번호를 알려줌

/* ========== Windows ========== */
#include <winsock2.h>
SOCKET accept(
	SOCKET sock,
	struct sockaddr * addr,
	int *addrlen
);

// 성공 : 새로운 소켓
// 실패 : INVALID_SOCKET
/* ========== Linux ========== */
#include <sys/types.h>
#include <sys/socket.h>
int accept(
	int sock,
	struct sockaddr *addr,
	socklen_t *addrlen
);

// 성공 : 새롱누 소켓
// 실패 : -1
  • addr : 소켓 주소 구조체를 전달하면 접속한 클라이언트의 주소 정보(IP주소와 포트번호)로 채워진다
  • adrlen : 정수형 변수를 addr이 가리키는 소켓 주소
    • 구조체 크기로 초기화한 후 전달
    • accept() 함수가 리턴하면 *addrlen 변수는 accept() 함수가 채워 넣은 주소 정보의 크기(바이트 단위)

connect() 함수

- TCP 프로토콜 수준에서 서버와 논리적 연결을 설정

/* ========== Windows ========== */
#include <winsock2.h>
int connect(
	SOCKET sock,
	const struct sockaddr * addr,	
	int addrlen
);

// 성공 : 0
// 실패 : SOCKET_ERROR
/* ========== Linux ========== */
#include <sys/types.h>
#include <sys/socket.h>

int connect(
	int sock,
	const struct sockaddr *addr;
	socklen_t addrlen
);

// 성공 : 0
// 실패 : -1
  • sock : 서버와 통신할 목적으로 만든 소켓
  • addr : 소켓 주소 구조체를 서버 주소로 최기화 하여 전달(원격 IP 주소, 원격 포트번호)
  • addrlen : 소켓 주소 구조체의 길이(바이트 단위)

데이터 전송 함수

  • 소켓 버퍼 : 송신 버퍼와 수신 버퍼가 있다
    • 송신 버퍼 : 데이터를 전송하기 전에 임시 저장
    • 수신 버퍼 : 데이터를 수신하기 전에 임시 저장
  • 입(수신) 출력(송신) 버퍼 특성
    • 송·수신버퍼는 TCP 소켓 각각에 대해 별도로 존재한다
    • 송·수신버퍼는 소켓 생성 시 자동으로 생성된다
    • 소켓을 닫아도 출력 버퍼에 남아있는 데이터는 계속 전송이 이루어진다
    • 소켓을 닫으면 입력 버퍼에 있는 데이터는 소멸이 되어버린다

send() 함수

- 응용 프로그램 데이터를 운영체제의 송신 버퍼에 복사함으로써 데이터를 전송

- 데이터 복사가 성공하면 그 크기만큼 반환

/* ========== Windows ========== */
#include<winsock2.h>
int send(
	SOCKET sock,
	const char *buf,
	int len,
	int flags
)

// 성공 : 보낸 바이트 크기
// 실패 : SOCKET_ERROR
/* ========== Linux ========== */
#include<sys/types.h>
#include<sys/socket.h>

ssize_t send(
	int sock,
	const void * buf,
	size_t len,
	int flags
)

// 성공 : 보낸 바이트 크기
// 실패 : -1
  • sock : 통신할 대상과 연결된 소켓
  • buf : 보낼 데이터를 담고 있는 응용 프로그램 버퍼 주소
  • len : 보낼 데이터의 크기
  • flags : send() 함수의 동작을 바꿈 -> 대부분 0을 사용

Send() 함수에서 소켓 특성에 따른 리턴 종류

  • 블로킹 소켓
    • 블로킹 소켓을 대상으로 send() 함수를 호출하면 송신 버퍼의 여유공간이 send() 함수의 len 보다 작을 경우 해당 프로세스는 대기 상태
    • 송신 버퍼에 충분한 공간이 생기면 프로세스를 깨어나고 len 크기만큼 데이터 복사가 일어난 후 send 함수가 리턴 -> len
  • 넌 블로킹 소켓
    • 소켓 함수 호출 시 조건이 만족하지 않더라도 함수가 리턴되므로 스레드 중단 없이 다음 코드를 실행
    • send() 함수 호출 하면 송신 버퍼의 여유공간만큼 데이터를 복사한 후 실제 복사한 바이트 수를 리턴 -> 1, len

recv() 함수

- 운영체제의 수신 버퍼에 도착한 데이터를 응용 프로그램 버퍼에 복사

- 소켓 내부의 수신 버퍼를 성공하면 그 크기를 반환함

/* ========== Windows ========== */
int recv(
	SOCKET sock,
	char *buf,
	int len,    
	int flags
)

// 성공 : 받은 바이트 크기 또는 0
// 실패 : SOCKET_ERROR
/* ========== Linux ========== */
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(
	int sock,
	void *buf,
	size_t len,
	int flags
)

// 성공 : 받은 바이트 크기 또는 0
// 실패 : -1
  • 성공적인 리턴
    • 수신 버퍼에 데이터가 도달할 경우 : 버퍼에 복산한 후 실제 복사한 바이트 크기를 리턴
    • 접속이 정상 종료한 경우 : 리턴 값이 0이면 정상 종료
  • MSG_PEEK : 수신 버퍼의 데이터를 응용프로그램 버퍼에 복사한 후 해당 데이터를 수신 버퍼에서 삭제
  • MSG_WAITIALL : 수신 버퍼에 도착한 데이터를 최대 len 크기만큼 응용 프로그램 버퍼에 복사
    • 수신 버퍼에 도착한 데이터 크기가 len보다 작더라도 도착할 때까지 기다리지 않고 현재 있는 만큼만 복사해서 리턴

TCP Server & Client 구현

#include "Common.h"
#define SERVERPORT 9000
#define BUFSIZE 512

int main() {

	WSADATA wsa;
	int retval;

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

	// ===== 소켓 생성 =====
	SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);	// IPv4, TCP 통신 소켓
	if (listenSocket == INVALID_SOCKET)	printf("Socket ERROR!!!\n");

	// ===== bind() =====	
	struct sockaddr_in serveraddr;	//IPv4 소켓 주소 구조체
	memset(&serveraddr, 0, sizeof(serveraddr));		// 주소 초기화
	serveraddr.sin_family = AF_INET;	// IPv4
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);	// INADRR_ANY : 모든 IP에서 접속 가능!!
	serveraddr.sin_port = htons(SERVERPORT);	// h -> n Port Number 설정

	retval = bind(listenSocket, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
	if (retval == SOCKET_ERROR)	printf("BIND() ERROR!!\n");
	
// -- LISTEN() -- 대기 상태 변경, 클라이언트 갯수
	retval = listen(listenSocket, SOMAXCONN);
	if (retval == SOCKET_ERROR)	printf("LISTEN() ERROR!!!\n");
	printf("Server Listen...\n");

	// 통신에 사용할 변수 선언
	SOCKET clientSocket;			// 클라이언트 소켓
	struct sockaddr_in clientaddr;	// 클라이언트 소켓 주소(원격 IP / 원격 PORT)
	int addrlen;						// 주소 길이
	char buf[BUFSIZE + 1];			// 받은 데이터를 저장할 응용프로그램 버퍼

	while (1) {
		// ===== accept() =====
		addrlen = sizeof(clientaddr);
		clientSocket = accept(listenSocket, (struct sockaddr*)&clientaddr, &addrlen);
		if (clientSocket == INVALID_SOCKET) {
			printf("ACCEPT() ERROR!!\n");
			break;
		}

		char addr[INET_ADDRSTRLEN];
		inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));	//  inet_ntop : 네트워크 바이트 정렬
		printf("TCP Connect : IP = %s, PORT = %d\n", addr, ntohs(clientaddr.sin_port));
		// inet_ntop 네트워크 바이트 정렬(숫자) -< 문자열로
		// inet_btop(주소체계, IP주소, 저장공간, 크기)

		while (1) {
			// ===== recv() =====
			retval = recv(clientSocket, buf, BUFSIZE, 0);	// recv(원격소켓, 버퍼, 버퍼크기, 0);
			// 수신 버퍼에 있는 값을 읽어 와서 응용프로그램 버퍼에 복사
			if (retval == SOCKET_ERROR) {	// 오류가 있을 경우
				printf("RECV() ERROR!!!\n");
				break;
			}
			else if (retval == 0)	break; // 정상 종료
			
			buf[retval] = '\0';	// 받은 마지막 데이터 표시
			printf("[TCP : %s : %d] %s\n", addr, ntohs(clientaddr.sin_port), buf);
			// ===== send() =====
			retval = send(clientSocket, buf, retval, 0);
			if (retval == SOCKET_ERROR) {
				printf("SEND() ERROR!!!\n");
				break;
			}
		}
		// 클라이언트 소켓 닫기
		closesocket(clientSocket);
		printf("Client 종료 IP = %s, Port Number = %d\n", addr, ntohs(clientaddr.sin_port));
	}
	// Listen Socket 닫기
	closesocket(listenSocket);

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

TCPServer.cpp

#include "Common.h"
#define SERVERPORT 9000
#define BUFSIZE 512

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

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

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

	// 소켓 생성
	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == INVALID_SOCKET)	printf("SOCKET ERORR!!!\n");

	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 = ntohs(SERVERPORT);

	// ===== connect() =====
	retval = connect(sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
	if (retval == SOCKET_ERROR)	printf("CONNECT() ERORR!!!\n");

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

	while (1) {
		// 데이터 입력
		printf("\n[보낼 데이터] ");
		if (fgets(buf, BUFSIZE + 1, stdin) == NULL)	// fgets() 함수로 사용자로 부터 문자열을 입력 받고 검사한다
			break;
		len = (int)strlen(buf);
		if (buf[len - 1] == '\n')	buf[len - 1] = '\0';	// 입력을 했을 경우 \n을 지우고 \0으로 끝났다는 것을 표시
		if (strlen(buf) == 0)	break;	// 사용자가 입력 하지 않을 경우

		// ===== send() =====
		retval = send(sock, buf, (int)strlen(buf), 0);
		if (retval == SOCKET_ERROR) {
			printf("SEND() ERROR!!!\n");
			break;
		}
		printf("[TCP Client] %dByte를 보냈습니다\n", retval);

		// ===== recv() =====
		retval = recv(sock, buf, retval, MSG_WAITALL);
		if (retval == SOCKET_ERROR) {
			printf("RECV() ERROR!!!\n");
			break;
		}
		else if (retval == 0) break;

		buf[retval] = '\0';
		printf("[TCP Client] %dByte를 받았습니다\n", retval);
		printf("[recv Data] %s\n", buf);

	}
	closesocket(sock);
	WSACleanup();


	return 0;
}

TCPClient.cpp

서버가 클라이언트를 기다림 -> listen()
Established(Server, Client)
데이터 송수신
연결 종료

  • 공통
    • TCP / IPv4 통신(SOCK_STREMA, AF_INET)
    • 윈속 초기화, 소켓 생성
    • 통신 후 소켓 닫기, 윈속 종료 
  • Server 
    • 서버 포트 : 9000
    • bind(), listen(), accept() 함수 구현
    • recv() 함수 구현 
  • Client
    • 서버 IP 주소 : 127.0.0.1 (localhost)
    • 서버 포트 : 9000
    • send() 구현

 

[아이콘 저작권]

디지털 아이콘 제작자: Muhammad_Usman - Flaticon

Ui 아이콘 제작자: Ilham Fitrotul Hayat - Flaticon

지원 아이콘 제작자: Freepik - Flaticon

스마트 폰 아이콘 제작자: Freepik - Flaticon

섬기는 사람 아이콘 제작자: Those Icons - Flaticon