reference: "모두를 위한 리눅스 프로그래밍" / 아오키 미네로
source: https://blog.daum.net/jackie8462/24
구체적인 네트워크 통신을 위한 API. 앞서 이야기한 대로 신경 쓸 부분은 open()에 해당하는 부분.
리눅스에서는 네트워크 통신을 위해 소켓(socket)이란 것을 사용한다. 소켓이란 전구나 콘센트처럼 무언가를 연결하는 접점이라는 뜻으로 사용된다. 리눅스에서 소켓은 스트림을 연결하기 위해 사용한다.
소켓은 추상화가 많이 된 개념으로, 상당히 넓은 범위에서 응용할 수 있다. 예를 들면, 서버와 클라이언트 양측에서 사용할 수 있고, 프로토콜도 TCP와 UDP 외에 IP나, 인터넷 이외의 프로토콜에서 사용할 수 있다. 그만큼 소켓의 내부 구조는 조금 복잡하지만, IPv4나 IPv6를 하나의 API로 처리할 수 있다는 장점이 있다.
https://recipes4dev.tistory.com/153
소켓
엄밀히 따지자면, "네트워크 소켓"이라는 용어가 정확한 표현은 아닙니다. 전기 소켓이 전기를 공급받기 위해 정해진 규격(110V, 220V 등)에 맞게 만들어져야 하듯, 네트워크에 연결하기 위한 소켓 또한 정해진 규약, 즉, 통신을 위한 프로토콜(Protocol)에 맞게 만들어져야 합니다. 보통 OSI 7 Layer(Open System Interconnection 7 Layer)의 네 번째 계층인 TCP(Transport Control Protocol) 상에서 동작하는 소켓을 주로 사용하는데, 이를 "TCP 소켓" 또는 "TCP/IP 소켓"이라고 부릅니다. (UDP에서 동작하는 소켓은 "UDP 소켓"이라고 합니다.)
TCP/IP 소켓 프로그래밍
소켓(Socket)을 사용하여 네트워크 통신 기능을 구현하는 과정, 즉, 소켓 프로그래밍(Socket Programming)은 그 개념만큼 아주 단순하지만은 않습니다. 그 이유는 바로, 소켓(Socket)으로 네트워크 통신 기능을 구현하기 위해서는, 소켓을 만드는 것과, 만들어진 소켓을 통해 데이터를 주고 받는 절차에 대한 이해가 필요하고, 운영체제 및 프로그래밍 언어에 종속적으로 제공되는 소켓 API 사용법을 숙지해야 하기 때문입니다.
덤으로, 케이블 분리로 인한 네트워크 단절, 트래픽 증가에 따른 데이터 전송 지연, 시스템 리소스 관리 문제로 인한 에러 등, 네트워크 환경에서 발생할 수 있는 다양한 예외사항에 대해서도 처리가 필요하다.
두 개의 시스템(또는 프로세스)이 소켓을 통해 네트워크 연결(Connection)을 만들기 위해서는, 최초 어느 한 곳에서 그 대상이 되는 곳으로 연결을 요청한다.
IP 주소와 포트 번호로 식별되는 대상에게, 자신이 데이터 송수신을 위한 네트워크 연결을 수립할 의사가 있음을 알리는 것이다.
최초 한 곳에서 무작정 연결을 시도한다고 해서, 그 요청이 무조건 받아들여지고 연결이 만들어져 데이터를 주고 받을 수 있게 되는 것일까? 그것은 아니다. 한 곳에서 연결 요청을 보낸다 하더래도 그 대상 시스템이 그 요청을 받아들일 준비가 되어 있지 않다면, 해당 요청은 무시되고 연결은 만들어지지 않는다.
그러므로 요청을 받아들이는 곳에서는 어떤 연결 요청(일반적으로 포트 번호로 식별)을 받아들일 것인지를 미리 시스템에 등록하여, 요청이 수신되었을 때 해당 요청을 처리할 수 있도록 준비해야한다.
이렇듯 두 개의 시스템(또는 프로세스)이 소켓을 통해 데이터 통신을 위한 연결(Connection)을 만들기 위해서는, 연결 요청을 보내는지 또는 요청을 받아들이는지에 따라 소켓의 역할이 나뉘게 되는데, 전자에 사용되는 소켓을 클라이언트 소켓(Client Socket), 후자에 사용되는 소켓을 서버 소켓(Server Socket)이라 한다.
주의점
1. 마치 클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)이 태생적으로 구조가 다른, 전혀 별개의 소켓(Socket)인 것처럼 여겨질 수 있다. 하지만 두 소켓(Socket)은 동일하다. 소켓의 역할과 구현 절차 구분을 위해 다르게 부르는 것일 뿐, 전혀 다른 형태의 소켓이 아니라는 것이다. 단지 역할에 따라 처리되는 흐름, 즉, 호출되는 API 함수의 종류와 순서들이 다를 뿐이다.
2. 또한 위의 그림을 보고, 소켓 연결이 완료된 다음 클라이언트 소켓과 서버 소켓이 직접 데이터를 주고 받는다고 생각하면 안된다. 서버 소켓은 클라이언트 소켓의 연결 요청을 받아들이는 역할만 수행할 뿐, 직접적인 데이터 송수신은 서버 소켓의 연결 요청 수락의 결과로 만들어지는 "새로운 소켓(accept()함수의 반환 소켓)"을 통해 처리된다.
클라이언트 측에서 서버에 스트림을 연결시키려면 다음의 두 시스템 콜을 사용한다.
socket()은 소켓을 만드는 시스템 콜이다. 사용할 프로토콜을 지정하고, 접속할 대상에 대한 정보는 connect()에서 지정하여 스트림을 연결한다.
#include <sys/socket.h>
#include <sys/types.h>
int socket(int domain, int type, int protocol);
socket()은 소켓을 만들고 이에 대응하는 파일 디스트립터를 반환한다.
// IPv4상의 TCP라면,
socket(PF_INET, SOCK_STREAM, 0);
socket()은 실행에 성공하면 파일 디스트림터(0 이상의 정수)를 반환하고, 실패하면 -1을 반환한 후 error 값을 기재한다.
#include <sys/socket.h>
#include <sys/types.h>
int connect(int sock, const struct sockaddr *addr, scklen_t addrlen);
connect()는 소켓 sock에서 스트림을 꺼내 addr로 지정한 주소의 서버에 스트림을 연결한다.
두 번째 인자 addr은 open()에서의 경로와 유사하다. 인터넷이라면 IP주소와 포트 번호를 지정하면 된다.
여기서 주의해야 할 점이 connect()에서는 호스트 이름이 아니라 IP 주소와 번호를 지정해야 한다. 호스트명은 반드시 IP 주소와 포트 번호로 변환해야 한다(이 후 설명).
세 번째 인자 addrlen은 *addr의 크기를 지정한다. connect()는 실행에 성공하면 0을 반환하고 실패하면 -1을 반환한다.
스트림의 연결을 기다리고 있는 서버 측에 대해 알아본다면,
클라이언트보다 조금 더 복잡하여 다음 4개의 시스템 콜을 호출해야 한다.
#include <sys/socket.h>
#include <sys/types.h>
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
bind()는 접속을 기다리는 주소 addr을 소켓 sock에 할당한다. 마찬가지로 addrlen은 *addr의 크기이다.
bind()는 실행이 성공하면 0, 실패하면 -1을 반환하고 errno를 설정한다.
아주 간단하게 추상화하자면, 이 함수는 접속을 기다리는 포트 번호를 지정하는 역할을 함.
#include <sys/socket.h>
int listen(int sock, int backlog);
listen()은 소켓 sock이 서버용 소켓, 즉 접속을 기다리는 소켓임을 커널에 알린다. 두 번쨰 인자 backlog는 동시에 받아들일 수 있는 커넥션의 최대 수이다.
linsten()도 마찬가지로 성공하면 0을, 실패하면 -1을 반환한 후 errno를 설정한다.
#include <sys/socket.h>
#include <sys/types.h>
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
accecpt()는 sock에 클라이언트가 접속하는 것을 기다리다 접속이 완료되면 연결된 스트림의 파일 디스크립터를 반환한다. addr에는 클라이언트의 주소가 기재되며, addrlen에는 *addr의 크기가 적힌다.
클라이언트
클라이언 소켓은 처음 소켓을 생성(create, socket())한 다음, 서버 측에 연결(connect, connect())을 요청한다. 그리고 서버 소켓에서 연결이 받아들여지면 데이터를 송수신(send/recv, send()/recv())하고, 모든 처리가 완료되면 소켓을 닫는다(close, close()).
서버
일단 클라이언트와 마찬가지로, 첫 번째 단계는 소켓을 생성(create, socket())한다. 그리고 서버가 사용할 IP 주소와 포트 번호를 생성한 소켓에 결합(bind, bind())시킨다. 그런 다음 클라이언트로부터 연결 요청이 수신되는지 주시(listen, listen())하고, 요청이 수신되면 요청을 받아들여(accept, accept()) 데이터 통신을 위한 소켓을 생성한다.
일단 새로운 소켓을 통해 연결이 수립되면, 클라이언트와 마찬가지로 데이터를 송수신(send/recv, send()/recv())할 수 있다. 마지막으로 데이터 송수신이 완료되면 소켓을 닫는다(close, close()).
source: https://recipes4dev.tistory.com/153