分享

TCP通信的基本流程

 心不留意外尘 2016-08-30

2016-03-08 

http://houjixin.blog.163.com/blog/static/3562841020162825344890/


1      TCP 基础知识

1.1       TCP的七层协议简介

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

1.2基本概念

Tcpdump port 1883 -w /tmp/mosq.pcap

1TCP建立连接的过程

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 [原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

2) TCP 断开连接的过程

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

3)为什么需要四次断开

TCP支持双工通信,即收、发两个方向各自独立工作,一个FIN+ACK只能断开一个方向的通信,当要断开两个方向的通信时就需要两个FIN+ACK,即四次操作。

1.2   TCP状态

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

TCP状态解释:

SYN-RECVD:再收到和发送一个连接请求后等待对方对连接请求的确认
ESTABLISHED
:代表一个打开的连接
FIN-WAIT-1
:等待远程TCP连接中断请求,或先前的连接中断请求的确认
FIN-WAIT-2
:从远程TCP等待连接中断请求
CLOSE-WAIT
:等待从本地用户发来的连接中断请求
CLOSING
:等待远程TCP对连接中断的确认
LAST-ACK
:等待原来的发向远程TCP的连接中断请求的确认
TIME-WAIT
:等待足够的时间以确保远程TCP接收到连接中断请求的确认
CLOSED:没有任何连接状态

 

1.3   需要关注的点

1.3.1 TIME_WAIT状态

主动关闭socket的一方会存在这个状态,

为什么会存在socket的这个状态?让tcp连接可靠地终止;让重复分组在网络中消失;

它有什么影响?哪些系统参数可以影响此状态?

1.4基本概念,

1.4.1 服务端socket分类:监听socket、业务socket

监听socket处于Listen状态的socket,它主要用于接收客户端的连接请求;

业务socket:用于服务端和客户端之间进行业务通信的socket

注意客户端的socket没有这个区分,它的socket都是用作和服务端进行业务通信的,因此可以认为客户端创建的socket都是业务socket

1.4.2 地址、端口与协议

地址:一般都是指主机地址或者ip地址,这二者有一一对应关系,例如常用的IPV4ip地址形式为:210.45.0.12;一个地址就标识了一台主机,如果在通信程序中知道了对方的地址,你就知道往哪台机子上发送了。但是现在还不知道发给那台机子上的哪个程序。

端口:端口的数据类型是unsigned short,为16位,因此端口的使用范围为0~65535,其中0~1024之间的端口为特殊用途,用户可以使用的端口范围需在1024~65535之间,端口标识了一个服务或者是应用程序。因此知道了一个地址+端口,就知道了向哪台机子的哪个程序发送数据。但是,现在还不知道跟对方通信是采用哪种协议,就像两个人要说话,但是还不知道该用英文说还是中文说。

协议:协议是指通信双方使用的通信协议,例如tcpudp之类。协议就表明了双方采用什么方式进行通信。

 

2      TCP通信的基本流程

2.1简单的实例伪程序,服务端伪程序

  // 1. 建立一个监听socket

    listen_socket = socket(AF_INET, SOCK_STREAM, 0);

    // 2. 创建一个监听socket绑定的本地地址结构体

    serverAddr.sin_family = AF_INET;

    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);  // 使用通配地址

    serverAddr.sin_port = htons(5500);                 // 5500是服务器的监听端口号

    int ret = bind(listen_socket, (sockaddr*)&serverAddr, sizeof(serverAddr) );

    // 3. 开始监听客户端请求

    listen(listenfd, SOMAXCONN);  // 注意这里的 SOMAXCONN 参数                                 

    // 4. 循环接受客户端的请求

    while (true){

       // 5. accept以阻塞方式工作,当有一个连接进来时,为这个连接产生一个业务socketbusiness_socket

        business_socket = accept(listenfd, (sockaddr*)NULL, NULL);         

       // 6. 向业务socketbusiness_socket中写入数据                      

        send(business_socket, buf, strlen(buf), 0 );                           

       // 7. 关闭业务socket

        close(business_socket);

    }

一个简单的客户端伪程序

        //1.创建一个客户端使用的socket

        client_socket = socket(AF_INET, SOCK_STREAM, 0)      

        //2.创建一个地址结构体,用于存储服务端的地址信息

        servaddr.sin_family = AF_INET;

        servaddr.sin_port   = htons(5500);

        servaddr.sin_addr.s_addr = inet_addr(server_ip);

        //3.连接服务器程序

        connect(client_socket, (struct sockaddr*) &servaddr, sizeof(servaddr))

        //4.socket中读取数据到recvline

        while ( (n = read(client_socket, recvline, MAXLINE)) > 0)            {

        //5.处理存储在recvline中的内容

        }

2.2 连接建立过程

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

[1] 服务器端先启动,创建一个监听socket,将这个socket绑定到一个公开端口,然后监听这个socket以等待客户端的连接请求发送过来;

[2] 客户端后启动,创建一个socket,然后用它连接到服务端的监听socket

[3] 服务端在监听socket等着客户端连接进来之后,在服务端为这个连接产生一个业务socket,后续服务端就用此业务socket给这个客户端通信;

[4] 客户端在连接建立后,就可以向服务端发送请求数据;

[5] 客户端在请求满足之后,主动关闭连接;

(1)     连接建立时的socket五元组变化情况

假设上述的服务端程序运行在192.168.1.201上,客户端运行在192.168.1.202上,根据前面的表述,一个socket可以认为是由一个五元组标识:<本地地址,本地端口,远端地址,远端端口,通信协议>,用*表示一个未确定项(通配项),下面的过程将以这个五元组的变化来描述客户端和服务器的工作过程(图片中黄色代码本地的地址和端口,红色为远端地址和端口):

[1] 服务端创建一个socket,执行代码:  listen_socket = socket(AF_INET, SOCK_STREAM, 0)后这个socket的五元组的样子是:<*,*,*,*,TCP>,即这时只知道这个socketTCP协议通信,但是还不知道TCP对应的源端口、IP和目的端口、IP

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

[2] 服务端创建一个监听地址,并将监听socket绑定到这个监听地址上,经过该步骤之后,监听socket对应五元组的本地端口号已经被设置为5500

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

[3] 服务端执行监听操作之后将在监听socket上监听客户端的连接请求;

[4] 客户端创建一个socket,与服务端创建socket时一样,这里创建时只指定了socket五元组的通信协议:TCP

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

[5] 客户端执行connect操作,该操作中将指定要连接的服务端的IP地址和端口号,本地的IP地址和端口号由OS帮我们填写,然后与服务器端建立连接,即执行三次握手操作;

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

[6] 服务端的监听socket接到客户端连接请求之后将为之产生一个业务socket,业务socket的本地地址和端口都有监听socket一致,监听socket指定本地地址,业务socket里要指定,业务socket的远端地址和端口来自这个新连接中,上层业务通过accept获得这个新业务socket

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

[7] 多个客户端的socket五元组中,目的地址、目的端口号、通信协议是一样的;同一主机上的客户端(下图中的client1client2)所对应的socket五元组中只有本地端口号不一样,不同主机上客户端所对应的socket五元组中本地IP地址不一样,本地端口则可能一样;在服务端生成的业务socket中,目的地址和端口号中必须有一个不一样才能对它们进行区分;

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

[8] 连接建立之后,客户端与服务端之间使用业务socket进行通信

2.3 数据交付过程

问题描述:每台主机可能很多socket在使用,这些socket可能分属于不同的应用进程,但是在操作系统内,他们都是一样的,那么数据到来之后,操作系统是怎么判断数据是属于哪个socket的呢?

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

UNIX操作系统内部,每个socket中都包含一个协议控制块,协议控制块中将有每个socket对应的五元组信息,这些协议控制块将被串联起来放在一个链表中,socket结构图和协议控制块中分别有指针指向彼此;如下图TCP的协议控制块链表:

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

协议控制块属于传输层的组成部分,如下图绿色框所示,TCPUDP分别有自己独立的链表。

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

TCP收到来自IP层的数据报后,将查找协议控制块列表,通过对比数据报文中端口号等信息,找到通配匹配数最小的协议控制块,即是目标协议控制块中,由于协议控制块中有指针指向对应的socket,也就找到了数据应该交付到哪个socket中,然后将数据放在socket的接收buffer中即可;这里通配匹配数是TCP的本地地址、本地端口、远端地址、远端端口这四项中,在没有确定不匹配项的前提下,用通配符*所匹配的项数,通配匹配数越小,说明匹配度越高;

例如,假设当前server端主机只有一个监听socket和三个业务socket,如下:

本地地址

本地端口

远端地址

远端端口

TCP状态

*

5500

*

*

LISTEN

192.168.1.201

5500

192.168.1.202

3500

ESTABLISH

192.168.1.201

5500

192.168.1.202

3501

ESTABLISH

192.168.1.201

5500

192.168.1.203

3501

ESTABLISH

 

 

 

 

 

当前TCP收到一个来自端口5500的报文段,其源地址为192.168.1.202,源端口为3500,目的地址为192.168.1.201,目的端口为5500(这个报文来自客户端,因此这里要站在客户端的角度来看),此报文与server端主机的所有SOCKET对比如下:

本地地址

本地端口

远端地址

远端端口

通配匹配数

*

5500

*

*

3

192.168.1.201

5500

192.168.1.202

3500

0

192.168.1.201

5500

192.168.1.202

3501

端口不匹配

192.168.1.201

5500

192.168.1.203

3501

Ip和端口不匹配

 

 

 

 

 

第一个socket匹配当前报文段时,只有本地端口5500是完全匹配,本地地址、远端地址和远端端口都是使用通配符*匹配的,因此通配匹配数为3

第二个socket匹配当前报文段时,全部精确匹配,使用通配符*匹配的项数为0

第三个socket匹配当前报文段时,其远端端口3501与报文的源端口3500不匹配,因此排除此项;

第四个socket匹配当前报文段时,其远端端口3501和远端IP192.168.1.203与报文段的源端口3500和源IP192.168.1.202都不匹配,因此排除此项;

通过上述的比较,可以得到最小匹配数为0的第二个socket的协议控制块的结构体(PCB),通过此PCB就能知道它对应的socket结构体,接下来就可以将数据放到此socket结构体的接收缓存中。

2.4 一些注意点

(1)     关于TCP连接

所谓TCP建立连接就是进行通信的客户端和服务端之间各自在自己的OS中准备好了一个socket结构体,并且已经通过三次握手的方式确定双方当前的链路是通的,这里的当前是指三次握手的时候,但它并不保证接下来使用时这两个socket之间还能正常通信;

TCP正常断开连接时,通信双方会进行四次交互,这时双方socket能知道连接要被断开,socket也能被OS正常释放;

由于现实网络的复杂性,极可能两个socket之间的网络在三次握手的时候还是好的,但是数据传输的时候就不行了;

假如在如下网络中,Client要与Server进行TCP通信:

[原]TCP通信的基本流程 - 逍遥子 - 逍遥子 曰:

 

按照前面所述流程,Server端先启动一个监听socket来等待接收新连接,然后Client创建一个socket,并连接到Server端的监听socket,一旦客户端的连接返回成功,也就意味着:

[1] Clientserver端各自建立一个socket用于数据通信;

[2] Clientserver之间已经完成三次握手,说明三次握手期间,双方之间的链路是通的;

这时如果中间的网关1坏了,ClientServer之间是无法完成通信的,而且双方也无法及时知道网关1坏了,此时双方发送数据时将会失败。

针对上述情况,

(2)     文档《关于socket的一些总结》

为什么我们的服务器程序可以支持一百万甚至更多的连接,但是我们在一台主机上只能启动6万多个连接?

 

2.1       一些注意事项

1)连接队列长度参数

3      多路IO复用

(1)     阻塞/非阻塞socket

http://blog.csdn.net/houjixin/article/details/27662489

(2)     Select\poll\epoll

 

4      TCP服务开发

1)常见的规则:服务端被动提供服务,客户端主动请求服务;正常情况下,在基于tcp的服务中,一般是客户端发起请求服务,服务被处理之后,客户端再主动断开连接;

服务端需处理连接异常断开的情况;

2)应用层Keepalive存在的作用

5      多线程操作

下列操作需要头文件:pthread.h

 

5.1       多线程

1)创建线程:

pthread_create(pthread_t *tidp, const pthread_attr_t *attr, (vod*)(*start_rtn)(void*), void *arg)

成功返回0,失败返回出错码;

2)等待线程结束

pthread_join(A, B)

3)取消线程执行

pthread_cancel(A)

编译使用命令要加上 -pthread, 例如:

Gcc multithread.c -o multithread -pthread

 

5.2       互斥量

pthread_mutex_init(A, B)

pthread_mutex_destroy(A)

pthread_mutex_lock(A)

pthread_mutex_unlock(A)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多