Post

소켓 프로그래밍 관련 ( Socket programming )

2016/09/20 - [Network] - TCP { TIME_WAIT }

실험적, 경험적으로 판단한 내용이라 항상 이렇게 동작함을 보장하지는 않는다.

1
2
client → output\_buf → ~network~ → input\_buf → recv
└───────     not app layer    ───────┘

TCP socket에서, socket 사이에 주고받는 데이터의 단위는 TCP segment다.

client에서 send(str)str이 하나의 TCP segment에 들어가 전송된다. server에서 recv()는 다음과 같이 동작한다.

  • buffer에 데이터가 있는 경우

buffer에 있는 데이터를 모두또는 지정된 크기만큼 가져오면서 blocking을 해제하고 다음 코드를 진행한다.

  • buffer에 데이터가 없는 경우

buffer에 TCP segment가 들어올 때 까지 대기 하다가 TCP segment가 들어오면 payload를 가져오면서 blocking을 해제하고 다음 코드를 진행한다. 입출력 단위가 TCP segment이기 때문에, buf에 1 byte가 들어왔다고 전송하거나(send) 입력 대기가 끝나는 것이 아니라(recv) 하나의 완전한 TCP segment가 들어오기 전 까지 입력을 대기한다. 단, buffer에 데이터가 있는 경우 이 데이터가 서로 다른 TCP segment로 각각 전송된 데이터라고 하더라도 이를 구분하지 않고 모두 가져온다.

data의 끝 판단하기

read() / recv() 등으로 받는 경우

read() / recv()등으로 대기하고 있는 상태에서 client 측에서 c send()를 호출해 데이터를 보내주면, 상기한 대로 recv()는 buffer에 하나의 완전한 TCP segment가 들어오면 리턴한다. 따라서 이 경우 client 측에서 \n을 보내주던 말던 상관 없다.

fgets() 등으로 받는 경우

fgets()\n을 수신할 때 까지 입력받는다. 따라서 client 측에서 send()를 호출해 데이터를 보내고, buffer에 하나의 완전한 TCP segment가 들어온다고 하더라도 여기에 \n이 포함되어 있지 않으면 계속 대기한다. 따라서 끝에 \n을 넣어 보내주어야 한다. Note ) buffer에서 \n까지만 가져오기 때문에 만약 \n 이후에 데이터가 더 있는 경우 buffer에 남는다.

유용한 패턴

can_recv 반대쪽 소켓이 닫히면 EOF를 수신하게 된다!
1
2
3
## can\_recv check
if len(s.recv(4096)) != 0:
pass

정상적으로 소켓이 close되면 EOF 를 보내오기 때문에 이를 수신하면서 0을 리턴하고 recv()의 blocking이 풀린다. 수신한 문자열을 출력했을 때 아무 것도 출력되지 않으며 리턴값도 0이면 EOF를 수신한 것. 따라서 이를 이용해 check하고 닫아주면 된다. * 그래서 “CLOSE”같은 제어 문자열을 보내서 굳이 app layer단의 close를 진행할 필요가 없다는 얘기다.

API는 이걸 쓰는걸 권장.

POSIX 1003.1의 2001년도 이후 개정판의 제안에 따르면 inet_addr, gethostbyname, gethostbyaddr같은 함수 대신 새로 제안된 표준 함수인 getaddrinfo, getnameinfo을 사용할 것을 권장하고 있다.

recvuntil
1
2
3
4
5
6
7
8
def recvuntil(self, delim):
res = ''
while True:
res += self.recv(4096)
offset = res.find(delim)
if offset != -1:
return res[:offset+len(delim)]
socket.socket.recvuntil = recvuntil
timeout

보통 socket library에서 timeout을 지원해주는데, 그렇지 못한 경우 직접 timeout을 구현해야 한다. timeout을 구현하는 방법은 크게 두 가지로,

  1. 일정 시간이 지나면 signal을 수신한다.
  2. while에서 반복 돌 때 마다 현재 시간과 timeout 시간을 비교한다.
1
while time.time() - start < timeout:

Note ) 비동기 프로그래밍에서 이 패턴을 사용하려면, while내부의 작업이 1. non-blocking이면서 2. python yield from으로 제어권을 양보할 수 있어야 한다. * blocking이면 작업을 양보하다가 모든 코루틴이 대기하게 된다.

nagle algorithm

송신할 때 적용되는 알고리즘으로, TCP socket에는 default로 적용되어 있다.

1
2
3
4
5
6
7
r.send('123')
r.send('456')
r.send('789')
r.send('abcd')
====tcpdump
123
456789abcd
  • 단일 send(str)로 넘어가는 문자열 str은 각 char 하나 하나가 버퍼에 들어간다고 생각하기 보다는, 한 번에 전체 문자열이 출력 버퍼로 들어가서 함께 전송된다고 생각하는 것이 편하다.
  • nagle이 비활성화 되어 있다면 123, 456…이 모두 따로 전송된다.
  • nagle이 활성화 되어 있기 때문에 처음으로 출력 버퍼에 들어온 데이터(123)만 대기 없이 바로 전송하고, 이후 출력 버퍼로 들어온 데이터들은 대기하고 있다가 ACK가 돌아오면 한꺼번에 모두 전송한다.
1
2
int opt\_val = TRUE;
setsockopt(sock, IPPROTO\_TCP, TCP\_NODELAY, &opt\_val, sizeof(opt\_val));

이렇게 하면 비활성화할 수 있으나 비활성화하면 패킷을 각각 전송해야 해서 보통 트래픽 증가, 서버 성능 저하를 초래한다.

This post is licensed under CC BY 4.0 by the author.