소켓이란
멀리 떨어져 있는 대상이 데이터를 주고 받을때 소프트웨어 차원에서 연결을 해주는 장치가 필요한데 이러한 기능을 해주는 장치를 소켓이라 한다.
소켓을 이해하기 쉽도록 전화망을 예로 들어보겠습니다.
전화를 걸고 싶은데 무엇이 필요한가?
단연코 전화기가 필요합니다. 전화기는 멀리 떨어져 있는 두 사람이 서로 대화할 수 있도록 연결해주는 매개체입니다.
소켓이란 멀리 떨어져 있는 두 개의 호스트를 연결시켜 주는 매개체 역할을 하고, 네트워크 프로그래밍에서 소켓이 필요한 이유입니다.
윈도우즈 소켓을 위한 헤더와 라이브러리 설정하기
윈도우 소켓 초기화관련
WSAStartup()
윈속 프로그래밍을 할 때 반드시 WSAStartup 함수를 호출해 줘야 한다.
해당 함수를 호출하는 목적은 프로그램에서 요구하는 윈속의 버전을 알려줘서, 해당 버전의 윈속 사용을 위한 라이브러리 초기화 작업을 진행하기 위한 것입니다.
- wVersionRequested : 프로그램에서 요구하는 윈속의 최상위 버전을 알려주기 위해 사용됩니다.
WORD는 16비트 unsigned short를 의미하는데 상위 8비트에는 부 버전을, 하위 8비트에는 주 버전을 표시해 줍니다.
따라서 2바이트 0x0202를 인자로 넘겨주면 되는데 이 부분을 바이트 단위로 쪼개서 값을 설정하기가 번거롭기 때문에 MAKEWORD함수라는 매크로를 이용하여 WORD값을 쉽게 만들 수 있게된다.(뒤에 설명) - lpWSAData : WSADATA타입 변수의 포인터를 인자로 전달한다. 함수 호출이 끝나고 나면 WSADATA 변수에는 로딩한 DLL에 대한 정보가 채워진다. (일반적으로 많이 사용되지는 않는다고 함)
MAKEWORD()
MAKEWORD는 매크로 함수로서 원하는 WORD값을 만들어 준다.
bLow에는 하위 8비트에 채워질 데이터를 전달하고, bHight에는 상위 8비트에 채워질 데이터를 전달한다.
MAKEWORD(2, 2)를 호출하면 0x0202가 리턴하게 된다.
윈도우 소켓 기반의 소켓 관련 함수들
소켓의 생성 socket() -> [전화기 구매]
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);
소켓을 생성하는 함수이다.
주소 할당 bind() -> [전화기에 전화번호 부여]
#include <winsock2.h>
int bind(SOCKET s, const struct sockaddr FAR* name, int namelen);
소켓에 주소를 할당하는 함수입니다.
연결 요청 대기 상태로의 진입 listen() -> [전화기를 케이블에 연결]
#include <winsock2.h>
int listen(SOCKET s, int backlog);
연결 요청 대기 상태로 들어가는 함수입니다.
연결 수락 listen() -> [전화 응답]
#include <winsock2.h>
SOCKET accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
클라이언트가 연결 요청을 수락하는 함수이다.
연결 요청 connect() -> [전화 걸기]
#include <winsock2.h>
int connect(SOCKET s, const struct sockaddr FAR* name, int namelen);
클라이언트가 연결 요청을 할 때 호출하는 함수.
윈속 기반의 입 출력 함수
send() 함수
#include <winsock2.>
int send(SOCKET s, const char FAR* buf, int len, int flags)
send()함수는 데이터를 전달할 때 사용하는 함수이다.
- s : 연결된 소켓을 식별하는 설명자입니다. 즉, 소켓의 핸들을 인자로 전달
- buf : 전송할 데이터를 포함하는 버퍼에 대한 포인터입니다.
- len : buf 매개 변수가 가리키는 버퍼의 데이터의 길이(바이트)입니다.
- flags : 호출을 수행하는 방법을 지정하는 플래그 집합입니다. 이 매개 변수는 다음 값과 함께 비트 OR 연산자를 사용하여 생성됩니다.
recv() 함수
#include <winsock2.h>
int recv(SOCKET s, char FAR* buf, int len, int flags);
- s : 데이터를 수신할 영역을 나타내는 소켓의 핸들
- buf : 수신할 데이터를 저장할 버퍼의 포인터
- len : buf 매개 변수가 가리키는 버퍼의 데이터 길이(바이트), 수신할 최대 바이트
- flags 호출을 수행하는 방법을 지정하는 플래그 집합 이 매개 변수는 다음 값과 함께 비트 OR 연산자를 사용하여 생성됩니다.
Hello World 출력해보기
위의 함수들을 이용하여 Hello World 출력해보았습니다.
서적이 옛날 서적이라 일부 내용은 개편된 부분이 있어 일부 수정하였습니다.
소켓 프로그래밍 서버 코드
// 필요한 헤더 파일들
#include <stdio.h> // 표준 입력/출력 헤더
#include <stdlib.h> // 표준 라이브러리 헤더
#include <string.h> // 문자열 처리 라이브러리
#include <WinSock2.h> // 윈도우 소켓 2 헤더
#include <WS2tcpip.h> // WinSock2에 추가 기능을 제공하는 헤더
//#include <unistd.h>
#pragma comment(lib, "ws2_32.lib") // WinSock2를 위한 링커 지시문
#define PORT 4578
void ErrorHandling(const char* message);
int main()
{
WSADATA wsaData;
SOCKET hServSock;
SOCKET hClntSock;
SOCKADDR_IN servAddr;
SOCKADDR_IN clntAddr;
int szClntAddr;
const char message[] = "Hello World!\n";
// 윈도우 소켓 프로그래밍을 할 때는 반드시 WSAStartup함수를 호출해 줘야 한다고함.
// 해당 함수를 호출하는 목적은 프로그램에서 요구하는 윈도우 소켓의 버전을 알려줘서,
// 해당 버전의 윈도우 소켓 사용을 위한 라이브러리 초기화 작업을 진행한다.
// 이때 1번 인자는 프로그램에서 요구하는 윈속의 최상위 버전을 알려주기 위해 사용되는데
// WORD는 16비트 unsigned short를 의미한다.
// 상위 8비트에다가는 부 버전을, 하위 8비트에는 주 버전을 표시해주는데
// 이를 매크로화 한게 MAKEWORD이다
// MAKEWORD(2, 2)를 호출하면 0x0202가 리턴되어 들어간다.
// Load Winsock 2.2 dll
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(PORT);
// 소켓에 주소 할당 (전화기에 전화 번호를 할당하는 개념으로 생각하자)
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);
// 해당 함수 호출을 통해서 할당 받은 리소스를 해제하는 작업을 의미한다.
WSACleanup();
return 0;
}
void ErrorHandling(const char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
소켓 프로그래밍 클라이언트 코드
// 필요한 헤더 파일들
#include <stdio.h> // 표준 입력/출력 헤더
#include <stdlib.h> // 표준 라이브러리 헤더
#include <string.h> // 문자열 처리 라이브러리
#include <WinSock2.h> // 윈도우 소켓 2 헤더
#include <WS2tcpip.h> // WinSock2에 추가 기능을 제공하는 헤더
#pragma comment(lib, "ws2_32.lib") // WinSock2를 위한 링커 지시문
#define PORT 4578
#define IP "172.???.???"
void ErrorHandling(const char* message);
int main()
{
WSADATA wsaData;
SOCKET hSocket;
char message[30];
int strLen = 0;
SOCKADDR_IN servAddr;
// 윈도우 소켓 프로그래밍을 할 때는 반드시 WSAStartup함수를 호출해 줘야 한다고함.
// 해당 함수를 호출하는 목적은 프로그램에서 요구하는 윈도우 소켓의 버전을 알려줘서,
// 해당 버전의 윈도우 소켓 사용을 위한 라이브러리 초기화 작업을 진행한다.
// 이때 1번 인자는 프로그램에서 요구하는 윈속의 최상위 버전을 알려주기 위해 사용되는데
// WORD는 16비트 unsigned short 의미한다.
// 상위 8비트에다가는 부 버전을, 하위 8비트에는 주 버전을 표시해주는데
// 이를 매크로화 한게 MAKEWORD이다
// MAKEWORD(2, 2)를 호출하면 0x0202가 리턴되어 들어간다.
// Load WinSocket 2.2 DLL
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
// 서버 접속을 위한 소켓 생성
hSocket = socket(PF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET)
ErrorHandling("hSocket() error");
// 서버 주소 구조체 초기화
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
//servAddr.sin_addr.s_addr = inet_addr(IP);
// 위 주석에서 아래로 개편됨
inet_pton(AF_INET, IP, &(servAddr.sin_addr.s_addr));
servAddr.sin_port = htons(PORT);
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");
message[strLen] = 0;
printf_s("Message from server : %s \n", message);
closesocket(hSocket); // 연결 종료
// 해당 함수 호출을 통해서 할당 받은 리소스를 해제하는 작업을 의미한다.
WSACleanup();
return 0;
}
void ErrorHandling(const char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
결과
출력이 되는것을 확인해 볼 수 있다.
[해당 코드를 이용하여 확인하실 때 IP 본인 IP로 바꾸고 진행하셔야 합니다]
해당 게시글은 서적을 통한 개인 공부 목적으로 작성된 글입니다.
출처 : TCP/IP 소켓 프로그래밍 서적
'TCP_IP 소켓 프로그래밍' 카테고리의 다른 글
UDP 기반 서버/클라이언트 (0) | 2023.06.16 |
---|---|
TCP 기반 서버 / 클라이언트에 대한 이해2 (에코 서버, TCP 내부 구조) (0) | 2023.06.14 |
TCP 기반 서버 / 클라이언트에 대한 이해1 (0) | 2023.06.13 |
IP 주소 체계와 데이터 정렬의 이해 (0) | 2023.06.12 |
소켓 프로그래밍에서 프로토콜이란 (1) | 2023.06.09 |
댓글