当前位置:首页>Linux>深入Linux网络子系统02:bind()到底做了什么?

深入Linux网络子系统02:bind()到底做了什么?

  • 2026-09-02 16:00:32
深入Linux网络子系统02:bind()到底做了什么?

前言

上一篇文章我们讲了socket()系统调用:它仅仅在内核创建一个空的套接字对象,分配 fd、初始化sock结构体等等,但是没有 IP、没有端口,没有任何地址信息,内核不知道要从哪个网卡、哪个端口收发数据。今天继续系列第二篇,拆解函数bind():

  1. bind到底干了什么?
  2. 为什么客户端通常不需要 bind?
  3. 内核端口绑定原理?

一、一行代码,看懂入口

1.1 函数原型

intbind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数详解:

  • sockfd:socket()创建的套接字文件描述符
  • addr:地址结构体,包含协议族、IP、端口
  • addrlen:地址结构体长度

返回值:

  • 成功:返回0
  • 失败:返回-1,并设置errno

1.2  极简示例(TCP 服务端)

int sockfd = socket(AF_INET, SOCK_STREAM, 0);structsockaddr_inserv_addr;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(8080);// 绑定所有网卡serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);int ret = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

二、调用 bind()时,内核默默做了什么事情

2.1 关键结构体

// struct inet_hashinfo 是 Linux 内核网络协议栈中用于管理 TCP/UDP 连接查找的关键数据结构structinet_hashinfo{// 所有建立连接的socket,哈希key:四元组structinet_ehash_bucket *ehash;// 所有绑定端口的socket,哈希key:端口structinet_bind_hashbucket *bhash;// 所有监听scoket,哈希key:端口structinet_listen_hashbucketlistening_hash[INET_LHTABLE_SIZE];};

四元组:网络通信中唯一标识一条 TCP 连接的四个要素。

  • 源 IP 地址(Source IP)
  • 源端口号(Source Port)
  • 目的 IP 地址(Destination IP)
  • 目的端口号(Destination Port)

有时也会提到五元组,就是在四元组基础上加上传输层协议号(TCP/UDP),用于区分不同协议的连接。

2.2  bind()完整执行链路

socket的相关函数都在net\socket.c中,bind()方法主要内容如下:

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen){structsocket *sock;structsockaddr_storageaddress;int err, fput_needed;// 由 fd 获取 监听的 socket    sock = sockfd_lookup_light(fd, &err, &fput_needed);if (sock)    {// 将用户态地址拷贝到内核中        err = move_addr_to_kernel(umyaddr, addrlen, &address);// 实际调用 inet_stream_ops 中的 inet_bind(详见第一篇文章,socket已绑定操作集)        err = sock->ops->bind(sock, (struct sockaddr *)&address, addrlen);    }return err;}

inet_bind主要做的工作包括:

intinet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len){// 确保 sock 处于 TCP_CLOSE 状态且尚未绑定过端口if (sk->sk_state != TCP_CLOSE || inet->inet_num)     goto out_release_sock;// inet_rcv_saddr:收包是谁// inet_saddr:发包是谁// 在普通单播 bind 时,它们是同一个值,此时确定了四元组中的源IP    inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;// 绑定端口,TCP协议实际调用的是 inet_csk_get_port    sk->sk_prot->get_port(sk, snum);// 到这里,四元组中的 源地址 源端口 确定好了    inet->inet_sport = htons(inet->inet_num);    inet->inet_daddr = 0;    inet->inet_dport = 0;}

rcv_saddr和saddr的双地址设计:

字段
用途
场景差异
inet_rcv_saddr
收包时用于四元组匹配
始终等于用户 bind 的地址
inet_saddr
发包时填充 IP 源地址
多播/广播时置 0,由出包路径决定

inet_csk_get_port比较复杂,主要内容总结如下:

  1. 端口分配:
  • 若用户指定了端口,则尝试绑定指定的端口号。
  • 若用户没指定端口或指定端口为0,则从系统临时端口范围(由 ip_local_port_range 控制)内自动选取一个可用的端口。
  1. 冲突检测与绑定:
  • 通过查找哈希表(bhash),检查目标端口是否已被占用。
  • 若端口已被其他套接字使用,则根据是否允许地址复用(SO_REUSEADDR / SO_REUSEPORT)以及绑定的具体地址(INADDR_ANY 或特定 IP)判断是否冲突。
  • 若无冲突,则将当前套接字加入bhash,并更新套接字的本地端口字段。

三、总结

  1. socket()创建空白套接字;bind()为套接字分配本地 IP + 端口,在内核建立地址映射
  2. bind 内核流程:状态校验→拷贝地址→端口冲突检查→填充 socket 地址→注册bhash
  3. 服务端必须 bind 固定端口,方便客户端访问;客户端默认依靠 connect 触发隐式绑定

四、后续预告

本文基于 Linux 内核源码梳理 bind 的底层实现,文中如有错误或者理解偏差,欢迎大家在评论区留言指正,一起交流探讨。下一篇继续拆解:listen()到底完成了什么? 深入解析 TCP 半连接队列、全连接队列、backlog 参数真实作用。

最新文章

随机文章