Linux Socket 端口使用

2018-11-13 linux network

Linux 使用的 TCP/IP 协议栈会使用四元组,在客户端与服务端建立链接的时候一般无需指定端口,可以直接使用系统默认指定的端口。

当然,也可以指定固定的端口,不过此时可能会导致端口冲突。

示例

如下是通过 nc 进行的简单测试。

----- 建立一个8080的监听端口
$ nc -k -l 127.0.0.1 8080
----- 客户端直接连接到服务端
$ nc 127.0.0.1 8080
----- 查看客户端使用的端口
# netstat -atunp | grep 8080
tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN      28436/nc
tcp        0      0 127.0.0.1:8080          127.0.0.1:55596         ESTABLISHED 28436/nc
tcp        0      0 127.0.0.1:55596         127.0.0.1:8080          ESTABLISHED 48830/nc

也即是说,当前客户端使用的是 55596 这个端口,如果现在再次使用 nc -l 127.0.0.1 55596 就会报错,其内容为 Ncat: bind to 127.0.0.1:55596: Address already in use. QUITTING.

这也就意味着,只要是一个端口被占用,不管是客户端还是服务端,其它进程是无法再次使用的。

那么可以在连接到服务时指定一个端口,也是通过 bind() 进行绑定,当然,前提仍然是改端口没有被使用,可以参考如下的示例。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>

int main(void)
{
        int rc, sock;
        const char *ipaddr = "127.0.0.1";
        struct sockaddr_in svr, cli;

        sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0) {
                fprintf(stderr, "Create socket failed, %s.\n", strerror(errno));
                return -1;
        }

        memset(&cli, 0, sizeof(cli));
        cli.sin_family = AF_INET;
        cli.sin_addr.s_addr = htonl(INADDR_ANY);
        cli.sin_port = htons(55596);
        if (bind(sock, (struct sockaddr *)&cli, sizeof(cli)) < 0) {
                fprintf(stderr, "Bind client socket failed, %s.\n", strerror(errno));
                close(sock);
                return -1;
        }

        memset(&svr, 0, sizeof(svr));
        svr.sin_family = AF_INET;
        svr.sin_port = htons(8080);
        rc = inet_pton(AF_INET, ipaddr, &svr.sin_addr);
        if (rc <= 0) {
                if (rc == 0)
                        fprintf(stderr, "Not in presentation format.\n");
                else
                        fprintf(stderr, "Inet_pton() failed, %s.\n", strerror(errno));
                close(sock);
                return -1;
        }

        if(connect(sock, (struct sockaddr *)&svr, sizeof(svr)) < 0) {
                fprintf(stderr, "Connect to server failed, %s.\n", strerror(errno));
                close(sock);
                return -1;
        }

        sleep(10000);
        write(sock, "123\n", 4); /* try best */

        close(sock);

        return 0;
}

通常来说,用户不会关心客户端的端口是什么,所以默认不会 bind() ,而是由内核默认分配。

内核配置

实际内核提供了参数用于指定本地客户端分配端口的范围,可以参考如下的设置。

----- 查看当前的配置
$ cat /proc/sys/net/ipv4/ip_local_port_range 
$ sysctl net.ipv4.ip_local_port_range

----- 可以临时修改
$ echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range
$ sysctl -w net.ipv4.ip_local_port_range="1024 65535"

----- 修改配置文件中的默认配置
$ cat /etc/sysctl.conf | grep 'ip_local_port_range'
net.ipv4.ip_local_port_range = 32768 59000

----- 重新加载,使配置修改生效
$ sysctl -p /etc/sysctl.conf

详细可以参考 man 5 sysctl.conf 或者 man sysctl 中的介绍。

注意,上述的配置时,一般建议使用奇数 + 偶数的配置方式。