ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

深入Linux网络子系统02:bind()到底做了什么? 目录前言一、一行代码看懂入口1.1 函数原型1.2 极简示例TCP 服务端二、调用 bind()时内核默默做了什么事情2.1 关键结构体2.2 bind()完整执行链路三、总结前言上一篇文章我们讲了socket()系统调用它仅仅在内核创建一个空的套接字对象分配 fd、初始化sock结构体等等但是没有 IP、没有端口没有任何地址信息内核不知道要从哪个网卡、哪个端口收发数据。今天继续系列第二篇拆解函数bind()bind到底干了什么为什么客户端通常不需要 bind内核端口绑定原理一、一行代码看懂入口1.1 函数原型intbind(intsockfd,conststructsockaddr*addr,socklen_taddrlen);参数详解sockfdsocket()创建的套接字文件描述符addr地址结构体包含协议族、IP、端口addrlen地址结构体长度返回值成功返回0失败返回-1并设置errno1.2 极简示例TCP 服务端intsockfdsocket(AF_INET,SOCK_STREAM,0);structsockaddr_inserv_addr;serv_addr.sin_familyAF_INET;serv_addr.sin_porthtons(8080);// 绑定所有网卡serv_addr.sin_addr.s_addrhtonl(INADDR_ANY);intretbind(sockfd,(structsockaddr*)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,structsockaddr__user*,umyaddr,int,addrlen){structsocket*sock;structsockaddr_storageaddress;interr,fput_needed;// 由 fd 获取 监听的 socketsocksockfd_lookup_light(fd,err,fput_needed);if(sock){// 将用户态地址拷贝到内核中errmove_addr_to_kernel(umyaddr,addrlen,address);// 实际调用 inet_stream_ops 中的 inet_bind详见第一篇文章socket已绑定操作集errsock-ops-bind(sock,(structsockaddr*)address,addrlen);}returnerr;}inet_bind主要做的工作包括intinet_bind(structsocket*sock,structsockaddr*uaddr,intaddr_len){// 确保 sock 处于 TCP_CLOSE 状态且尚未绑定过端口if(sk-sk_state!TCP_CLOSE||inet-inet_num)gotoout_release_sock;// inet_rcv_saddr收包是谁// inet_saddr发包是谁// 在普通单播 bind 时它们是同一个值此时确定了四元组中的源IPinet-inet_rcv_saddrinet-inet_saddraddr-sin_addr.s_addr;// 绑定端口TCP协议实际调用的是 inet_csk_get_portsk-sk_prot-get_port(sk,snum);// 到这里四元组中的 源地址 源端口 确定好了inet-inet_sporthtons(inet-inet_num);inet-inet_daddr0;inet-inet_dport0;}rcv_saddr**和saddr的双地址设计**字段用途场景差异inet_rcv_saddr收包时用于四元组匹配始终等于用户 bind 的地址inet_saddr发包时填充 IP 源地址多播/广播时置 0由出包路径决定inet_csk_get_port比较复杂主要内容总结如下端口分配若用户指定了端口则尝试绑定指定的端口号。若用户没指定端口或指定端口为0则从系统临时端口范围由ip_local_port_range控制内自动选取一个可用的端口。冲突检测与绑定通过查找哈希表bhash检查目标端口是否已被占用。若端口已被其他套接字使用则根据是否允许地址复用SO_REUSEADDR/SO_REUSEPORT以及绑定的具体地址INADDR_ANY或特定 IP判断是否冲突。若无冲突则将当前套接字加入bhash并更新套接字的本地端口字段。三、总结socket()创建空白套接字bind()为套接字分配本地 IP 端口在内核建立地址映射bind 内核流程状态校验→拷贝地址→端口冲突检查→填充 socket 地址→注册bhash服务端必须 bind 固定端口方便客户端访问客户端默认依靠 connect 触发隐式绑定
返回列表