[Socket] 네트워크 프로그래밍과 소켓의 이해

네트워크 프로그래밍이란, 네트워크로 연결된 둘 이상의 컴퓨터 사이에서의 데이터 송수신 프로그램의 작성을 말한다. 

소켓(Socket)은 네트워크(인터넷)의 연결 도구이며 OS에 의해 제공되는 소프트웨어적인 장치이다. 또한, 소켓은 프로그래머에게 데이터 송수신에 대한 물리적, 소프트웨어적으로 자세한 내용을 신경 쓰지 않게 한다. 


소켓 프로그래밍에서 사용되는 함수들을 서버와 클라이언트 측으로 나누어 간단하게 살펴보자.

Server

socket

TCP 소켓 전화기에 비유될 수 있다. 소켓은 socket함수의 호출을 통해 생성한다.

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//성공 시 파일 디스크립터, 실패 시 -1 return

bind

전화기에 전화번호가 부여되듯이 소켓에도 주소 정보가 할당된다. 소켓의 주소 정보는IP와 PORT번호로 구성 된다.

#include <sys/socket.h>
int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
//성공 시 0, 실패 시 -1 return

listen

연결 요청이 가능한 상태의 소켓으로, 걸려오는 전화를 받을 수 있는 상태에 비유할 수 있다. 연결 요청을 하는 소켓(client)에서는 필요하지 않다. 연결 요청을 받는 소켓(server)에서 필요한 상태이다.

#include <sys/socket.h>
int listen(int sockfd, int backlog); // backlog = 동시에 처리 가능한 client 수
//성공 시 0, 실패 시 -1 return

accept

걸려오는 전화에 대한 수락의 의미로 수화기를 드는 것에 비유할 수 있다. 연결요청이 수락되어야 데이터의 송수신이 가능하다. 수락된 이후에 데이터의 송수신은 양방향으로 가능하다.

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);
//성공 시 파일 디스크립터, 실패 시 -1 return

accept함수 호출 이후에 데이터의 송수신이 가능하다. 단, 연결요청이 있을 경우에만 accept함수가 반환을 한다.

 

연결 요청을 허용하는 프로그램을 일반적으로 Server라 한다. 서버는 연결을 요청하는 클라이언트보다 먼저 실행되어야 한다.

Server에서 연결요청을 수락하는 소켓의 생성 과정

  1. 소켓의 생성(socket)
  2. IP & PORT# 할당(bind)
  3. 연결요청 가능상태로 변경(listen)
  4. 연결 요청에 대한 수락(accept)

Client

connect

연결을 요청하는 소켓의 구현으로, 전화를 거는 상황에 비유할 수 있다.

#include <sys/socket.h>
int connect(int sockfd, struct sockaddr* serv_addr, socklen_t addrlen);
//성공 시 0, 실패 시 -1 return
Socket을 생성하는 순간에는 Server socket과 Client socket으로 나누어지지 않는다. 
bind, listen 함수의 호출이 이어지면 Server socket이 되고, connect함수가 호출되면 Client socket이 된다.

 


리눅스 기반의 파일 조작

‼️리눅스는 소켓도 파일로 간주하기 때문에 저 수준 파일 입출력 함수를 기반으로 소켓 기반의 데이터 송수신이 가능하다.

 

File descriptor(파일 디스크립터)

 

  • OS가 만든 파일(and socket)을 구분하기 위한 일종의 숫자
  • 저수준 파일 입출력 함수는 입출력을 목적으로 fd를 요구한다.
  • 저수준 파일 입출력 함수에게 소켓의 fd를 전달하면, 소켓을 대상으로 입출력을 진행한다.

 

write

파일에 데이터 쓰기

Socket을 통해서 다른 컴퓨터에 data를 전송할 경우에 사용할 수 있다.

#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t nbytes);
//성공 시 전달한 byte 수, 실패 시 -1 return
  • fd = 데이터 전송 대상을 나타내는 file descriptor
  • buf = 전송할 데이터가 저장된 버퍼의 주소 값
  • nbytes = 전송할 데이터의 바이트 수

read

파일에 저장된 데이터 읽기

#include <unistd.h>
ssize_t read(int fd, void* buf, size_t nbytes);
//성공 시 수신한 바이트 수(단, 파일의 끝을 만나면 0), 실패 시 -1 return
  • fd = 데이터 수신 대상을 나타내는 file descriptor
  • buf =  수신한 데이터를 저장할 버퍼의 주소 값
  • nbytes = 수신할 최대 바이트 수