Skip to content

Linux TCP implementation

TCP/IP connection四要素

一个TCP/IP connection是由四要素组成:

{source address, source port, destination address, destination port}

因此在使用socket进行TCP/IP编程的时候,涉及上述四个要素:

1、有的是需要由programmer来进行指定的

2、有的是有kernel来动态分配

两端/endpoint

一个TCP connection由两端构成:

source: {source address, source port}

destination: {destination address, destination port}

address、port的reuse

TCP option:

1、SO_REUSEADDR

2、SO_REUSEPORT

socket

socket是Linux network programming的核心。

listen socket VS connection socket

listen socket是比较特殊的,由于它并不参与TCP connection,因此它和connection socket是有明显差异的:

1、connection socket会参与TCP FSM,但是listen socket并不参与

2、TCP connection socket相当于一个TCP connection endpoint

3、listen socket

connection socket、connection endpoint

connection socket是TCP connection endpoint。

TCP connection、socket、file descriptor、process

一、一个TCP connection由两个socket组成

[dk@localhost ~]$ netstat -tna | grep 3459
tcp        0      0 127.0.0.1:55490         127.0.0.1:3459          CLOSE_WAIT 
tcp        0      0 127.0.0.1:3459          127.0.0.1:55490         FIN_WAIT2

上面就展示了一个TCP connection由两个socket组成。

二、当close、process exit后,kernel中的TCP connection不会立即被销毁,而是会存在一段时间,这样做是因为:

1、对端可能还没有关闭连接

2、TIME_WAIT

所有,此时socket还存在于kernel中,并不会被销毁;

测试程序

下面是测试程序,源自 zhihu 网络编程:SO_REUSEADDR的使用

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char const *argv[])
{
    int lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (lfd == -1)
    {
        perror("socket: ");
        return -1;
    }

    struct sockaddr_in sockaddr;
    memset(&sockaddr, 0, sizeof(struct sockaddr_in));
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(3459);
    inet_pton(AF_INET, "127.0.0.1", &sockaddr.sin_addr);

    // int optval = 1;
    // setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    if (bind(lfd, (struct sockaddr*) &sockaddr, sizeof(sockaddr)) == -1)
    {
        perror("bind: ");
        return -1;
    }

    if (listen(lfd, 128) == -1)
    {
        perror("listen: ");
        return -1;
    }

    struct sockaddr_storage claddr;
    socklen_t addrlen = sizeof(struct sockaddr_storage);
    int cfd = accept(lfd, (struct sockaddr*) &claddr, &addrlen);
    if (cfd == -1)
    {
        perror("accept: ");
        return -1;
    }
    printf("client connected: %d\n", cfd);

    close(cfd);
    close(lfd);
    return 0;
}
// gcc test.c

然后用nc模拟客户端连接:

nc 127.0.0.1 3459

连接后可以正常和服务器通常,用netstat看看连接的状态:

$ netstat -tna | grep 3459
tcp        0      0 127.0.0.1:3459          0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:36463         127.0.0.1:3459          ESTABLISHED
tcp        0      0 127.0.0.1:3459          127.0.0.1:36463         ESTABLISHED

第1条是监听套接字,第2条客户端的连接,第3条是服务器和客户端的连接;

测试一

第一步: 关闭server,不关闭client

[dk@localhost ~]$ netstat -tna | grep 3459
tcp        0      0 127.0.0.1:3459          127.0.0.1:55486         FIN_WAIT2  
tcp        0      0 127.0.0.1:55486         127.0.0.1:3459          CLOSE_WAIT

此时server process已经退出,但是TCP connection还是存在的,可以看到:

1、listening socket已经被销毁了

2、TCP connection 的server socket处于 FIN_WAIT2 状态,即它已经收到了client的FIN ACK

3、TCP connection 的client socket处于 CLOSE_WAIT 状态

第二步: 关闭client

将client关闭,再查看socket

[dk@localhost ~]$ netstat -tna | grep 3459
tcp        0      0 127.0.0.1:3459          127.0.0.1:55488         TIME_WAIT

可以看到,TCP connection 的server socket处于 TIME_WAIT 状态;client socket已经被销毁了;

第三步: 重启server

第1条是监听套接字,第2条客户端的连接,第3条是服务器和客户端的连接;我们强制中止服务器(Ctrl+C),再看看netstat:

$ netstat -tna | grep 3459
tcp        0      0 127.0.0.1:3459          127.0.0.1:36463         TIME_WAIT

此时只有服务器建立的连接,处于是TIME_WAIT状态,再次启动服务器,会报下面错误:

$ ./treuseaddr 
bind: : Address already in use

如果把上面两行注释的去掉,就能解决这个问题,可以自己动手试试看。

测试二

测试超时,在关闭server后,一直不关闭client,此时server connection socket一直处于处于FIN_WAIT2状态,但是,server connection socket并不会一直处于处于FIN_WAIT2状态,经过一段时间后,kernel会将它清理掉。