소켓 프로그래밍
목차
OSI 7 계층 #
컴퓨터 시스템에서 OSI 7 계층은 네트워킹 시스템을 설명하는 데 사용할 수 있습니다. TCP/IP 통신 프로토콜은 전송 계층에 있으며 HTTP 프로토콜은 가장 상위의 응용 계층에 있습니다.
OSI 계층이 제공되더라도 네트워크 프로그램 개발은 여전히 복잡합니다. 소켓 프로그래밍은 TCP/IP 프로토콜에 대한 깊은 이해 없이도 프로그램을 개발할 수 있게 해줍니다. 소켓은 OSI 7 계층의 전송 계층에 위치합니다. (또한 사용자가 네트워크 계층을 제어할 수 있는 Raw Socket도 있습니다)
네트워크 및 프로세스 #
소켓은 동일하거나 다른 기계의 두 개의 다른 프로세스 간의 통신을 허용합니다. 일반적으로 인터넷은 한 컴퓨터를 다른 컴퓨터에 연결하는 것처럼 보입니다. 그러나 개발자에게는 인터넷이 동일하거나 다른 기계의 프로세스 간 연결임을 인식해야 합니다. 예를 들어, Chrome(클라이언트 측)에서 Apache Server(서버 측)로의 연결처럼 말이죠.
여러 인터넷 서비스 모델이 있지만, 이 과정에서는 서버-클라이언트 모델에 중점을 둘 것입니다.
- 서버-클라이언트 모델
- P2P (Peer-to-Peer) 모델
- 에이전트/매니저 모델
멀티 프로세스를 사용하면 한 컴퓨터에서 여러 네트워크 서비스를 사용할 수 있습니다. 각 서비스는 다른 포트 번호를 사용하여 구분됩니다. 모든 서비스는 자체 포트 번호를 가지고 있습니다. 1부터 1023까지는 이미 명시적으로 선언된 포트 번호가 있습니다.
클라이언트와 서버는 포트를 통해 연결됩니다. 원하는 프로그램에 연결하려면 포트를 지정해야 합니다. 서버의 포트는 대부분 고정되어 있지만 클라이언트의 포트는 운영 체제에 의해 무작위로 선택됩니다.
네트워크 프로그래밍 #
네트워크에서 프로세스 간 데이터를 전송하거나 수신하려면 소켓을 사용해야 합니다. 아래 사진은 소켓 프로그래밍이 어떻게 작동하는지에 대한 간략한 단계와 방법을 보여줍니다.
소켓은 BSD(버클리 소켓) 소켓으로도 불립니다. 이는 1989년 UC 버클리에서 처음 발견되었으며 여전히 사용되고 있습니다. 거의 모든 운영 체제가 BSD 소켓을 사용하기 때문에 네트워크 프로그램은 운영 체제에 관계없이 널리 사용될 수 있습니다.
소켓 프로그래밍의 단계 #
아래 목록은 소켓 프로그래밍을 구현하기 위한 기본 단계와 방법을 나타냅니다.
- socket 함수 : 소켓 생성
bind
함수 : 소켓의 속성 정의listen
함수 : 클라이언트 요청을 수신할 준비 (스트림 소켓에 적용)- 시스템 간 연결
connect
함수 : 클라이언트에서 연결 시도accept
함수 : 서버에서 연결 수락
- read & write 함수 : 데이터 통신
- close 함수 : 소켓 닫기
socket #
int socket(int domain, int type, int protocol);
Windows: winsock2.h
Linux: sys/socket.h
BSD 소켓은 헤더 파일에 위치합니다.
소켓은 연결하고 듣기의 두 가지 목적으로 사용됩니다. 클라이언트의 경우 connected socket, 서버의 경우 listen socket이라고 합니다.
Parameters:
-
domain
- AF_UNIX → used in IPC(Inter-Process Communication)
- AF_INET → used in Internet Communication
- AF_INET6 → same as AF_INET using IPv6
- AF_X25 → used in Radio Protocol
-
type
- SOCK_STREAM → connection oriented TCP/IP based communication
- SOCK_DGRAM → Datagram UDP/IP based communication
- SOCK_RAW → using Raw Socket mentioned above
-
protocol
- IPPROTO_TCP → TCP protocol used with AF_INET and SOCKET_STREAM
- IPPROTO_UDP → UDP protocol used with AF_INET and SOCKET_DGRAM
-
return
Data type: int
Return value-1
→ Fail- returns sockfd (listening socket descriptor, integer)
bind #
int bind(SOCKET sockfd, const struct sockaddr *my_addr, socklen_t namelen)
// sockfd:
struct sockaddr {
// AF_INET
unsigned short sa_family;
char sa_data[14];
}
struct sockaddr_in {
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
}
struct sockaddr_un {
unsigned short sin_family;
char sun_path[108];
}
// Example of bind function
int bind_lsock(int lsock, int port) {
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
return bind(lsock, (struct sockaddr*)&sin, sizeof(sin));
}
listen #
int listen(int sockfd, int backlog);
클라이언트의 새 요청이 진행 중인 요청이 끝나기 전에 수신된 경우 이전 요청이 끝날 때까지 기다리기 위해 대기열이 있어야 합니다. listen 함수는 이 대기열을 만드는 데 사용됩니다.
Parameters:
- sockfd
- backlog → the size of queue
- return
Data type: int
Return value-1
→ Fail0
→ Success
accept #
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// Example
while(1) {
client_addr_size = sizeof(client_addr);
client_sockfd = accept(sockfd, (struct sockaddr *) &client_sockaddr,
&client_addr_size);
if (client_sockfd == -1) {
//error
}
}
accept 함수는 listen 함수에 의해 생성된 대기열에서 첫 번째 요청을 가져옵니다.
또한 connect 함수는 accept 함수와 거의 동일합니다. 그러나 connect 함수는 클라이언트용이며 새 소켓을 생성하지 않습니다.
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
Parameters:
- sockfd
- addr → If succeeded, struct with connected client’s address and port information
- addrlen → size of struct sockaddr
- return
Data type: int
Return value-1
→ Fail- returns sockfd (But different with return value of socket function) listen socket은 요청을 받고 대기열에 넣는 것만을 목적으로 합니다. 그러나 요청을 받아 통신하는 과정은 다른 클라이언트로부터 또 다른 요청을 받을 수 있도록 구분되어야 합니다. (대개 멀티 프로세스나 멀티 스레드를 사용)
read & write #
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
데이터를 통신하기 위해 read 및 write 함수가 사용됩니다. read 및 write 함수에는 두 가지 유형이 있습니다. 하나는 OS에서 제공하는 기본 함수이며, 다른 하나는 소켓에서 제공됩니다.
리눅스에서는 소켓을 포함한 모든 것이 파일로 처리됩니다. 따라서 read 및 write 시스템 함수도 소켓에 사용됩니다.
Parameters:
- sockfd client-side → socketfd created by socket function server-side → socketfd created by accept function
- buf → If succeeded, struct with connected client’s address and port information
- count → size of struct sockaddr
- return
Data type: int
Return value-1
→ Fail- returns the byte size of data
close #
#include <unistd.h>
int close(int sockfd);
남아 있는 소켓은 시스템 자원을 계속 점유하게 됩니다. 따라서 이 함수를 사용하여 소켓을 닫아야 합니다.
return
Data type: int
Return value
-1
→ Fail0
→ Success
Reference #
- https://www.cs.dartmouth.edu/~campbell/cs50/socketprogramming.html
- https://www.ibm.com/docs/en/zos/2.4.0?topic=functions-listen-prepare-server-incoming-client-requests
- https://www.ibm.com/docs/en/ztpf/1.1.0.14?topic=functions-close-shut-down-socket
- https://helloworld-88.tistory.com/215
- https://codingfarm.tistory.com/534?category=812608