TCP&IP 복습

TCP/IP 소켓 프로그래밍 복습 1

Captic 2020. 10. 26. 10:33

참고문헌 - 윤성우 <윤성우의 열혈 TCP/IP 소켓 프로그래밍>

♨ 개인적 해석이 들어간 글임으로, 인지하지 못한 오류가 있을 수 있습니다 ♨

 

▣ 네트워크 프로그래밍과 소켓에 대한 간단한 이해

- 네트워크 프로그래밍 : 네트워크로 연결되어 있는 서로 다른 컴퓨터가 데이터를 주고 받는 것

-> 조건 : 물리적인 연결(인터넷) + 소프트웨어적 데이터 송수신(운영체제에서 제공하는 socket)

* Socket? : 물리적으로 연결된 네트워크상에서의 데이터 송수신에 사용할 수 있는 소프트웨어적인 장치

 

▣ 윈도우 기반으로 소켓 구현

- 필요사항

1. #include <WinSock2.h>

 

2. 추가 종속성에 'ws2_32.lib' 추가 (프로젝트 설정 -> 구성 속성 -> 링커 -> 입력 -> 추가 종속성)

 

3. winsock의 초기화

윈속 프로그래밍을 할 때에는 반드시 WSAStartup 함수를 호출해서, 프로그램에서 요구하는 윈도우 소켓의 버전을 알리고

해당 버전을 지원하는 라이브러리의 초기화 작업을 진행해야 한다

 

-> #include <WinSock2.h>

int WSAStartup(WORD wVersonRequested, LPWSADATA lpwSAData);

=> 성공 시 0, 실패 시 0이 아닌 에러코드 값

① wVersonRequested : 프로그래머가 사용할 윈속의 버전정보 전달

② lpwSAData : WSADATA라는 구조체 변수의 주소 값 전달

 

※ ① : 윈도우 소켓에는 버전이 존재한다

-> 사용할 소켓 버전정보를 WORD형(unsigned short, typedef로 정의된 것)으로 구성하여 ①로 보내야 한다

예) 소켓의 버전이 1.2라면, 1이 주 버전, 2가 부 버전 -> 0x0201을 ①로 전달

상위 8비트에 부 버전 정보 / 하위 8비트에 주 버전 정보를 표시

 

이때 MAKEWORD 매크로 함수를 이용하면 간단히 WORD형 버전정보 구성 가능

 

 

※ ② : WSADATA 구조체 변수의 주소 값 (LPWSADATA는 WSADATA의 포인터 형)

WSAStartup함수 호출이 완료된 후 해당 변수에는 초기화된 라이브러리의 정보가 채워진다

특별한 의미를 지니는 것이 아니지만, WSAStarup 함수를 호출하기 위해서는 반드시 WSADATA 구조체 변수의 주소 값을 전달해야 한다

 

- winsock 라이브러리의 해제

윈속관련 함수의 호출이 불가능해진다

더 이상 윈속관련 함수의 호출이 불필요할 때 호출하는게 국롤

-> #include <WinSock2.h>

int WSACleanup(void);

=> 성공 시 0, 실패 시 SOCKET_ERROR 반환

 

 

▣ 윈도우 기반의 소켓관련 함수

- #include <WinSock2.h>하고 시작

 

- 소켓 생성

SOCKET socket(int af, int type, int protocol);-> 성공 시 소켓 핸들, 실패 시 INVALID_SOCKET 반환

 

- 소켓 IP주소 및 PORT번호 할당

int bind(SOCKET s, const struct sockaddr* name, int namelen);-> 성공 시 소켓 핸들, 실패 시 SOCKET_ERROR 반환

 

- 소켓 연결요청 가능상태로 변경

int listen(SOCKET s, const struct sockaddr* name, int namelen);-> 성공 시 0, 실패 시 SOCKET_ERROR 반환

 

- 클라이언트의 연결요청에 대한 수락

SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);-> 성공 시 소켓 핸들, 실패 시 INVALID_SOCKET 반환

 

- 클라이언트 프로그램에서 소켓을 기반으로 연결요청

int connect(SOCKET s, const struct sockaddr* name, int namelen);-> 성공 시 0, 실패 시 SOCKET_ERROR 반환

 

- 소켓 닫기

inr closesocket(SOCKET s);-> 성공 시 0, 실패 시 SOCKET_ERROR 반환

 

----------------------------------------------------------------------------------------------------------------------------------------------------------

예) hello_server_win.c (서버 소켓)

#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("WSAStartip() 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)// 서버소켓에 IP주소, PORT번호 할당(bind 함수)
		ErrorHandling("bind() error");

	if (listen(hServSock, 5) == SOCKET_ERROR)// 위에서 생성한 서버 소켓 완성(listen 함수)
		ErrorHandling("listen() error");

	szClntAddr = sizeof(clntAddr);
	hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);// 클라이언트의 연결요청을 수락하기 위해 accept 함수 호출

	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);
}

 

 

▣ 원도우에서의 파일 핸들과 소켓 핸들

- 리눅스는 내부적으로 소켓도 파일 취급한다 -> 파일/소켓을 생성하면 파일 디스크립터가 반환된다

- 윈도우에서 시스템 함수의 호출을 통해서 파일을 생성할 때 '핸들(handle)'이라는 것을 반환

-> 윈도우 : 핸들 = 리눅스 : 파일 디스크립터

 

but! 윈도우는 파일 핸들과 소켓 핸들을 구분하고 있다

-> 파일 핸들 기반의 함수와 소켓 핸들 기반의 함수에 차이가 있다

 

- 소켓관련 함수들의 SOCKET 자료형은 정수로 표현되는 소켓의 핸들 값을 저장하기 위해서 typedef 선언으로 정의된 것

 

 

▣ 윈도우 기반 서버, 클라이언트 예제의 작성

예) hello_client_win.c (클라이언트)

#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);
}

 

 

▣ 윈도우 기반 입출력 함수

int send(SOCKET s, const char* buf, int len, int flags);
//-> 성공 시 전송된 바이트 수, 실패 시 SOCKET_ERROR 반환

① s : 데이터 전송 대상과의 연결을 의미하는 소켓의 핸들 값 전달

② buf : 전송할 데이터를 저장하고 있는 버퍼의 주소 값 전달

③ len : 전송할 바이트 수 전달

④ flags : 데이터 전송 시 적용할 다양한 옵션 정보 전달

 

- ④을 제외하고 리눅스의 write 함수와 차이가 없다

-> ④은 일단은 설정하지 않는다는 의미로 0을 전달한다

 

 

int recv(SOCKET s, const char* buf, int len, int flags);
//-> 성공 시 수신한 바이트 수(단 EOF 전송 시 0), 실패 시 SOCKET_ERROR 반환

① s : 데이터 수신 대상과의 연결을 의미하는 소켓의 핸들 값 전달

② buf : 수신된 데이터를 저장할 버퍼의 주소 값 전달

③ len : 수신할 수 있는 최대 바이트 수 전달

④ flags : 데이터 수신 시 적용할 다양한 옵션 정보 전달