ps:最近学习freebsd协议栈,发现网上极少文章,初学者自己啃代码实在费劲,很多结构体看得不觉明历,还找找到了这篇非常nice的解析,可惜的是不知道怎么作者没有继续写博客了。原文地址:https://blog.csdn.net/liuyang931361279/article/details/53433419

本文仅涉及对基本的建连过程的讨论,同时打开、建连失败处理等异常流程均不涉及,后期有时间会逐步完善; 
另外,因时间仓促,加之能力有限,文章错误之处在所难免,敬请批评指正

TCP建连状态机

TCP建立过程就是相互发送信息,驱动客户端、服务器状态变化至于稳定可通信状态的过程,我们先给出状态变迁图,后面的论述都将围绕此展开


图1.TCP状态变迁图

接口层代码

我们日常应用TCP协议都是通过调用接口层的系统调用完成的,下面摘录了两段服务器和客户端建立TCP连接的代码,我们主要关注建立连接部分(其他部分被省略了),接下来将通过代码中的系统调用深入内核代码去看看TCP三次握手背后的逻辑

int main(int argc, char *argv[])
{  int server_sockfd;//服务器端套接字  int client_sockfd;//客户端套接字  int len;  struct sockaddr_in my_addr;   //服务器网络地址结构体  struct sockaddr_in remote_addr; //客户端网络地址结构体  int sin_size;  char buf[BUFSIZ];  //数据传送的缓冲区  memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零  my_addr.sin_family=AF_INET; //设置为IP通信  my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上  my_addr.sin_port=htons(8000); //服务器端口号/*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/  if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)  {    perror("socket");  return 1;  }/*将套接字绑定到服务器的网络地址上*/  if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)  {  perror("bind");  return 1;  }  /*监听连接请求--监听队列长度为5*/  listen(server_sockfd,5); sin_size=sizeof(struct sockaddr_in);  /*等待客户端连接请求到达*/  if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)  {  perror("accept");  return 1;  }  printf("accept client %s/n",inet_ntoa(remote_addr.sin_addr));  ...
}  

图2.服务器端代码

int main(int argc, char *argv[])
{  int client_sockfd;  int len;  struct sockaddr_in remote_addr; //服务器端网络地址结构体  char buf[BUFSIZ];  //数据传送的缓冲区  memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零  remote_addr.sin_family=AF_INET; //设置为IP通信  remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址  remote_addr.sin_port=htons(8000); //服务器端口号  /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/  if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)  {  perror("socket");  return 1;  }  /*将套接字绑定到服务器的网络地址上*/  if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)  {  perror("connect");  return 1;  }  printf("connected to server/n");  ...
} 

图3.客户端代码 
上面两部分代码可以概括成下图


图4.系统调用流程图

结构体

在深入系统调用之前有必要让大家大致了解一下,建连所必须的结构体以及其层级关系。我把需要讲解的数据结构基本关系图附在下面,里面涉及了一些和建连无关的结构体和关系,但是考虑到我学习过程所遇到的困惑大多数人也会有,所以在这里一并消除一下


图5.内核数据结构基本关系

这个图的大致意思就是我们可以通过一个描述符找到一个socket,通过这个socket在inpcb链表里有个结构体,如果要是TCP连接则inpcb下面挂着一个tcpcb结构体;当发送信息时候会在路由表里找到下一跳的rtentry,在rtentry所标记的接口(以以太网为例)发送该信息,这样整个发送流程都通了。

以下仅说明一下和建连有关的结构体以及项 
socket{}结构体 
so_type标明服务的协议(tcp、udp、原始套接字等) 
so_head指向调用了accept函数的socket 
so_q0指向未完成三次握手的socket队列 
so_q指向完成了三次握手的消息队列 
so_qlimit为so_q0队列设置上限


图6.插口连接队列

inpcb{}结构体 
存储建连四元组外部地址、外部接口、本端地址、本端接口,标明一个tcp连接

tcpcb{}结构体 
t_state标明连接状态

系统调用

下面将根据接口层代码图4,结合TCP状态变迁图1,说明在建立连接时系统内核的主要逻辑

首先从服务器开始:

1、调用socket来创建一个插口,对于tcp协议,这不仅仅是申请socket结构体挂在file下,而是连同inpcb、tcpcb以及结构体的关系都准备好,置t_state为CLOSED 
2、调用bind为inpcb设定本地地址、本地端口,这个时候对端的ip和端口还不知道,所以四元组为(0.0.0.0,0,laddr,lport),这里需要记住这个四元组,后面会作出说明 
3、接下来调用listen函数,这个函数主要是设定so_qlimit值;另外,图1中的状态机在这里做一次跳动CLOSED——>LISTEN 
4、接下来是accept函数的调用,这个函数是需要和tcp_input函数配合完成任务的。 
4.1调用accept函数如果so_q0没有socket则调用sleep睡眠 
4.2当收到客户发送的含有SYN报文后三次握手第一次握手,tcp_input调用sonewconn生成一个socket,置t_state为CLOSED,把报文中源端dst、源端port赋值在新生成socket关联的inpcb中的laddr、lport,置t_state为LISTEN;然后把其挂在睡眠socket的so_q0上,然后调用in_pcbconnect回复客户端SYN、ACK,并给inpcb设定faddr、fport,状态迁移LISTEN——>SYN_RECV三次握手第二次握手等待对端ACK;

到这里大家有什么疑问没有?有那么多的socket,程序是怎么把SYN报文调度到正确的socket上的,自然是依靠四元组;可是睡眠在accept的socket四元组为(0.0.0.0,0,laddr,lport);客户端发送来的报文的四元组为(x.x.x.x,y,laddr,lport)并不一样如何匹配?原来协议栈规定在寻找四元组配对时如果没有完整匹配0.0.0.0可以匹配任意的ip;0可以匹配任意的端口号

4.3当收到对端ACK时t_state从SYN_RECV——>ESTABLISHED,并且tcp_input调用soisconnected函数把socket从so_q0移到so_q并唤醒睡眠在accept的进程 
4.4accept被唤醒后分配一个文件描述符把socket移除so_q,至此这个连接就正式建立了 
卷二有一张图,形象的描述了上述过程;实线表示真的函数调用;虚线表示状态变迁


图7.处理进入的TCP连接

记得有人问过我LISTEN可不可以不需要,因为貌似在这个状态没有做什么重大的工作。 
首先我们看哪里用到LISTEN这个状态, 
第一个地方:执行accept时设定so_qlimit的时候 
第二个地方:tcp_input收到对端的SYN时候 
在卷二里是这么解释这个状态的通知协议进程准备接收插口上的连接请求。 
请大家也考虑一下LISTEN是不是可以不需要。

客户端:

刚才在讲服务器的时候都涉及到了客户端,所以这里仅需要简要的说明一下 
1、调用socket来创建一个插口,对于tcp协议,这不仅仅是申请socket结构体挂在file下,而是连同inpcb、tcpcb以及结构体的关系都准备好,置t_state为CLOSED 
2、调用bind为inpcb设定本地地址、本地端口 
3、调用connect发送SYN报文三次握手第一次握手,t_state从CLOSED——>SYN_SEND,然后connect也进入sleep 
4、收到对端回复的SYN、ACK报文三次握手第二次握手后,这里的inpcb四元组为(远端ip、远端端口(服务器的知名端口)、本端ip、本端端口)然后置t_state为ESTABLISH,回复ACK三次握手第三次握手,之后建连成功

freebsd协议栈学习相关推荐

  1. [转载]Bluetooth协议栈学习之SDP

    原文地址:Bluetooth协议栈学习之SDP作者:BigSam78 作者: Sam (甄峰) sam_code@hotmail.com SDP(service discovery protocol: ...

  2. CanOpen协议栈学习笔记1-帧格式,SYNC和NMT报文介绍

    前面已经记录过can协议,后面开始CanOpen协议栈学习.其实协议栈代码已经看过了,而且已经在开发板上跑过了.这里回过头来,重新看下之前遇到的坑,记录下学习笔记.下面均以标准帧为例 文章目录 1.C ...

  3. linux协议栈学习 第七节 GRO的实现

    linux协议栈学习 第七节 GRO的实现 GRO (generic receive offload) 概述: GRO是在协议栈接收报文时进行减负的一种处理方式,该方式在设计上考虑了多种协议报文.主要 ...

  4. linux 协议栈学习 第八节 链路层GRO的处理

    linux 协议栈学习 第八节 链路层GRO的处理 链路层的接收匹配函数__napi_gro_receive(napi, skb): 该函数对报文进行匹配,并不合并报文. 匹配规则必须同时满足以下两个 ...

  5. linux 网络协议栈变化,ZZ Linux网络协议栈学习

    最近学习linux内核网络协议栈,把数据包接收流程大致理了一下, 前面也看了瀚海书香兄的总结,感觉总结的比我精炼,抓住了主干,是一目了然的那种 我的这篇本来是自己看得,因此把我自己学习中一些遇到的问题 ...

  6. Linux 内核协议栈 学习资料

    终极资料 1.<Understanding Linux Network Internals> 2.<TCP/IP Architecture, Design and Implement ...

  7. BLE协议栈学习2——OSAL

    OSAL简介 BLE 协议栈包含了 BLE 协议所规定的基本功能,这些功能是以函数的形式实现的,为了便于管理 这些函数集,BLE 协议栈内加入了实时操作系统(并非真正意义上的操作系统),称为 OSAL ...

  8. Android蓝牙协议栈学习

    Android蓝牙协议栈当前的名字叫做fluoride(氟化物),同时Google正在开发一个新的蓝牙协议栈叫做Gabeldorsh,其中用到了Rust语言. 学习Android蓝牙协议栈涉及到的内容 ...

  9. CC2530 ZigBee协议栈 学习心得

    最近一直在学习研究cc2530这款单片机,感觉自己的C语言水平还是不够有得提升的空间,但还是有不少收获.    CC2530是一款支持ZigBee无线组网协议的低功耗单片机,cc2530主要的应用场景 ...

  10. zigbee协议栈学习(二)

    协议栈规范的 ID号可以通过查询设备发送的 beacon 帧获得.在设备加入网络之前,首先 需要确认协议栈规范的 ID."特定网络"规范 ID号为0: ZigBee协议栈规范的 I ...

最新文章

  1. android TextView里边实现图文混配效果
  2. c语言单链表冒泡排序的步骤,急!!求c语言单链表冒泡排序的详细流程图
  3. iframe cross domain
  4. 3.spring cloud + zookeeper注册中心 + Feign调用案例
  5. React路由组件传递参数
  6. python内置装饰器property_python之内置装饰器(property/staticmethod/classmethod)
  7. 2018-2019-2 20175230 实验三《Java面向对象程序设计》实验报告
  8. 拓端tecdat|用R对Twitter用户的编程语言语义分析
  9. 有趣的逻辑较量——《啊哈C语言》更新开始
  10. 一键去除AutoCAD图形乱码的问题
  11. Head First Java 中文版 (第 2 版) PDF 下载
  12. java der格式_读取DER格式java中的私钥
  13. python开发板卡驱动开发_树莓派开发板如何驱动LED灯
  14. 机器学习(9)--决策树和随机森林
  15. OpenGL - Hermite算法多点画光滑曲线
  16. 大数据 | Hadoop性能测试
  17. Linux 线程基础 1
  18. Linux Shell相关记笔记
  19. Revit调用winform
  20. pytorch求导总结(torch.autograd)

热门文章

  1. 对python程序设计的学习心得_程序设计心得体会-精选模板
  2. 漏洞扫描器——nmap的使用
  3. OpenGL下载和配置
  4. 基于Linux下的Nand (Nor) Flash读写速度测试
  5. 软件单元测试及测试用例设计
  6. 未来计算机作文想象,想象未来作文450字
  7. 基于java的试题库管理系统(java CS窗体版)
  8. 手柄xinput模式_让你的普通手柄变成360手柄(XInputEmulator)
  9. 鸿蒙大陆6.1正式版 密码,天寒大陆1.06下载 天寒大陆1.06正式版 附游戏攻略及隐藏英雄密码 魔兽防守地图 下载-脚本之家...
  10. 【电子技术实验设计】简易水位控制器设计报告