I'm FanJae.
[Socket] 소켓에 대한 기본적인 내용 본문
1. 소켓(Socket)
소켓은 프로그램이 네트워크를 통해 데이터를 주고받기 위해 사용하는 통신 인터페이스이다.
운영체제가 제공하는 소켓 API를 이용하면 다른 컴퓨터 또는 프로세스와 데이터를 송수신할 수 있다.
즉, TCP/IP의 세부 구현을 직접 다루지 않아도 된다.
1) TCP 통신과 UDP 통신 방식 차이
소켓 통신은 대표적으로 TCP 방식과 UDP 방식으로 나뉜다.
TCP는 연결 지향 방식으로 데이터를 주고받기 전에 먼저 연결을 수립하며, 데이터의 순서와 신뢰성을 보장한다.
UDP는 비연결 방식으로, 연결 과정 없이 데이터를 전송한다. 속도는 빠르지만 데이터의 도착 여부나 순서를 보장하지 않는다.
※ 여기서는 TCP를 기준으로 소켓 생성 및 통신 흐름을 살펴본다.
2) 리눅스와 윈도우에서의 소켓 관점 차이
소켓의 기본 개념과 TCP 통신 흐름은 리눅스와 윈도우에서 거의 동일하다.
둘 다 소켓을 이용해 네트워크 통신을 수행한다.
socket, bind, listen, accept, connect, send, recv, close와 같은 흐름도 유사하다.
- 다만, 리눅스에서는 소켓도 파일처럼 다루는 경향이 강하다.
- 소켓을 생성하면 소켓도 파일처럼 다루는 경향이 강하여, 소켓을 생성하면 파일 디스크립터가 반환된다.
- 일반 파일처럼, read, write, close 계열 함수와 함께 사용이 가능하다.
- 반면, 윈도우에서는 Winsock API를 통해 소켓을 다룬다.
- 윈도우에서 소켓은 별도의 SOCKET 타입 핸들로 취급된다.
- 따라서 사용 전에 WSAStartup으로 Winsock 초기화가 필요하다.
3) TCP 서버 흐름
- 리눅스 소켓 관련 함수에 대응하는 윈도우 소켓 함수는 큰 차이가 없다.
- 이는 윈도우 소켓(윈속)이 상당부분 BSD 계열 유닉 소켓을 참고하여 설계되었기 때문이다.
① 소켓 생성 (socket)
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);
- socket() 함수는 네트워크 통신에 사용할 소켓을 생성한다.
- 리눅스는 파일 디스크립터를 반환하고, 윈도우는 SOCKET 타입을 핸들을 반환한다.
- 이 값은 이후 소켓을 조작하거나 데이터 송수신을 수행할 때 사용한다.
② 소켓의 주소 정보 할당 (bind)
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
#include <sys/socket.h>
int bind(SOCKET s, const struct sockaddr *name, int namelen);
- bind() 함수는 생성한 소켓에 IP 주소와 포트 번호를 할당한다.
- 서버는 특정 IP와 포트에서 클라이언트의 연결 요청을 받아야 한다.
- bind()를 통해 이 소켓은 이 주소와 포트를 사용한다는 정보를 운영체제에 등록한다.
③ 연결 요청 대기 상태로 전환 (listen)
#include <sys/socket.h>
int listen(int sockfd, int backlog);
#include <winsock2.h>
int listen(SOCKET s, int backlog);
- listen() 함수는 bind()가 완료된 서버 소켓을 클라이언트의 연결 요청을 받을 수 있는 상태로 전환한다.
- 이때 서버 소켓은 직접 데이터를 주고 받는 것이 아니다.
- 클라이언트의 연결 요청을 대기열에 쌓고, accept()가 이를 꺼내 처리할 수 있게 도와준다.
④ 연결 요청 수락 (accept)
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#include <winsock2.h>
SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen);
- accept() 함수는 클라이언트의 연결 요청을 수락하고, 해당 클라이언트와 통신할 새로운 소켓을 반환한다.
- 기존 서버 소켓은 계속 연결 요청을 받는 용도로 남아있다.
- 실제 데이터의 송수신은 accept()가 반환한 통신용 소켓으로 수행한다.
※ 서버의 흐름 정리
1단계 : 소켓 생성 (socket() 호출)
2단계 : IP주소와 PORT번호 할당 (bind() 호출)
3단계 : 연결요청 가능상태로 변경 (listen() 호출)
4단계 : 연결요청에 대한 수락 (accept() 호출)
4) 클라이언트의 흐름
① 소켓 생성 (socket)
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);
- 앞서 보았던 socket 생성을 진행한다.
② 서버 접속 요청 (connect)
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
#include <winsock2.h>
int connect(SOCKET s, const struct sockaddr *name, int namelen);
- connect() 함수는 클라이언트가 서버의 IP 주소와 포트 번호로 연결을 요청할 때 사용한다.
- TCP에서는 connect() 과정에서 서버와 연결 수립이 이뤄진다.
- 연결이 성공하면 이후 send()와 recv()를 통해 데이터를 주고 받을 수 있다.
5) 서버 / 클라이언트 공통 흐름
① 데이터 송수신 (send, recv)
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
#include <winsock2.h>
int send(SOCKET s, const char *buf, int len, int flags);
int recv(SOCKET s, char *buf, int len, int flags);
- send()와 recv() 함수는 연결된 TCP 소켓을 통해 데이터를 송수신할 때 사용한다.
- 핵심은 TCP는 바이트 스트림 방식으로, 한 번의 send()가 한 번의 recv()와 정확히 대응된다고 보장되지 않는다.
- 따라서, 수신한 데이터의 크기와 메시지 경계를 직접 관리해야 한다.
- 다만, 리눅스에서는 소켓이 파일 디스크립터이므로, read(), write()도 사용할 수 있다.
② 소켓 종료 (close)
#include <unistd.h>
int close(int fd);
#include <winsock2.h>
int closesocket(SOCKET s);
- 통신이 끝나면 소켓을 종료하여 운영체제에 할당된 자원을 반환한다.
- 리눅스에서는 close()를 사용하고, 윈도우에서는 closesocket()을 사용한다.
- 서버에서는 통신용 소켓을 종료해도 서버 소켓은 계속 유지할 수 있다.
- 단, 서버 소켓을 종료하면 더 이상 새로운 연결 요청을 받을 수 없다.
6) 윈도우에서 필요한 추가적인 작업
① Winsock 라이브러리 초기화
- 윈도우에서는 소켓을 사용하기 전에 Winsock 라이브러리를 초기화 해야한다.
- 프로그램 시작 시 WSAStartup()을 호출해야 한다.
- 소켓 사용이 끝나면 WSACleanup()을 호출해 Winsock 사용을 종료한다.
② ws2_32.lib 링크 작업
- 윈도우 소켓 사용 시 ws2_32.lib에 대한 링크를 진행해야 한다.
'Network > Network Programming' 카테고리의 다른 글
| [Socket] Byte order에 따른 소켓 통신 시 유의할 점 (0) | 2025.06.03 |
|---|