Network IPC: Sockets

hyeony·2025년 1월 31일

시스템프로그래밍

목록 보기
3/4

1. IPC(Inter Process Communication)

프로세스 간 통신(IPC) 은 서로 다른 프로세스가 데이터를 주고받는 방법을 의미한다.

pipes, FIFOs, message queues, semaphores, shared memory같은 machine 내에서 실행되는 프로세스들이 통신하는 방법을 제공한다. 예를 들어, pipe은 한 프로세스에서 다른 프로세스로 데이터를 전달하는 데 사용된다.

한편, socket은 서로 다른 machine에서 실행되는 프로세스 간 통신을 가능하게 한다. 즉, socket을 사용하면 네트워크를 통해 두 개의 프로세스가 데이터를 주고 받을 수 있다.

2. Socket

가. Socket

Socket이란 통신 endpoint의 추상화된 개념이다. 즉, 데이터를 송수신할 수 있는 접점을 의미한다.

나. Socket descriptor

파일을 다룰 때 File descriptor을 사용하듯, socket을 다룰 때 socket descriptor을 사용한다. socket descriptor도 결국 file descriptor로 구현되어 있어, read, write와 같은 함수들을 사용할 수 있다.

하지만 lseek(파일 오프셋을 변경하는 함수)은 socket에서는 지원되지 않는다. socket은 단순히 데이터 스트림을 다룰 뿐, 파일처럼 특정 위치로 이동할 수 없기 때문이다.

file descriptorsocket descriptor는 같은 테이블을 공유한다. 다음 그림에서 3과 4는 file descriptor이고, 5은 socket descriptor이다.

descriptor table은 프로세스마다 개별적으로 관리된다. 표준 입력(0), 표준 출력(1), 표준 에러(2) 이외에 file descriptor 또는 socket descriptor가 존재한다. 특정 descriptor는 file을 가리킬 수도 있고, socket을 가리킬 수도 있다.

다. socket()

socket() 함수는 socket을 생성하는 system call이며, 다음과 같은 형식으로 사용된다.

#include <sys/socket.h>

int socket(int domain, int type, int protocol);
			Returns: file (socket) descriptor if OK, -1 on error

1) domain argument

domain 인자는 주소 형식을 포함하여 통신 방식을 결정하며 다음과 같은 값을 가진다.

- AF_INET: IPv4 인터넷 도메인 (주로 사용됨). PF_INET도 사용된다.
- AF_INET6: IPv6 인터넷 도메인
- AF_UNIX: UNIX 도메인 (로컬 프로세스 간 통신)
- AF_UNSPEC: 미지정

2) type argument

type 인자는 socket의 유형을 결정하며, 통신 방식의 특징을 정의한다.

- SOCK_DGRAM: 비연결형(UDP), 신뢰성이 낮음.
- SOCK_STREAM: 연결형(TCP), 신뢰성이 높음.

3) protocol argument

protocol 인자는 domain 및 socket 유형에 따른 기본 프로토콜을 선택한다.

- TCP(Transmission Control Protocol): SOCK_STREAM in AF_INET domain
- UDP(User Datagram Protocol): SOCK_DGRAM in AF_INET domain

일반적으로 protocol 값으로 0을 사용하여 기본 프로토콜을 선택한다.

4) Example

- 코드:

#include <stdio.h> 	 
#include <sys/types.h> 	
#include <sys/stat.h> 	
#include <fcntl.h> 	
#include <sys/socket.h>	

int main() { 
	int fd1, fd2, sd1, sd2;

	fd1 = open("/etc/passwd", O_RDONLY); 
	printf("/etc/passwd's file descriptor = %d\n", fd1); 

	sd1 = socket(PF_INET, SOCK_STREAM, 0); 
	printf("stream socket descriptor = %d\n", sd1); 
    
    sd2 = socket(PF_INET, SOCK_DGRAM, 0); 
	printf("datagram socket descriptor = %d\n", sd2); 

	fd2 = open("/etc/hosts", O_RDONLY); 
	printf("/etc/hosts's file descriptor = %d\n", fd2); 

	close(fd2) ; 
	close(fd1) ; 
	close(sd2) ; 
	close(sd1) ; 
}

- 실행 결과:

라. shutdown()

shutdown() 함수는 socket의 I/O 기능을 비활성화하는 데 사용된다.

#include <sys/socket.h>

int shutdown (int sockfd, int how);
				Returns: 0 if OK, -1 on error

close() 함수와 유사하지만, shutdown() 함수는 socket의 특정 방향의 데이터 송수신을 차단할 수 있다. 한편, how 인자의 값은 다음과 같다.

- SHUT_RD: 읽기 비활성화
- SHUT_WR: 쓰기 비활성화(socket으로 데이터 전송 불가)
- SHUT_RDWR: 읽기 및 쓰기 모두 비활성화

마. Socket address

1) Overall structure

다음은 socket을 포함한 전체 구조이다.

2) Socket Address Structure

#include <sys/socket.h>

struct sockaddr {
	sa_family_t  sa_family;     	// address family
	char         sa_data[];			//variable-length address
};

socket을 이용한 통신 객체(클라이언트 또는 서버)의 주소를 표현하기 위해서는 address family, IP address, port number가 지정되어야 하며, 이 주소 정보를 socket address라고 한다.

sockaddr 구조체에 IP address, port number 등을 직접 쓰거나 읽는 것이 불편하므로, sockaddr 구조체를 사용하는 대신, 4 바이트의 IP address와 2 바이트의 port 번호를 구분하여 지정할 수 있는 인터넷 전용 소켓주소 구조체sockaddr_in을 주로 사용한다.

3) Internet Protocol address structure

#include <netinet/in.h>

struct in_addr {
	in_addr_t       s_addr;       	/* IPv4 address */
};

struct sockaddr_in {
	sa_family_t    sin_family;   	/* address family */
	in_port_t      sin_port;     	/* port number (2bytes) */
	struct in_addr sin_addr;     	/* IPv4 address (4bytes) */
	char sin_zero[8];				/* not used */
};

sin_family 값으로 AF_INET(IPv4) 또는 AF_UNIX 등을 설정한다.

4) Socket address structure

sockaddr_insockaddr 구조체의 데이터를 internet protocol에서 사용하기에 적합하도록 수정한 것이다.

3. Socket Programming

가. basic procedure

나. bind()

bind()는 socket을 특정 주소(IP, 포트)와 연결하는 역할을 수행한다.

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
					Returns: 0 if OK, -1 on error

서버는 특정 IP와 포트에서 클라이언트의 요청을 수신해야 하므로 명확한 주소를 할당해야 한다. 즉, 클라이언트가 요청을 보낼 수 있도록 well-known address에 socket을 binding해야 한다.

그런데 서버와 달리, 클라이언트는 bind()을 사용하지 않는다. 클라이언트는 일반적으로 시스템이 자동으로 IP 주소와 포트를 할당하도록 설정한다. 즉, bind() 없이 socket을 생성하고 connect()을 호출하면 시스템이 적절한 로컬 주소를 할당한다.

한편, len은 socket address의 크기이다.

다. connect()

connect()은 클라이언트가 서버에 연결을 요청하는 함수이다.

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
				Returns: 0 if OK, -1 on error

데이터 교환을 하기 전에 클라이언트의 소켓과 서버의 소켓을 연결해야 한다. addr 인자는 서버의 주소이며, sockaddr_in 구조체를 이용해 IP 주소와 포트 번호를 지정한다.

라. listen()

listen()은 서버가 클라이언트의 연결 요청을 수락할 준비가 되었음을 알리는 함수이다.

#include <sys/socket.h>

int listen(int sockfd, int backlog);
				Returns: 0 if OK, -1 on error

클라이언트가 connect()를 호출하면, 서버가 이를 수락할 준비가 되어 있어야 한다. 서버가 listen()을 호출하면, OS는 연결 요청을 보관하는 큐(queue)를 생성하여 관리한다.

마. accept()

accept()은 서버가 클라이언트의 연결 요청을 수락하는 함수이다.

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *len);
			Returns: file (socket) descriptor if OK, -1 on error

클라이언트가 connect()를 호출하면, 서버는 accept()를 통해 해당 연결을 받아들인다. accept()를 호출하면 대기열(backlog)에 있는 클라이언트 연결 요청 중 하나를 가져와 처리한다.

바. send()

#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
			Returns: number of bytes sent if OK, -1 on error

send()은 소켓을 통해 다른 프로세스(또는 네트워크의 다른 호스트)로 데이터를 전송하는 역할을 한다. 파일 I/O의 write() 함수와 유사하지만, 추가적인 전송 옵션(flags)을 지정할 수 있다. 다음은 옵션의 예이다.
- MSG_DONTROUTE
- MSG_DONTWAIT
- MSG_EOR
- MSG_OOB

사. receive()

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
	Returns: length of message in bytes, 
	0 if no messages are available and peer has done an orderly shutdown, 
	or -1 on error

recv()함수로, socket을 통해 다른 프로세스 또는 네트워크의 다른 호스트로부터 데이터를 수신한다. 파일 I/O의 read() 함수와 유사하지만, 데이터 수신 방식을 제어하는 flags 옵션을 제공한다. 다음은 옵션의 예이다.
- MSG_OOB
- MSG_PEEK
- MSG_TRUNC
- MSG_WAITALL

4. Server & Client Example

가. Server Example

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

#define PORT    5555

int main(void)
{
        char buf[256];
        struct sockaddr_in server, client;
        int sd, cd, clientlen = sizeof(client);
        
        if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        	perror("socket");
            exit(1);
        }
        
        memset((char *)&server, '\0', sizeof(server));
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = inet_addr("127.0.0.1");  // binary 형태로 바꿈
        server.sin_port = htons(PORT);

        if(bind(sd, (struct sockaddr*)&server, sizeof(server))) {
        	perror("bind");
            exit(1);
        }
        
        if(listen(sd, 5)) {
        	perror("listen");
            exit(1);
        }
        
        if ((cd = accept(sd, (struct sockaddr*)&client, &clientlen)) == -1) {
        	perror("accept");
            exit(1);
        }

        sprintf(buf, "Your IP address is %s", inet_ntoa(client.sin_addr));
        
        if(send(cd, buf, strlen(buf) + 1, 0) == -1) {
        	perror("send");
            exit(1);
        }
        close(cd);
        close(sd);

        return 0;
}

나. Client Example

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

#define PORT    5555

int main(void) 
{
        int sd;
        char buf[256];
        struct sockaddr_in server;
        
        if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
                perror("socket");
                exit(1);
        }

        memset((char *)&server, '\0', sizeof(server));
        server.sin_family       	= AF_INET;
        server.sin_addr.s_addr  	= inet_addr("127.0.0.1");
        server.sin_port         	= htons(PORT);

        if(connect(sd, (struct sockaddr*)&server, sizeof(server))) {
                perror("connect");
                exit(1);
        }
                if(recv(sd, buf, sizeof(buf), 0) == -1) {
                perror("recv");
                exit(1);
        }
        close(sd);
        printf("From Server: %s\n", buf);

        return 0;
}

다. Procedure

<참고 자료>
- 광운대학교 컴퓨터정보공학부 시스템프로그래밍 강의, 김태석(2020)

profile
Chung-Ang Univ. EEE.

0개의 댓글