Linux深入理解TCP协议(connect、bind、listen、accept)及其源码_linux tcp connect-程序员宅基地

技术标签: 网络  linux  i.MX6  socket  


主要探讨的问题就是connect及bind、listen、accept背后的三次握手相关问题

基本知识

TCP/UDP/IP

协议 连接 可靠 服务
TCP 面向连接的 可靠的 字节流服务
UDP 无连接 不可靠的 数据报服务
IP 无连接 不可靠的 无状态的

RUDP:介于TCP和UDP中间,两者取其中。

OSI七层模型

在这里插入图片描述

TCP/UDP区别

TCP与UDP通信过程建立的区别。除了它们通信过程建立的不同之外,两者还有以下区别:

TCP通信特点

1)可靠性;

通信双方均就位,一方发送数据,另一方收到后会做出回应,如果超时未发送成功,会自动重发,数据不会丢失。

2)顺序性;

既然数据是按顺序走在建立的一条隧道中,那么数据遵循“先走先达到”的规则,并且隧道中的数据以“流”的形式传输,发送方发送的前后两次数据之间没有边界,需要接收方自己根据事先规定好的“协议”去判断数据边界。

3)高损耗。

“高损耗”包括机器性能损耗高、宽带流量损耗高。因为通信双方时刻需要维持着连接的存在,这必然会损耗通信双方主机性能,要想维持隧道的通畅,通信双方必须不断地发送检测包和应答包,同时,它还支持数据重发等数据纠错功能,这些都将导致网络流量的增加。

UDP通信特点

1)不可靠性;

既然无连接,发送方只管发送数据,而不管对方是否能够正确地接收到数据,更不负责数据超时重发等功能。

2)无序性;

数据以“数据报”的形式发送,可以把“数据报”看成是一个“包”。如果把TCP传输数据比如成“河里的流水”,那么UDP传输数据就是‘邮局寄信’。发送方先发送的数据可能后到达,后发送的数据可能先到达,这个跟短消息类似。

3)低损耗。

“低损耗”包括机器性能损耗低、宽带流量损耗低。UDP通信不需要维持一个连接的存在,所以它不需要消耗额外的机器性能。同时它也没有像TCP通信那样为了保持隧道的通畅,而必须不停地发送检测包和应答包,更不会进行一些数据检测纠错、重发等行为。

这次我们只讨论TCP通信。

TCP建立连接的三次握手过程

首先还是老生常谈的三次握手问题,还是照惯例先来再温习一遍
在这里插入图片描述

  1. 第一次握手:客户端尝试连接服务器,向服务器发送syn(全称是同步序列编号)报文,syn=i,客户端进入SYN_SEND状态等待服务器确认
  2. 第二次握手:服务器接收客户端syn报文并确认(ack=i+1),同时向客户端发送一个新的SYN报文(syn=j),即SYN+ACK报文,此时服务器进入SYN_RECV状态
  3. 第三次握手:客户端收到服务器的SYN+ACK报文,向服务器发送确认报文ACK(ack=j+1),此报文发送并被客户端接收后,客户端和服务器进入ESTABLISHED状态,完成三次握手

TCP编码流程

服务器

实现TCP需要的头文件:

#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

需要的函数原型:

创建socket

int socket(int domain, int type, int protocol);//创建socket

  • 返回值:-1 出错,只要返回值>=0即正确
    返回的值,也是文件描述符 socket文件描述符
  • domain:协议簇 一般用AF_INET(TCP/IP)协议簇
  • type:在该协议簇下选择的具体的协议 TCP:SOCK_STREAM UDP:SOCK_DGRAM
  • pro:具体的协议下更具体的协议:默认给0

bind绑定ip和端口号

int bind(int sockfd, struct sockaddr *seraddr, socklen_t);

  • sockfd:socket返回的文件描述符
  • struct sockaddr *seraddr:服务器端的ip地址
  • socklen_t:第二个参数的长度

为了了解这个函数我们还需要知道:

TCP如何标识一台主机?

需要知道 IP地址 + 端口号

 struct sockaddr_in
   {
    
      sa_family_t sin_family;//地址族
      u_int16_t sin_port;//端口号
      struct in_addr sin_addr;IP地址
   }

   struct in_addr
   {
    
        u_int32_t s_addr;//无符号32位整型值
   }

端口号的转化函数:将IP地址转化成整型值
在这里插入图片描述

端口号取值范围:0-65535 但0-1024(无法使用,系统预留) 1024-5000保留

一般我们用5000以上端口号

地址转化函数:
在这里插入图片描述
主机字节序:

大端模式:高位存低地址
小端模式:高位存高地址

网络字节序:

都用的大端模式(如果主机是小端模式不经过转化在网络中传输数据就会乱套)

listen启动监听

int listen(int sockfd, int size);//给sockfd启动监听

  • sockfd:socket返回的socket值监听sockfd套接字。监听:等客户端连接
  • size:指定已完成连接队列的大小。一般为:size + 1
    两个队列:
    已完成连接的队列
    正在完成连接的队列

accept接收连接的套接字

int accept(int sockfd, struct sockaddr* cliaddr, int len);

  • sockfd:接收连接的套接字
  • struct coskaddr cliaddr:客户端连接时内核自动填充
  • int *len:填充的大小
  • 返回值:返回维护本次连接的文件描述符,服务器和客户端通讯时通过accept返回的文件描述符进行通讯

recv获取数据

int recv(int c, void *buff ,int buffsize, int flag);//获取数据

  • int c:标识从哪个客户端来获取数据
  • void *buff:读到的数据存放在哪
  • int buffsize:buff缓冲区的大小有多少,一次最多读多少个字节的数据
  • int flag:不用默认给0

send发送数据

int send(int c, void *buff,int datasize, int flag); 发送数据

  • int c:将数据发送给谁
  • void *buff:发送的数据从哪开始
  • int datasize:数据的大小
  • int flag:不用默认给0

小结

服务器端编码流程:

  1. 调用socket()创建套接字;
  2. 定义strcuct sockaddr_in ser,cli;
  3. 对地址族,端口号,IP地址进行赋值;
  4. bind()进行命名;
  5. listen进行启动监听
  6. while(1)循环
    {
    int c = accept();获取连接
    while(1)//一个客户端与服务器进行多次通讯
    {
    recv()/send();
    }//收发
    }//一个服务器可以多次处理不同客户端的连接
  7. close();
    如下图为TCP服务器的创建:
    在这里插入图片描述
    在这里插入图片描述

客户端

需要的函数原型:

① int socket(int domain, int type, int protocol);//与前面一样

② int connect(int sockfd, (struct sockaddr*)seraddr, int len);//发起连接

  • int sockfd:上面socket返回的文件描述符
  • (struct sockaddr*)seraddr:和哪个服务器进行连接,服务器的地址和端口号
  • int len:连接的长度
  • 返回:-1 连接失败 返回>-1 成功

③ int send(int c, void *buff,int datasize, int flag);//发起连接

④ int recv(int c, void *buff ,int buffsize, int flag);//获取数据

⑤ close();//发送数据

客户端编码流程:

 1.socker();创建套接字
 2.connect();发起连接和服务器进行连接
 3.while(1)//多次接受发送数据
  {
     send()/recv();多次
  }
 4.close();

下图为TCP客户端的创建:
在这里插入图片描述
在这里插入图片描述

客户端和服务器通信

在这里插入图片描述

探究使用Linux Socket api建立TCP连接的过程

在这里插入图片描述
从创建socket,到建立连接接收数据,最后关闭socket的过程如上图所示。

其中,和建立连接有关系的socket api主要是:connect、bind、listen和accept

为了探究建立连接时发生了什么,和TCP三次握手有什么关系,我们使用之前实验所写的hello/hi程序,用gdb为这四个函数打上断点,并使用wireshark监视相应端口,抓取数据包

bind,listen

当服务端运行bind,listen后,并没有捕获到任何数据包
在这里插入图片描述
在这里插入图片描述

connect

直到客户端运行connect后,才捕获到TCP三次握手发送的数据包,如下图所示
在这里插入图片描述

可以通过抓取的数据包信息看到Socket是如何建立TCP连接的

  1. 由客户端(44434端口)发送SYN数据报给服务端(65432端口),其中seq=0(这里和后面的seq,都是显示的相对seq,实际并不是0)
  2. 服务端返回SYN+ACK数据报给客户端,其中ack=1,seq=0
  3. 客户端返回ACK数据报,其中ack=1

通过这个实践可以推测,TCP的三次握手是在connect和accept之间完成的,bind和listen只是完成绑定和监听的功能

从源码角度分析TCP三次握手的过程

在上一个实验探究Socket底层是如何实现多态机制的时候,我们发现socket结构体中有一个名为ops的结构体指针,结构体中又通过函数指针绑定了具体的底层函数,完成了connectaccept的实现。在struct proto tcp_prot的初始化中我们可以找到对应的绑定函数。

struct proto tcp_prot = {
    

    .name             = "TCP",
    .owner            = THIS_MODULE,
    .close            = tcp_close,
    .pre_connect      = tcp_v4_pre_connect,
    .connect          = tcp_v4_connect,
    .disconnect       = tcp_disconnect,
    .accept           = inet_csk_accept,

    ...
};

可以看到,socket->ops->connect绑定了函数tcp_v4_connectsocket->ops->accept绑定了inet_csk_accept

对tcp_v4_connect的部分源码分析

...
 
    //设置套接字状态,从CLOSE变为TCP_SYN_SENT,对应客户端从CLOSED->SYN_SENT这一过程
    tcp_set_state(sk, TCP_SYN_SENT);
    //将套接字sk放入TCP连接管理哈希链表中
    err = inet_hash_connect(&tcp_death_row, sk);
    if (err)
        goto failure;
   //为连接分配一个随机的空闲端口
    err = ip_route_newports(&rt, IPPROTO_TCP,
                inet->inet_sport, inet->inet_dport, sk);
    if (err)
        goto failure;
 
...
...
 
if (!tp->write_seq)
        //初始化报文内容
        tp->write_seq = secure_tcp_sequence_number(inet->inet_saddr,
                               inet->inet_daddr,
                               inet->inet_sport,
                               usin->sin_port);
 
    inet->inet_id = tp->write_seq ^ jiffies;
    //构建并发送SYN数据报
    err = tcp_connect(sk);
    rt = NULL;
    if (err)
        goto failure;
 
...

inet_csk_accept的部分源码分析

在分析代码前我们需要了解,套接字有监听套接字和具体通信的套接字(accept返回的那个)。监听套接字的扩展结构inet_connection_sock中存在icsk_accept_queue成员,此成员中有两个队列,一个用于完全建立连接(完成三次握手)的队列,此队列项中会包含新建的

用于通信的sock结构,在进程不在阻塞获得此sock结构后会把此队列项从完全建立连接的队列删除.此队列的最大长度即是listen(int s, int backlog)中第二个参数指定的;另一个队列是半连接队列,即还没有完成三次握手的队列项会加入到此队列,此队列项中的sock完成三次握手后会从此队列中移除,添加到完全建立连接的队列中

...
//检查套接字是否处于监听状态(应该是在调用listen时设置的)
    error = -EINVAL;
    if (sk->sk_state != TCP_LISTEN)
        goto out_err;
 
    //在监听套接字上的连接队列如果为空(没有任何连接完成)
    if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {
    
 
        //设置接收超时时间,若调用accept的时候设置了O_NONBLOCK,表示马上返回不阻塞进程
        long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
 
        error = -EAGAIN;
        if (!timeo)//如果是非阻塞模式timeo为0 则马上返回
            goto out_err;
 
        //将进程阻塞,等待连接的完成,inet_csk_wait_for_connect核心是一个循环,等待三次握手中,客户端发来的最后一个ACK报文
        error = inet_csk_wait_for_connect(sk, timeo);
        if (error)
            goto out_err;
    }
 
    //在监听套接字建立连接的队列中删除此request_sock连接项 并返回建立连接的sock
    newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);
 
    //套接字状态变为TCP_SYN_RECV,对应连接建立完成,服务端进入ESTABLISHED状态
    WARN_ON(newsk->sk_state == TCP_SYN_RECV)

分析这两段代码后,我们对TCP连接的建立已经有了一部分认知,tcp_v4_connect()会发送SYN报文开始三次握手,而inet_csk_accept接收来自客户端的ACK报文,标志着TCP连接建立完成。

三次握手的分析还并不完整,服务器端是如何接收第一次握手发来的SYN数据报,并返回SYN+ACK数据报的?实际上服务器端接收到SYN报文后,最终会调用tcp_v4_do_rcv()进行处理, 和tcp_send_ack()一起返回第二次握手中的SYN+ACK报文,客户端则是使用tcp_send_ack() 返回最后的ACK报文。受限于篇幅,不再对这些函数的源码进行分析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/szm1234/article/details/116980451

智能推荐

[解决]Hadoop 2.4.1 UnsatisfiedLinkError: org.apache.hadoop.io.nativeio.NativeIO$Windows.access0-程序员宅基地

问题:UnsatisfiedLinkError: org.apache.hadoop.io.nativeio.NativeIO$Windows.access0这个问题 花我

bert 中文使用(2)_fail to optimize the graph!-程序员宅基地

曾写过bert cs服务方式的调用方法:https://blog.csdn.net/renyuanfang/article/details/86701148,这种方式优点是操作简单,但也有很大的缺陷,不能 fine-tune,操作繁琐,而且速度非常慢 ,不能在windows上使用。最近一直尝试另一种bert的调用方式,克服了上面所说缺点,具体如下:1.准备 tenso..._fail to optimize the graph!

cocos2d-x 3.x 图形学渲染系列六_cocos2d- x navmesh作用-程序员宅基地

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社 和《Unity3D实战核心技术详解》电子工业出版社等书籍。Cocos2D-x引擎目前只提供了3D模块用于3D游戏的开发,针对3D常用模块已经开发完成,但是对于引擎的编辑器而言,目前没有任何编辑器可用,一个成熟的3D游戏引擎_cocos2d- x navmesh作用

Red Hat Enterprise Linux Server 7.4 安装方法_red hat7.4 linux镜像-程序员宅基地

Red Hat Enterprise Linux Server 7.4 安装方法 2017年12月12日 22:34:27 原文链接:http://blog.csdn.net/yangs_2012/article/details/78784764 ..._red hat7.4 linux镜像

计算机考研复试真题 数字求和-程序员宅基地

题目描述给定一个正整数a,以及另外的5个正整数,问题是:这5个整数中,小于a的整数的和是多少?输入描述:输入一行,只包括6个小于100的正整数,其中第一个正整数就是a。输出描述:可能有多组测试数据,对于每组数据,输出一行,给出一个正整数,是5个数中小于a的数的和。示例1输入10 1 2 3 4 11输出1...

自适应单本小说网站源码,基于bootstrap+dedecms。-程序员宅基地

具体效果:http://www.ishengxu.cc/ http://www.yozhou.cc基于bootstrap+dedecms,响应式网站,自适应PC端与手机端,广告位也都设计好了,很简单。最近又鼓捣了一下,增加了火车头自动采集,什么都不用管了。转载于:https://www.cnblogs.com/kpsm/p/6075513.html...

随便推点

如何使用amplifycolor调色插件_unity的 amplify color-程序员宅基地

amplifycolor:可以用PS来调整整个游戏项目的颜色,使用Amplify Color Volume可以局部的设置所进入区域的颜色。第一步:导入amplifycolor。第二步:连接PS第三步:设置Ps;编辑—远程连接——修改密码。勾选启用远程连接。第四步:返回unity3d找到Window,选择LUT Editor。选择settings——password——密码选择自_unity的 amplify color

vue移动端裁剪图片结合插件Cropper的使用-程序员宅基地

之前写了一个上传头像的功能模块,以下的内容是描述上传头像过程中裁剪图片插件结合vue的一个使用。当然,使用就安装 npm install cropperjs接着再引入 import Cropper from 'cropperjs'下面是源码 确定

VR的原理和实现_c# vr视频播放器-程序员宅基地

VR的原理和实现一、 VR内容制作VR内容场景的呈现分为两种情况:实景拍摄与3D建模场景制作。其中,3d建模场景制作又包含了“可以在VR里行走”和“不能在VR里行走”两种情况。1.1全景拍摄的流程第一步 拿到制作需求后,设计师进行头脑风暴思考场景内容,场景切换路径,界面里的文案交互逻辑,输出策划文档。第二步 摄制团队在实景进行视频或全景拍摄,输出全景视频或全景图。第_c# vr视频播放器

《MySQL面试指南(上)》MySQL版本管理、用户管理、服务器配置学习笔记_mysql 版本管理器-程序员宅基地

版本类常见问题1.你之前工作中使用的是什么版本的MySQL?为什么选择这个版本?MySQL常见的发行版、各个发行版之间的区别及优缺点2.如何决定是否要对MYSQL进行升级?如何进行升级?升级后的环境检查业务检查3.最新的MySQL版本是什么?它有什么特性比较吸引你?如何在给定场景下为某用户授权?如何定义MySQL数据库账号?使用CREATE USER命令建立用户My..._mysql 版本管理器

互联网+时代的创业机会与建议-程序员宅基地

2015年8月19日下午有机会给南方软件园创业梦工场的学员们作了一个题为《互联网+时代的创业机会与建议》的分享会,感谢抽时间前来参与与互动的学员们,互联网+以及互联网思维都被炒到快烂掉,令人疲倦的玩意了,我们还有勇气去探讨与交流,还真是辛苦大家了。 理解 对于互联网+,经过无数次的学习,我个人理解归纳为三个方面:一是互联网和传统行业的结合,让传统行业顺利地转型升级—在线化和数据化,我不认为是颠覆或

移植openssh-7.5p1(包括openssl-1.0.2l、zlib-1.2.11)到HISI3520d(编译篇)_openssl 1.02l-程序员宅基地

#PS:要转载请注明出处,本人版权所有#PS:这个只是 《 我自己 》理解,如果和你的 #原则相冲突,请谅解,勿喷 HOST: Linux 4.10.0-35-generic #39~16.04.1-Ubuntu SMP Wed Sep 13 09:02:42 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux TARGET: arm-hisiv4_openssl 1.02l