티스토리 뷰

POSIX에서의 TCP Socket에 대해 정리합니다.

Socket이란?

stackoverflow.com/questions/152457/what-is-the-difference-between-a-port-and-a-socket

  • 프로세스 간 데이터 통신의 종착점으로 OS의 API다.
  • 프로세스와 Transport Layer 사이에서 네트워킹을 담당해준다.
  • 네트워크를 경유하면 Network Socket, 같은 Host 상에서 통신하면 Unix domain socket로 나뉜다.
  • UDP와 TCP를 사용한다
  • IP+Port의 조합으로 Endpoint를 구분한다.
  • 쉽게 비유하면 Socket은 전화기와 같다.
    • IP는 전화번호
    • Port는 통화하는 사람
  • Linux나 Unix를 포함한 POSIX 계열 운영체제는 소켓을 file descriptor로 애플리케이션에 노출한다
    • POSIX 계열의 운영체제에서 소켓은 파일의 한 종류
  • 모든 네트워크 통신은 socket을 통한다.

사용 케이스

  • 채팅
  • 라이브 피드
  • 멀티플레이어 게임
  • 클라이언트 상태/로깅 실시간 보여줄 때
  • low-latency realtime communications

장점/단점

장점 단점
Full-duplex(전이중) Proxing 어려움
HTTP 호환성 L7 Loadbalancing
방화벽 친화적 확장성

장점

  • Full Duplex: 가장 큰 장점으로 handshake이후에 연결이 유지된다는 것이다. polling을 안 해도 되기 때문에 서버와 클라이언트 간에 최소한의 overhead로 정보를 주고받을 수 있다
  • HTTP Compatible: upgrade 헤더가 Websocket을 HTTP 확장적이게 해준다. 이로 인해서 프록시를 세팅할 수 있게 하고 인프라가 Websocket을 이해할 수 있게 된다.
  • Firewall friendly: HTTP나 HTTPS와 같은 port를 사용하기 때문에 port를 유지하면서 보안을 유지 할 수 있다. 

단점

  • Proxing: WebSocket은 복잡하기 때문에 특히 L7 프록시를 설정할 때 매우 힘들다. 오래 살아 있는 연결을 닫을 수가 있고 오래된 프록시 서버는 이상한 헤더가 붙은 요청이 들어왔다고 판단하고 연결을 끊기도 한다.(TLS를 사용하면 프록시 내부를 확인할 수 없기 때문에 통과된다.)
  • Load Balancing: WebSocket은 timeout을 갖고 있지 않기 때문에 강제로 끄면 안 된다. 서버가 꺼지면 연결도 종료되기 때문에 database에 상태를 유지시켜서 서버가 죽었을 때 연결할 수 있도록 해야 한다.
  • Scalability: WebSocket은 상태(Stateful)가 있기 때문에 scale out 할 때 상태를 유지시키면서 확장 하기 어려움이 있다.

TCP Socket API

데이터 전송

d2.naver.com/helloworld/47667

 

  1. Appliction: 전송할 데이터 생성(User Data), write 시스템 호출로 데이터 전송, Write 시스템 콜을 호출하면 유저 영역의 데이터가 커널 메모리로 복사
  2. File: 단순한 검사만 하고 파일 구조체에 연결된 소켓 구조체를 사용해서 소켓 함수를 호출
  3. Sockets: 순서대로 전송하기 위해 send socket buffer의 뒷부분에 추가
    1. TCB(TCP Control Block): 소켓과 연결된 구조체로 TCB에는 TCP 연결 처리에 필요한 정보 포함(connection state(LISTEN, ESTABLISHED, TIME_WAIT 등), receive window, congestion window, sequence 번호, 재전송 타이머 등)
  4. TCP: 현재 TCP 상태가 데이터 전송을 허용하면 새로운 TCP segment, 즉 패킷을 생성

데이터 수신

d2.naver.com/helloworld/47667

  1. TCP: TCP checksum 등 패킷이 올바른지 검사
  2. TCB: 패킷의 <소스 IP, 소스 port, 타깃 IP, 타깃 port>를 식별자로 사용하여 패킷이 속한 연결 찾기
  3. Socket: 새로운 데이터를 받았다면, 데이터를 receive socket buffer에 추가
    1. Receive socket buffer 크기가 결국은 TCP의 receive window
  4. Application: Read 시스템 콜을 호출하면 커널 영역으로 전환되고, socket buffer에 있는 데이터를 유저 공간의 메모리로 복사해가며 복사한 데이터는 socket buffer에서 제거.

Socket Buffer(sk_buff)

people.cs.rutgers.edu/~pxk/416/notes/16-sockets.html

응용 프로그램과 장치 간의 패킷 흐름을 관리하는 핵심 구성 요소는 sk_buff 또는 socket buffer라고 하는 구조이며, 리눅스 커널의 include/linux/skbuff.h에 정의되어 있다. 소켓 버퍼는 커널이 네트워크 스택의 각 계층에서 파라미터와 패킷 데이터를 복사하는 데 시간을 낭비할 필요가 없도록 메시지와 관련된 모든 데이터(다양한 프로토콜 헤더 포함)를 저장하는 고정된 장소이다.

 

프로토콜 스택의 여러 계층을 포함하는 데이터 패킷, 상태, 제어 데이터를 포함하는 커널 데이터 구조이다. 여기에는 네트워킹 스택의 특정 계층을 가리키는 필드가 포함되어 있다.

  • transport_header: 전송 계층(TCP or UDP) 정보
  • network_header: 네트워크 계층(IP)
  • mac_header: 링크 계층(ethernet)

Packet Data는 너무 비효율적이기 때문에 프로토콜 스택의 계층 간에 복사되지 않는다. 대신에 스택 포인터가 전달된다. 딱 두 번 복사되는데 한 번은 애플리케이션 → socket buffer, 두 번째는 socket buffer → network device(혹은 반대 방향)

 

sk_buff는 이중 연결 리스트로 구성되기 때문에 한 요소를 다른 리스트로 옮기기 쉽다. 각 sk_buff는 net_device 구조체로 network deveice를 식별할 수 있다. dev_rx는 받은 패킷의 network device 네트워크를 가리킨다. dev 요소는 버퍼가 작동하는 네트워크 장치를 식별한다.

TCP Control Block

TCP 연결을 대표하는 구조체로 Linux에서는 tcp_sock을 사용한다.

file만 찾으면 TCP 연결을 처리하는데 필요한 모든 구조체(file부터 드라이버까지)를 포인터로 쉽게 찾을 수 있다.

d2.naver.com/helloworld/47667

  • 시스템 콜이 발생하면 시스템 콜을 호출한 애플리케이션이 사용하는 file descriptor에 있는 file을 찾는다
  • Socket의 경우 별도 socket 구조체가 소켓 관련 정보를 저장하고, file은 socket을 포인터로 참조
  • Socket은 다시 tcp_sock을 참조, TCP 외의 다양한 프로토콜을 지원하기 위해 tcp_sock은 sock, inet_sock 등으로 세분화
  • Send socket buffer와 receive socket buffer는 sk_buff 리스트이고, tcp_sock을 포함
  • dst_entry를 사용해서 패킷 송신에 사용해야 하는 NIC를 찾는다.
    • 매번 라우팅 하지 않게 한다.
    • MAC 주소도 쉽게 찾는다.
    • dst_entry는 IP routing 결과물, routing table의 일부
  • TCP 연결 loopup table: 수신된 패킷이 속하는 TCP 연결을 찾는 데 사용하는 Hash table로 해시 값은 패킷의 소스 IP, 타깃 IP, 소스 port, 타깃 port>를 입력 데이터로 하고, Jenkins hash 알고리즘을 사용해서 계산

Client와 Server 간 Sequence

cs.dartmouth.edu/~campbell/cs50/socketprogramming.html

socket()

#include <sys/socket.h>

int socket (int family, int type, int protocol);

통신 프로토콜(TCP based on IPv4, TCP based on IPv6, UDP)을 정하는 함수

  • faimily: protocol family (AF_INET for the IPv4 protocols)
  • protocol: type of socket (SOCK_STREAM for stream sockets and SOCK_DGRAM for datagram

성공하면 socket descriptor를 실패하면 -1을 반환한다.

connect()

#include <sys/socket.h>

int connect (int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

server와 TCP 연결하기 위해 사용되는 함수다.

sockfd는 socket함수로부터 반환된 socket destripotor다.

bind()

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

process가 IP를 지정해줄 수 있지만 보통 Kernal이 Incoming packet을 보고 지정해준다.

또한, port가 겹치면 에러를 반환한다.

listen()

#include <sys/socket.h>

int listen(int sockfd, int backlog);

backlog는 pending상태인 연결을 둘 수 있는 대기열 수다. sockfd는 연결 요청들의 queue를 관리하게 한다.

accept()

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

sockfd는 client가 호출한 connect()로 연결되면서 생긴 새로운 file descripotor다.

cliaddr와 addrlen는 client 프로토콜 주소를 반환하기 위해 사용된다.

새로운 socket descript는 기존 socket의 type과 familiy와 동일하다.

accept에 전달되는 기존의 socket descriptor는 연결과 관련이 없지만 추가적인 연결 요청을 받기 위해 남아있다.

커널은 client의 각각의 연결이 accept 될 때마다 하나의 socket을 만든다.

send()

#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
  • 쓰기 작업에 해당한다.
  • socket은 file descriptor로 표현되기 때문에 연결된 상태라면 read와 write를 할 수 있다.

receive()

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
  • 읽기 작업에 해당한다

close()

#include <unistd.h>

int close(int sockfd);

socket과 TCP 소켓을 종료한다.

 

WebSocket

Handshake

RFC 6455명 세서에 정의된 프로토콜인 웹소켓(WebSocket)을 사용하면 서버와 브라우저 간 연결을 유지한 상태로 데이터를 교환할 수 있다. HTTP 버전은 반드시 1.1. 혹은 그 이상이어야하며, 반드시 GET방식 이어야 한다.

좌)hpbn.co/websocket/ 우)javascript.info/websocket

Request

GET /chat
Host: javascript.info
Origin: https://javascript.info
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13
  • Origin: 헤더를 보고 어떤 웹사이트와 소켓통신을 할지 결정하기 때문에 Origin 헤더는 웹소켓 통신에 중요한 역할을 한다. 참고로 웹소켓 객체는 기본적으로 크로스 오리진(cross-origin) 요청을 지원하고 웹소켓 통신만을 위한 전용 헤더나 제약도 없다.
  • Connection: Upgrade – 클라이언트 측에서 프로토콜을 바꾸고 싶다는 신호
  • Upgrade: websocket – 클라이언트 측에서 요청한 프로토콜은 'websocket’이라는 걸 의미
  • Sec-WebSocket-Key – 보안을 위해 브라우저에서 생성한 키
  • Sec-WebSocket-Version – 웹소켓 프로토콜 버전 명시(표준 버전은 13)

subprotocol

WebSocket은 low-level 프로토콜로 HTTP 위에서 data frame을 통해 데이터를 전달한다. 따라서 명확하게 하기 위해 서버와 클라이언트는 application-level에서 프로토콜을 설명할 필요가 있었고 message가 어떻게 구성되어야 하는지를 설명하는 것을 subprotocol이라고 부른다.

GET /chat HTTP/1.1
...
Sec-WebSocket-Protocol: soap, wamp
  • 위와 같이 설정하면 SOAP나 WAMP(The WebSocket Application Messaging Protocol) 프로토콜을 준수하는 데이터를 전송하겠다는 것을 의미
  • 웹소켓에서 지원하는 서브 프로토콜 목록은 IANA 카탈로그에서 확인
  • 클라이언트로부터 subprotocol 요청을 받으면 그중에서 자신이 지원할 수 있는 서브 프로토콜 하나 골라야 하고. 여러 개를 지원할 수 있다면 가장 첫 번째 subprotocol을 반환.
  • 서버는 반드시 하나의 Sec-Websocket-Protocol 헤더만을 반환. 어떠한 subprotocol도 지원하고 싶지 않다면 헤더를 빼고 보내야 한다.

extension

브라우저에 의해 자동 생성되는데, 그 값엔 데이터 전송과 관련된 무언가나 웹소켓 프로토콜 기능 확장과 관련된 무언가가 여러 개 나열된다.

GET /chat HTTP/1.1
...
Sec-WebSocket-Extensions: deflate-frame
  • deflate-frame – 이 헤더는 브라우저에서 데이터 압축(deflate)을 지원한다는 것을 의미

Response

101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
  • 서버는 클라이언트 측에서 보낸 웹소켓 통신 요청을 최초로 받고 이에 동의하면, 상태 코드 101이 담긴 응답을 클라이언트에 전송
  • 브라우저는 특별한 알고리즘을 사용해 서버에서 생성한 Sec-WebSocket-Accept 값을 받아, 이 응답이 자신이 보낸 요청에 대응하는 응답인지를 확인
  • 핸드 셰이크가 끝나면 HTTP 프로토콜이 아닌 웹소켓 프로토콜을 사용해 데이터가 전송되기 시작

데이터 전송

  • Frame: 통신의 가장 작은 단위. 애플리케이션 메세지를 전달 하는 payload와 프레임 헤더를 포함한다.
  • Message: 논리적인 어플리케이션 메시지와 매핑되는 일련의 frame이다. 

웹소켓 통신은 '프레임(frame)'이라 불리는 데이터 조각을 사용한다. 서버와 클라이언트 양측 모두에서 보낼 수 있고 프레임 내 담긴 데이터 종류에 따라 아래와 같이 분류 가능하다.

  • 텍스트 프레임(text frame) – 텍스트 데이터가 담긴 프레임
  • 이진 데이터 프레임(binary data frame) – 이진 데이터가 담긴 프레임
  • 핑 또는 퐁 프레임(ping/pong frame) – 커넥션이 유지되고 있는지 확인할 때 사용하는 프레임으로 서버나 브라우저에서 자동 생성해서 보내는 프레임
  • 이 외에도 '커넥션 종료 프레임(connection close frame) 등 다양한 프레임이 있음

브라우저 환경에서 개발자는 텍스트나 이진 프레임만 다룬다.

이유는 WebSocket. send()메서드는 텍스트나 바이너리 데이터만 보낼 수 있기 때문

 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 4               5               6               7
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
 8               9               10              11
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
 12              13              14              15
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
  • RSV1-3: 사용되지 않는다. 확장 프로토콜 또는 미래를 위해 예약
  • FIN: 메시지의 frame 중 마지막 frame인지를 나타내는 값
  • opcode(4 bits): frame의 유형을 나타낸다. utf-8 text(1), binary(2), 애플리케이션 데이터를 전달하거나 control frame 예를 들어 close(8), 연결이 살아있는지 체크 하는 ping(9), pong(10)
  • Mask: 비트는 메시지가 인코딩 되어있는지의 여부
    • proxy cache poisoning attacks을 방지하기 위한 방법 
    • client에서 보낼 때 항상 1이어야 한다. server에서 보낼 때는 항상 0
  • Payload len: represented as a variable-length field
  • Masking key: payload를 mask 하는 데 사용되는 값

Performance Checklist

  • 신뢰할 수 있는 배포를 위해 TLS를 통한 보안 WebSocket(WSS)을 사용합니다.
  • polyfull 성능에 대한 관심
  • application protocol결정에 subprotocol를 고려하기
  • 전송 크기를 줄이기 위해 binary payload 최적화
  • 전송 크기를 줄이기 위해 UTF-8 콘텐츠를 압축하는 것 고려하기
  • binary paload를 위해 올바른 binary type 설정하기
  • 클라이언트에서 버퍼링 된 데이터의 양을 모니터링
  • HOL(Head-of-line) blocking문제를 피하기 위해 메시지를 분할하기

Q&A

모든 네트워크 애플리케이션은 socket을 사용하나요?

Internet 앱은 그렇다. 소켓이 주로 TCP나 UDP와 같은 IP 프로토콜에 접근하는 방법이기 때문

Networking 앱은 OS와 사용되는 네트워킹 프로토콜에 따라 다를 수 있다. TCP/UDP 이외의 프로토콜은 로컬 네트워크(IPX, NetBIOS, SMB 등)에서 사용할 수 있으며 Socket API를 사용하지 않는다.

일반적으로 Berkeley socket은 사실상 표준이다. 고수준 라이브러리(HTTP, libcurl, libonion etc) 도 일반적으로 socket위에서 만들어진다.

두 개의 프로세스가 하나의 소켓을 공유할 수 있나요?

두 개의 소켓 및 연결이 한 엔드포인트에서 동일한 포트를 공유할 수 있지만 두 엔드포인트를 모두 공유할 수는 없다. 즉, 소스 IP, 소스 포트, 대상 IP, 대상 포트 또는 프로토콜 중 하나 이상이 서로 달라야 한다.

passive open vs active open

  • passive open: listening socket을 생성하는 것, socket(), bind(), listen(), accept() 함수가 사용된다.
  • active open: listening port에 연결하기 위해 client에서 생성하는 연결, socket(), connect() 함수가 사용된다.

왜 accept()는 새로운 socket을 만드나요

listening socket은 여러 개의 연결을 만들기 위해 사용된다.

accept함수가 반환하는 file dscriptor 즉 socket는 연결을 뜻하고 TCP 연결은 4가지 값(출발 IP/Port, 목적지 IP/Port)으로 정의된다. 반면 listening socket은 local IP와 Port만 관련 있다.

 

 

 


Reference

반응형

'CS' 카테고리의 다른 글

[Network] HTTP/2  (0) 2022.01.10
[Network] HTTP/0.9 ~ 1.1  (0) 2022.01.03
Interpreter VS Compiler  (0) 2021.11.26
[DB]Transaction(Race condition, Isolate level  (0) 2021.11.16
[Network]기본 개념  (0) 2021.10.30
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함