TCP(Transmission Control Protocol)

  • 연결 지향
  • 스트림 기반 소켓

TCP/IP 프로토콜 스택

  • 인터넷을 통한 효율적인 데이터 송수신을 목적으로 설계된 계층구조
  • 데이터 송수신 과정을 네 개의 영역으로 계층화

 

TCP 프로토콜 스택                                                   UDP 프로토콜 스택

 

LINK 계층

  • 물리적인 영역의 표준화
  • LAN, WAN, MAN과 같은 네트워크 표준과 관련된 프로토콜을 정의하는 영역

IP 계층

  • 데이터 전송 경로 설정과 관련된 계층
  • IP 자체는 비 연결지향적이며 신뢰할 수 없는 프로토콜

TCP/UDP 계층

  • 데이터의 실제 송수신 담당 > 전송 계층이라고도 함
  • TCP는 데이터 송수신 시 확인을 함으로써 신뢰성을 보장할 수 있는 프로토콜
  • UDP는 확인을 하지 않아 신뢰성 보장 X

APPLICATION 계층

  • 소켓을 기반으로 만들어지는 프로토콜

 

 

WSAStringToAdress & WSAAddressToString

  • inet_ntoa, inet_addr와 같은 기능
  • 다양한 프로토콜에 적용 가능
  • 윈도우에 종속적인 코드가 만들어져 타 운영체제로의 이식성 감소

 

WSAStringToAdress

#include <winsock2.h>
INT WSAStringToAddress(
	LPTSTR AddressString, INT AddressFamily, LPWSAPROTOCOL_INFO lpProtocolInfo,
    LPSOCKADDR lpAddress, LPINT lpAddressLength
);	// 성공 시 0, 실패 시 SOCKER_ERROR 리턴
  • AddressString: IP와 PORT번호를 담고 있는 문자열의 주소 값 전달
  • AddressFamily: 첫 번째 인자로 전달된 주소정보가 속하는 주소체계 정보 전달
  • lpProtocolInfo 프로토콜 프로바이더(Provider) 설정, 일반적으로 NULL 전달
  • lpAddress 주소정보를 담을 구조체 변수의 주소 값 전달
  • lpAddressLength 네 번째 인자로 전달된 주소 값의 변수 크기를 담고 있는 변수의 주소 값 전달

 

WSAAddressToString

#include <winsock2.h>
INT WSAAddressToString(
	LPSOCKADDR lpsaAddress, DWORD dwAddressLength, LPWSAPROTOCOL_INFO lpProtocolInfo, 
    LPTSTR lpszAddressString, LPDWORD lpdwAddressStringLength
); //성공 시 0, 실패 시 SOCKET_ERROR 리턴
  • lpsaAddress: 문자열로 변환할 주소정보를 지니는 구조체 변수의 주소 값 전달
  • dwAddressLength: 첫 번째 인자로 전달된 구조체 변수의 크기 전달
  • lpProtocolInfo: 프로토콜 프로바이더(Provider) 설정, 일반적으로 NULL 전달
  • lpszAddressString: 문자열로 변환된 결과를 저장할 배열의 주소 값 전달
  • lpdwAddressStringLength: 네 번째 인자로 전달된 주소 값의 배열 크기를 담고 있는 변수의 주소 값 전달

 

문자열 정보를 네트워크 바이트 순서의 정수로 변환

  • sockaddr_in에 주소정보를 저장하기 위해 선언된 멤버는 32비트 정수형 = IP주소 정보의 변환 필요
  • 문자열로 표현된 IP주소를 32비트 정수형으로 변환해 주는 함수 존재

 

inet_addr 함수

#include <arpa/inet.h>
in_addr_t inet_addr(const char * string);
//성공 시 빅 엔디안으로 변환된 32비트 정수 값, 실패 시 INADDR_NONE 리턴
  • 32비트 정수형으로 변환하는 과정에서 네트워크 바이트 순서로 정렬도 수행

 

inet_aton 함수

#include <arpa/inet.h>
int inet_aton(const char * string, struct in_addr * addr);
성공 시 1, 실패 시 0 리턴
  • inet_addr과 기능상으로는 동일
  • 구조체 변수 in_addr을 이용

 

inet_ntoa 함수

#include <arpa/inet.h>
char * inet_ntoa(struct in_addr adr);
//성공 시 변환된 문자열의 주소 값, 실패 시 -1 리턴
  • inet_aton과 반대되는 기능을 가짐
  • 정수형 IP정보를 문자열 형태의 IP 정보로 변환

 

인터넷 주소의 초기화

struct sockaddr_in addr;
char *serv_ip = "127.0.0.1";
char *serv_port ="8080";
memset(&addr, 0, sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr(serv_ip);
addr.sin_port=htons(atoi(serv_port));
  • inet_addr 함수와 htons 함수를 통해 sockaddr_in 구조체에 IP 정보와 PORT 번호를 초기화
  • 클라이언트의 경우 IP주소를 따로 입력하지 않고 INADDR_ANY 상수를 통해 소켓이 동작하는 컴퓨터의 IP주소를
    자동 할당할 수도 있음

 

소켓에 인터넷 주소 할당

#include <sys/socket.h>
int bind(int sockfd, struct sockaddr * myaddr, socklen_t addrlen);
//성공 시 0, 실패 시 -1 리턴

 

 

 

바이트 순서와 네트워크 바이트 순서

  • CPU에 따라 바이트를 저장하는 순서에 차이가 있을 수 있음
  • 이것을 고려하지 않고 데이터 송수신 시 전송되어온 데이터의 해석순서가 달라져 문제가 발생할 수 있음
  • CPU가 데이터를 저장하는 방식은 두 가지로 나뉨
  • 빅 엔디안(Big Endian): 상위 바이트의 값을 작은 번지수에 저장
  • 리틀 엔디안(Little Endian): 상위 바이트의 값을 큰 번지수에 저장
저장될 정수 빅 엔디안 리틀 엔디안
0x12345678 0x20번지 0x21번지 0x22번지 0x23번지 0x20번지  0x21번지 0x22번지 0x23번지
0x12  0x34 0x56 0x78 0x78 0x56 0x34 0x12

 

네트워크 바이트 순서는 빅 엔디안을 기준으로 함

 

바이트 순서의 변환(Endian Conversions)

  • unsigned short htons(unsigned short);
    short형 데이터를 호스트 바이트 순서에서 네트워크 바이트 순서로 변환
  • unisgned short ntohs(unsigned short);
    short형 데이터를 네트워크 바이트 순서에서 호스트 바이트 순서로 변환
  • unsigned long htonl(unsigned long);
    long형 데이터를 호스트 바이트 순서에서 네트워크 바이트 순서로 변환
  • unsigned long ntohl(unsigned long);
    long형 데이터를 네트워크 바이트 순서에서 호스트 바이트 순서로 변환

 

 

 

주소표현을 위한 구조체

 

struct sockaddr {
	sa_family_t 	sin_family;		//주소체계(Adress Family)
    char 			sa_data[14];	//주소정보
};

sockaddr 구조체

  • 주소 표현을 위한 구조체
  • 주소체계, PORT번호, IP주소정보를 담기엔 다소 불편함

 

struct sockaddr_in {
    sa_familty_t 	sin_family;		//주소체계(Adress Family)
    uint16_t 		sin_port;		//16비트 TCP/UDP PORT번호
    struct in_addr	sin_addr;		//32비트 IP주소
    char 			sin_zero[8];	//사용되지 않음
}

sockaddr_in 구조체

  • IPv4 기반의 주소 표현을 위한 구조체
  • sockaddr의 불편함 때문에 등장
  • sin_zero는 sockaddr과 크기를 일치시키기 위해 사용
struct in_addr{
    in_addr_t	    s_addr;	//32비트 IPv4 인터넷 주소
};

in_addr 구조체

  • 32비트 IP주소정보를 담는데 사용

윈도우 운영체제의 소켓 함수

#include <winsock2.h>

SOCKET socket(int af, int type, int protocol);
  • 인자의 종류와 의미는 리눅스의 socket 함수와 동일
  • 반환형은 리눅스의 int형과 달리 SOCKET형
  • 성공 시 소켓 핸들, 실패 시 INVALID_SOCKET 반환

 

윈도우 기반 TCP 소켓 예제

#define  _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>

void ErrorHandling(char* message);

int main(int argc, char* argv[]) {
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;

	char message[30];
	int strLen = 0;
	int idx = 0, readLen = 0;

	if (argc != 3) {
		printf("Usage: %s <IP> <PORT>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() Error!");

	hSocket = socket(PF_INET, SOCK_STREAM, 0);

	if (hSocket == INVALID_SOCKET)
		ErrorHandling("socket() Error!");

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr(argv[1]);
	servAddr.sin_port = htons(atoi(argv[2]));

	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("connect() Error!");

	while (readLen = recv(hSocket, &message[idx++], 1, 0)) {
		if (readLen == -1)
			ErrorHandling("read() Error!");

		strLen += readLen;
	}

	if (strLen == -1)
		ErrorHandling("read() Error!");

	printf("Message from Server: %s \n", message);
	printf("Function read call count: %d \n", strLen);

	closesocket(hSocket);
	WSACleanup();

	return 0;
}

void ErrorHandling(char* message) {
	fputs(message, stderr);
	fputc('\n', stderr);

	exit(1);
}

 

 

프로토콜

컴퓨터 상호간의 대화에 필요한 통신규약

 

소켓의 생성

#include <sys/socket.h>

int socket(int domain, int type, int protocol);
  • domain: 소켓이 사용할 프로토콜 체계(Protocol Family) 정보 전달
  • type: 소켓의 데이터 전송방식에 대한 정보 전달
  • protocol: 두 컴퓨터간 통신에 사용되는 프로토콜 정보 전달

 

프로토콜 체계(Protocol Family)

이름 프로토콜 체계(Protocol Family)
PF_INET IPv4 인터넷 프로토콜 체계
PF_INET6 IPv6 인터넷 프로토콜 체계
PF_LOCAL 로컬 통신을 위한 UNIX 프로토콜 체계
PF_PACKET Low Level 소켓을 위한 프로토콜 체계
PF_IPX IPX 노벨 프로토콜 체계

 

소켓의 타입(Type)

소켓의 전송 방식을 의미

 

연결지향형 소켓(SOCK_STREAM)

중간에 데이터가 소멸되지 않고 목적지로 전송

전송 순서대로 데이터가 수신

전송되는 데이터의 경계(Boundary)가 존재하지 않음

 

신뢰성 있는 순차적인 바이트 기반의 연결지향 데이터 전송 방식의 소켓

 

비 연결지향형 소켓

전송된 순서에 상관없이 가장 빠른 전송을 지향

전송된 데이터는 손실 또는 파손의 우려가 있음

전송되는 데이터의 경계 존재

한번에 전송할 수 있는 데이터의 크기 제한

 

신뢰성과 순차적 데이터 전송을 보장하지 않는, 고속의 데이터 전송을 목적으로 하는 소켓

 

프로토콜의 최종선택

하나의 프로토콜 체계 안에 데이터의 전송방식이 동일한 프로토콜이 둘 이상 존재하는 경우 세 번째 인자를 통해 원하는 프로토콜 정보를 구체화

 

리눅스 기반 tcp 소켓 예제

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void error_handling(char* message);

int main(int argc, char* argv[]) {
	int sock;
	struct sockadddr_in serv_addr;
	char message[30];
	int str_len;
	int idx = 0, read_len = 0;

	if (argc != 3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	sock = socket(PF_INET, SOCK_STREAM, 0);

	if (sock == -1)
		error_handling("socket() error");

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_addr.sin_port = htons(atoi(argv[2]));

	if (connect(sock, (struct sockaddr*) & serv_addr, sizeof(serv_addr)) == -1)
		error_handling("connect() error!");

	while (read_len = read(sock, &message[idx++], 1)) {
		if (read_len == -1)
			error_handling("read() Error!");

		str_len += read_len;
	}

	printf("Message from server : %s \n", message);
	printf("Function read call count: %d \n", str_len);
	close(sock);

	return 0;
}

void error_handling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

윈도우 소켓(Windows Socket API)

윈도우 운영체제에서 네트워크 프로그래밍을 위한 API

헤더 파일 winsock2.h

ws2_32.lib 라이브러리 링크 필요

 

윈도우 소켓의 초기화

윈도우 소켓 프로그래밍을 위해선 초기화가 필요

#include <winsock2.h>

int WSAStartup(WORD wVersionRequested, LPWSADATA lpwSAData);
  • wVersionRequested: 프로그래머가 사용할 윈도우 소켓의 버전정보 전달
  • lpWSAData: WSADATA라는 구조체 변수의 주소 값 전달

 

윈도우 기반 소켓관련 함수

소켓 생성

#include <winsock2.h>

SOCKET socket(int af, int type, int protocol);

 

IP주소와 PORT번호 할당

#include <winsock2.h>

int bind(SOCKET s, const struct sockaddr * name, int namelen);

 

연결 요청 허용 가능 상태로 변경

#include <winsock2.h>

int listen(SOCKET s, int backlog);

 

연결 요청 수락

#include <winsock2.h>

SOCKET accept(SOCKET s, struct sockaddr * addr, int * addrlen);

 

클라이언트에서 연결 요청 시

#include <winsock2.h>

int connect(SOCKET s, const struct sockaddr * name, int namelen);

 

소켓 닫기

#include <winsock2.h>

int closesocket(SOCKET s);

 

파일 핸들과 소켓 핸들

리눅스의 파일 디스크립터와 마찬가지로 윈도우에서는 핸들(Handle)을 반환

윈도우는 리눅스와는 달리 파일 핸들과 소켓 핸들을 구분

 

윈도우 기반 서버 예제

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	SOCKADDR_IN servAddr, clntAddr;

	int szClntAddr;
	char message[] = "Hello World!";
	if (argc != 2)
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartUp() error!");

	hServSock = socket(PF_INET, SOCK_STREAM, 0);
	if (hServSock == INVALID_SOCKET)
		ErrorHandling("socket() error");

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAddr.sin_port = htons(atoi(argv[1]));

	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("bind() error");

	if (listen(hServSock, 5) == SOCKET_ERROR)
		ErrorHandling("listen() error");

	szClntAddr = sizeof(clntAddr);
	hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
	if (hClntSock == INVALID_SOCKET)
		ErrorHandling("accept() error");

	send(hClntSock, message, sizeof(message), 0);
	closesocket(hClntSock);
	closesocket(hServSock);
	WSACleanup();
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

 

윈도우 클라이언트 기반 예제

#define  _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>

void ErrorHandling(char* message);

int main(int argc, char* argv[]) {
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;

	char message[30];
	int strLen;

	if (argc != 3) {
		printf("Usage: %s <IP> <PORT>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() Error!");

	hSocket = socket(PF_INET, SOCK_STREAM, 0);

	if (hSocket == INVALID_SOCKET)
		ErrorHandling("socket() Error!");

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr(argv[1]);
	servAddr.sin_port = htons(atoi(argv[2]));

	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("connect() Error!");

	strLen = recv(hSocket, message, sizeof(message) - 1, 0);

	if (strLen == -1)
		ErrorHandling("read() Error!");

	printf("Message from Server: %s \n", message);

	closesocket(hSocket);
	WSACleanup();
	
	return 0;
}

void ErrorHandling(char* message) {
	fputs(message, stderr);
	fputc('\n', stderr);

	exit(1);
}

 

윈도우 기반 입출력 함수

데이터 전송

#include <winsock2.h>

int send(SOCKET s, const chat *buf, int len, int flags);

 

데이터 수신

#include <winsock2.h>

int recv(SOCKET s, const char * buf, int len, int flags);

+ Recent posts