엄범


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

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


```

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() 등으로 받는 경우

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

fgets() 등으로 받는 경우

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

유용한 패턴

can_recv 반대쪽 소켓이 닫히면 EOF를 수신하게 된다!

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

API는 이걸 쓰는걸 권장.

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

recvuntil

```python
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. ``c while``에서 반복 돌 때 마다 현재 시간과 timeout 시간을 비교한다.
```python
while time.time() - start < timeout:
```
Note ) 비동기 프로그래밍에서 이 패턴을 사용하려면, ``c while`` 내부의 작업이 1. non-blocking이면서 2. ``python yield from``으로 제어권을 양보할 수 있어야 한다.
* blocking이면 작업을 양보하다가 모든 코루틴이 대기하게 된다.

nagle algorithm

송신할 때 적용되는 알고리즘으로, TCP socket에는 default로 적용되어 있다.
```python
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가 돌아오면 한꺼번에 모두 전송한다.

```c
int opt_val = TRUE;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt_val, sizeof(opt_val));
```
이렇게 하면 비활성화할 수 있으나 비활성화하면 패킷을 각각 전송해야 해서 보통 트래픽 증가, 서버 성능 저하를 초래한다.