加入收藏 | 设为首页 | 会员中心 | 我要投稿 我爱故事小小网_铜陵站长网 (http://www.0562zz.com/)- 视频终端、云渲染、应用安全、数据安全、安全管理!
当前位置: 首页 > 运营中心 > 网站设计 > 教程 > 正文

不可不知的Socket和TCP连接过程

发布时间:2019-08-27 18:37:04 所属栏目:教程 来源:工控自动化专家
导读:副标题#e# 本文主要说明的是TCP连接过程中,各个阶段对套接字的操作,希望能对没有网络编程基础的人理解套接字是什么、扮演的角色有所帮助。如发现错误,敬请指出。 一. 背景 1.完整的套接字格式{protocol,src_addr,src_port,dest_addr,dest_port}。 这常被

当监听者发起accept()系统调用的时候,如果已完成连接队列中没有任何数据,那么监听者会被阻塞。当然,可将套接字设置为非阻塞模式,这时accept()在得不到数据时会返回EWOULDBLOCK或EAGAIN的错误。可以使用select()或poll()或epoll来等待已完成连接队列的可读事件。还可以将套接字设置为信号驱动IO模式,让已完成连接队列中新加入的数据通知监听者将数据复制到app buffer中并使用accept()进行处理。

常听到同步连接和异步连接的概念,它们到底是怎么区分的?同步连接的意思是,从监听者监听到某个客户端发送的SYN数据开始,它必须一直等待直到建立连接套接字、并和客户端数据交互结束,在和这个客户端的连接关闭之前,中间不会接收任何其他客户端的连接请求。细致一点解释,那就是同步连接时需要保证socket buffer和app buffer数据保持一致。通常以同步连接的方式处理时,监听者和工作者是同一个进程,例如httpd的prefork模型。而异步连接则可以在建立连接和数据交互的任何一个阶段接收、处理其他连接请求。通常,监听者和工作者不是同一个进程时使用异步连接的方式,例如httpd的event模型,尽管worker模型中监听者和工作者分开了,但是仍采用同步连接,监听者将连接请求接入并创建了连接套接字后,立即交给工作线程,工作线程处理的过程中一直只服务于该客户端直到连接断开,而event模式的异步也仅仅是在工作线程处理特殊的连接(如处于长连接状态的连接)时,可以将它交给监听线程保管而已,对于正常的连接,它仍等价于同步连接的方式,因此httpd的event所谓异步,其实是伪异步。通俗而不严谨地说,同步连接是一个进程/线程处理一个连接,异步连接是一个进程/线程处理多个连接。

2.5 send()和recv()函数

send()函数是将数据从app buffer复制到send buffer中(当然,也可能直接从内核的kernel buffer中复制),recv()函数则是将recv buffer中的数据复制到app buffer中。当然,使用write()和read()函数替代它们并没有什么不可以,只是send()/recv()的针对性更强而已。

这两个函数都涉及到了socket buffer,但是在调用send()或recv()时,复制的源buffer中是否有数据、复制的目标buffer中是否已满而导致不可写是需要考虑的问题。不管哪一方,只要不满足条件,调用send()/recv()时进程/线程会被阻塞(假设套接字设置为阻塞式IO模型)。当然,可以将套接字设置为非阻塞IO模型,这时在buffer不满足条件时调用send()/recv()函数,调用函数的进程/线程将返回错误状态信息EWOULDBLOCK或EAGAIN。buffer中是否有数据、是否已满而导致不可写,其实可以使用select()/poll()/epoll去监控对应的文件描述符(对应socket buffer则监控该socket描述符),当满足条件时,再去调用send()/recv()就可以正常操作了。还可以将套接字设置为信号驱动IO或异步IO模型,这样数据准备好、复制好之前就不用再做无用功去调用send()/recv()了。

2.6 close()、shutdown()函数

通用的close()函数可以关闭一个文件描述符,当然也包括面向连接的网络套接字描述符。当调用close()时,将会尝试发送send buffer中的所有数据。但是close()函数只是将这个套接字引用计数减1,就像rm一样,删除一个文件时只是移除一个硬链接数,只有这个套接字的所有引用计数都被删除,套接字描述符才会真的被关闭,才会开始后续的四次挥手中。对于父子进程共享套接字的并发服务程序,调用close()关闭子进程的套接字并不会真的关闭套接字,因为父进程的套接字还处于打开状态,如果父进程一直不调用close()函数,那么这个套接字将一直处于打开状态,见一直进入不了四次挥手过程。

而shutdown()函数专门用于关闭网络套接字的连接,和close()对引用计数减一不同的是,它直接掐断套接字的所有连接,从而引发四次挥手的过程。可以指定3种关闭方式:

1.关闭写。此时将无法向send buffer中再写数据,send buffer中已有的数据会一直发送直到完毕。

2.关闭读。此时将无法从recv buffer中再读数据,recv buffer中已有的数据只能被丢弃。

3.关闭读和写。此时无法读、无法写,send buffer中已有的数据会发送直到完毕,但recv buffer中已有的数据将被丢弃。

无论是shutdown()还是close(),每次调用它们,在真正进入四次挥手的过程中,它们都会发送一个FIN。

三. 地址/端口重用技术

正常情况下,一个addr+port只能被一个套接字绑定,换句话说,addr+port不能被重用,不同套接字只能绑定到不同的addr+port上。举个例子,如果想要开启两个sshd实例,先后启动的sshd实例配置文件中,必须不能配置同样的addr+port。同理,配置web虚拟主机时,除非是基于域名,否则两个虚拟主机必须不能配置同一个addr+port,而基于域名的虚拟主机能绑定同一个addr+port的原因是http的请求报文中包含主机名信息,实际上在这类连接请求到达的时候,仍是通过同一个套接字进行监听的,只不过监听到之后,httpd的工作进程/线程可以将这个连接分配到对应的主机上。

既然上面说的是正常情况下,当然就有非正常情况,也就是地址重用和端口重用技术,组合起来就是套接字重用。在现在的Linux内核中,已经有支持地址重用的socket选项SO_REUSEADDR和支持端口重用的socket选项SO_REUSEPORT。设置了端口重用选项后,再去绑定套接字,就不会再有错误了。而且,一个实例绑定了两个addr+port之后(可以绑定多个,此处以两个为例),就可以同一时刻使用两个监听进程/线程分别去监听它们,客户端发来的连接也就可以通过round-robin的均衡算法轮流地被接待。

对于监听进程/线程来说,每次重用的套接字被称为监听桶(listener bucket),即每个监听套接字都是一个监听桶。

以httpd的worker或event模型为例,假设目前有3个子进程,每个子进程中都有一个监听线程和N个工作线程。

那么,在没有地址重用的情况下,各个监听线程是争抢式监听的。在某一时刻,这个监听套接字上只能有一个监听线程在监听(通过获取互斥锁mutex方式获取监听资格),当这个监听线程接收到请求后,让出监听的资格,于是其他监听线程去抢这个监听资格,并只有一个线程可以抢的到。如下图:

不可不知的socket和TCP连接过程

(编辑:我爱故事小小网_铜陵站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读