做网站的时候会用 鸟瞰图吗,angularjs 做团购网站,简单的cms源码,服装定制属于什么行业文章目录 1. TCP协议段格式1.1. 如何解包 / 向上交付1.1.1. 交付1.1.2. 解包 1.2. 如何理解可靠性1.2.1. 确认应答机制#xff08;ACK#xff09;1.2.2. 序号 与 确认序号 2. TCP做到全双工的原因2.1. 16位窗口大小2.2. 6个标记位 3. 如何理解连接3.1 连接管理机制3.1.1. 三次… 文章目录 1. TCP协议段格式1.1. 如何解包 / 向上交付1.1.1. 交付1.1.2. 解包 1.2. 如何理解可靠性1.2.1. 确认应答机制ACK1.2.2. 序号 与 确认序号 2. TCP做到全双工的原因2.1. 16位窗口大小2.2. 6个标记位 3. 如何理解连接3.1 连接管理机制3.1.1. 三次握手3.1.2. 四次挥手什么是 TIME_WAIT / CLOSE_WAIT 4. TCP实现可靠性的方式4.1. 发送缓冲区4.2. 超时重传机制4.3. 流量控制 5. TCP 提高性能的方式5.1. 滑动窗口5.1.1. 作用 与 本质5.1.2. 完善理解 - 模型5.1.3. 滑动窗口的部分问题 5.2. 快重传高速重发机制5.3. 拥塞窗口tmp5.4. 延迟应答5.5. 捎带应答 6. 如何理解 TCP面向字节流6.1. 粘包问题6.2. TCP异常情况 状态 7. TCP总结8. 基于TCP的应用层协议9. TCP / UDP 对比10. TCP相关实验 - listen的第二个参数理解 1. TCP协议段格式
下图为TCP协议段格式 1.1. 如何解包 / 向上交付
1.1.1. 交付
TCP的报头前两项和UDP一样交付过程也类似UDP根据16位目的端口号源端口号可以将数据向上交付给进行进程绑定了端口号。
1.1.2. 解包
首先根据TCP协议端格式了解到TCP报头标准长度为20字节。在这20字节中存在名为 4位首部长度 的内容 4位首部长度的范围即 0000 ~ 1111 转十进制即 0 ~ 15而该字段的单位为4字节得到TCP报头的长度范围为[20, 60]下面分析解包的过程 提取20字节标准长度根据标准报头提取4位首部长度 * 4如果等于20解包结束否则继续读取4位首部长度 * 4 - 20字节数据即“选项”内容此时报头读完剩余的为有效载荷 根据上面的内容引出一个疑问我们知道UDP是面向数据报的协议段中有整个报文的大小那么对于TCP面向字节流协议段中有没有整个报文或是有效载荷的大小呢 1.2. 如何理解可靠性
我们知道TCP是可靠的而UDP是不可靠的可靠就是好不可靠就是坏吗
网络通信中可靠性指的是数据传输的确保性和完整性。即两台主机/进程通信时双方能完整正确的获得对方通信的内容。举个例子 此时我们就可以理解这一句话可靠性指的是数据传输的确保性和完整性。
那么存不存在 100%可靠性的协议呢 —— 不存在 再次举一个例子
在实际的网络通信中不存在通信一方可以保证自己发送的消息一定会被对方接收到但是在局部上是可以做到可靠的。在刚刚的例子中一方发出的消息只要有对应的应答就证明是被对方接收到了 此时我们引入TCP的确认应答ACK机制只要一个报文收到了对应的应答就能保证发出的数据被对方收到了。
1.2.1. 确认应答机制ACK
我们知道TCP是全双工 的通信双方可以同时进行接收和发送数据。
在通信的过程中客户端向服务端发送了数个消息服务端都一一应答如何保证客户端接收到的消息能正确匹配呢如果不能正确顺序接收会发生乱序也是不可靠的一种 TCP协议段中有两个内容32位序号与32位确认序号 即可以通过序号给报文编号保证接收的顺序完整。
1.2.2. 序号 与 确认序号
对于 序号 与 确认序号
将请求与应答一一对应确认序号表示某段确认序号之前的数据全部收到允许部分”确认“丢失或不应答为什么是两段数字序号 与 确认序号任何通信的一方都是全双工两方在发送确认的同时会携带新的数据。乱序问题1000 - 2000 - 3000 —— 2000 - 1000 - 3000 在网络差的时候发送给别人的消息在己方看着是乱序的。而实际上任何一方通信时都会收到报文又报文携带着序号最后会根据序号进行排序 2. TCP做到全双工的原因 2.1. 16位窗口大小
两台主机通信的时候显然 发送方 应确保数据的发送不能过快 / 过慢如何保证发送放发送数据的速度适中呢
接收方始终给发送方同步自己的接收能力我们知道TCP通信时实际是将数据传输到缓冲区中自然接收能力与接收缓冲区相关接收能力由TCP协议段中的 16位窗口大小决定16位窗口大小 即接收缓冲区的剩余空间大小
通信双方同时向对方发送自身的接收能力也是构成全双工的一点。 2.2. 6个标记位
标记位1bit表示的某种含义 URG紧急指针 urgent pointer当URG为1时表明紧急指针字段有效用来指示数据中的紧急数据。ACK确认 Acknowledgment当ACK为1时表明确认号字段有效用来确认收到的数据。PSH推送 Push当PSH为1时表示接收方应该尽快将数据交给应用程序而不是等到缓冲区满再交付。RST复位 Reset当RST为1时表示连接复位用来中断连接。SYN同步 Synchronize在建立连接时SYN为1表示请求建立连接。FIN结束 Finish当FIN为1时表示发送方已经发送完数据并要求释放连接。 3. 如何理解连接 如上图所示实际的连接过程中会有大量的client连接server自然服务器端会有大量的连接操作系统如何管理
先描述再组织 。连接的本质本质是内核的一种数据结构类型建立连接成功时在内存中建立相应的连接对象再对多个连接对象进行某种数据结构组织。 3.1 连接管理机制
连接管理机制主要包括 三次握手和四次挥手、以及客户端与服务端的状态变化。
3.1.1. 三次握手
三次握手的过程可以由下图解释 不妨考虑一下为什么TCP协议规定了三次握手一次、两次、四次…有什么问题吗
我们知道 维护连接是有成本的CPU 内存下面我们分析一次、两次、四次握手的缺陷或缺点最好配合上面三次握手的过程图进行分析对于一次握手 客户端发送连接请求给服务器服务器接受请求并建立连接然后发送确认给客户端。而显然存在一个明显的安全问题无法验证客户端的身份当有一台不断的发送SYN给服务端会不断的在消耗耗服务器的TCP连接资源最后导致服务不可用或严重延迟SYN洪水 对于两次握手 客户端发送连接请求给服务器服务器接受请求并建立连接但服务器不发送确认给客户端。这样客户端无法确认连接是否已经建立成功可能导致客户端误以为连接已经建立成功从而向服务器发送数据而服务器并没有准备好接收数据可能会导致数据丢失或混乱。
而TCP所采用的三次握手的机制较好的解决了这一问题
第一次握手客户端发送连接请求给服务器表明客户端想要建立连接。第二次握手服务器接受连接请求并发送确认给客户端同时表明服务器也愿意建立连接。第三次握手客户端收到服务器的确认并向服务器发送确认表明客户端也愿意建立连接。
显然TCP的三次握手
server端嫁接相同的成本给client端验证了全双工 而在保证了客户端服务器通信功能完善的情况下采用四次握手自然会造成 额外的通信开销四次握手需要额外的一轮通信相比于三次握手会增加一定的通信开销和时间延迟。 复杂性增加四次握手会增加协议的复杂性使得实现和管理起来更加繁琐容易引入错误。 性能影响每增加一轮握手都会增加连接建立的时间延迟可能对某些应用场景的性能产生影响。
且TCP的三次握手在大多数情况下已经被证明是足够可靠和安全的。 3.1.2. 四次挥手
下图为四次挥手的过程
什么是 TIME_WAIT / CLOSE_WAIT
TIME_WAIT和CLOSE_WAIT分别表示连接已关闭但仍在等待一段时间以确保任何延迟数据包到达或确认。 TIME_WAIT: 在TCP连接正常关闭后主动关闭连接的一方通常是客户端会进入TIME_WAIT状态。在TIME_WAIT状态下该端口将等待一段时间通常是2倍的MSL最长生存时间保证历史数据从网络中消散确保在网络中所有的数据包都已经到达目的地而对端已经收到并确认了最后一个ACK。这个等待时间段的主要目的是确保之前的连接中的所有数据包都已经在网络中被处理完毕避免新连接中混入旧连接的数据包。TIME_WAIT状态的连接不能再接收新的数据包但仍然能够发送和接收一些控制报文如RST报文。 CLOSE_WAIT: 当一端关闭连接但另一端仍然发送数据时后者通常是服务器端会进入CLOSE_WAIT状态。CLOSE_WAIT状态表示本地端已经收到远程端发送的FIN报文并关闭了连接但仍然需要等待本地应用程序处理完所有的数据后才能关闭套接字。如果在CLOSE_WAIT状态持续过长时间可能会导致资源泄漏或连接耗尽的问题。 4. TCP实现可靠性的方式
4.1. 发送缓冲区
如何理解TCP的发送缓冲区可以将其看作一个char sendbuffer[NUM] 的数组 所以对于TCP收到了重复报文可以通过序号进行去重。 4.2. 超时重传机制
我们来看下图
对于上面的情况当主机A发送给主机B数据且在特定的时间间隔未收到确认应答网络拥堵、丢包等就会重传数据。
实际上未收到确认应答也可能是由于ACK应答丢失 对于超时重传超时时间应该如何设置
超时时间不能过长、不能过短。超时时间非固定网络状况良好时超时时间短一些网络状况偏差时超时时间长一些。我们可以了解一下TCP是如何设置超时时长的 Linux中(BSD Unix、Windows同), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍如果重发一次后, 仍得不到应答, 等待 2*500ms 后再进行重传如果仍然得不到应答, 等待 4*500ms 进行重传 依次类推以指数形式递增累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接 4.3. 流量控制
流量控制Flow Control用于确保发送端发送的数据不会超过接收端的处理能力从而避免数据丢失、拥塞和重传等问题。
TCP的流量控制机制主要通过 滑动窗口 实现。
接收端会在TCP报文的ACK中通知发送端自己的接收窗口大小即可接收的数据量。发送端根据这个接收窗口大小来控制发送数据的速度确保不会发送超出接收端处理能力的数据量。
首先我们介绍流量控制的相关事项和步骤后面对滑动窗口进行深度了解
接收端 将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;窗口大小字段越大, 说明网络的吞吐量越高;接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后, 就会减慢自己的发送速度;如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端 接收端如何把窗口大小告诉发送端呢?回忆我们的TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息; 另外16位数字最大表示65535, TCP窗口最大就是65535字节吗?实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位。 5. TCP 提高性能的方式
5.1. 滑动窗口
5.1.1. 作用 与 本质
首先通过下图理解滑动窗口的作用将发送缓冲区分成三部分。 根据上面的图引出一个问题滑动窗口在哪里
滑动窗口在发送缓冲区中属于发送缓冲区的一部分
滑动窗口的本质是什么
滑动窗口内的大小意味着发送方可以一次性向对方推送的数据上限滑动窗口也应有上限上限由对端的接受能力决定 5.1.2. 完善理解 - 模型
滑动窗口是什么结构内部结构是什么样的来看下图 滑动窗口滑动的过程 5.1.3. 滑动窗口的部分问题
滑动窗口一定右移吗—— 不一定 如果对端一直不取数据滑动窗口的左侧会一直右移缩小而右侧不会继续移动。 滑动窗口可以为0吗—— 可以 同样的只要对端一直不取数据滑动窗口左端一直右移而右端不动滑动窗口将所有数据发送给对端后就逐渐减小至0 如果没有收到中间报文的应答收到了后面序号的应答有问题吗—— 问题不大 收到了后面的报文应答可以确认前面的也收到了比如主机A给主机B发送了1001~2000的数据而主机B发送的应答是3001根据确认序号的定义可以直接令win_start3001 如果是丢了报文呢 主机A向主机B 发送 1001 ~ 4000而2001~3000的报文丢失了主机B会返回1001的确认应答丢失的部分会暂时存储等待超时重传 滑动窗口一直右移不会越界吗—— 不会 滑动窗口根据序号发送数据前面发送过的数据占用的空间自然就空闲了如何利用好空间显然用一个结构就能解决这一问题TCP的发送缓冲区 / 滑动窗口 是环状的每次只需要执行模运算即可。 滑动窗口解决的是效率问题还是可靠性问题 —— 效率为主可靠性为辅 显然滑动窗口的功能是为了提高传输效率配合着超时重传可以增强可靠性。
5.2. 快重传高速重发机制
我们知道两台主机通信过程中丢失了中间的ACK问题不大只要有后面的确认应答ACK即可但丢失了数据包报文该怎么办呢 上图问快重传的过程触发条件如图中文字表示
那么为什么有了快重传还要有超时重传呢 快重传是有触发条件的两重传方式非对立而是协作上图只是一次报文丢失如果出现了多次报文不同序号的丢失此时滑动窗口可以根据两个重传进行移动。 5.3. 拥塞窗口tmp 通过上面的内容我们知道有了滑动窗口TCP可以效可靠的发送大量的数据. 但是如果在通信开始阶段就发送大量的数据, 仍然可能引发问题. 两台主机在通信时传输效率以及可靠性需要考虑主机自身的各种因素而网络更是不可忽略的一大因素 从宏观角度看网络并非只有我们考虑的两台主机使用网络上有很多的计算机可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据是很有可能使网络状况更加糟糕的。 TCP引入 慢启动 机制, 先发少量的数据探路, 了解了当前的网络拥堵状态后, 再决定按照多大的速度传输数据。 发送开始的时候, 定义拥塞窗口大小为1;每次收到一个ACK应答, 拥塞窗口加1;每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口 拥塞窗口加速机制 , 是指数级别的“慢启动” 指初始时慢增长速度快。为了不增长速度有限制不过快不能使拥塞窗口单纯的加倍引入一个叫 慢启动 的阈值。当拥塞窗口超过此阈值时不再按照指数方式增长, 而按照线性方式增长。 上图非自制
对上图进行解释当TCP开始启动的时候, 慢启动阈值等于窗口最大值。在每次超时重发的时候, 慢启动阈值会变成原来的一半同时拥塞窗口置回1。
少量的丢包, 仅仅触发超时重传大量的丢包我们就认为网络拥塞。 当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降; 拥塞控制, 实际是TCP协议尽量快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案。 5.4. 延迟应答
我们知道滑动窗口的大小与拥塞窗口和对方的接收能力有关如果每次接收端收到报文后就立刻发送ACK此时发送端接收到的窗口就比较小
延迟应答的步骤
假设接收端缓冲区为1M. 一次收到了1000K的数据; 如果立刻应答, 返回的窗口大小就是1000K;但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;如果接收端等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;这种方法可以更好的提升效率减少网络拥塞
延迟方式
窗口越大传输效率就越高我们的目的是在网络不拥堵的前提下提高效率有两种延迟的方式
数量限制每隔N个包就应答一次时间限制超过最大限制时间就应答一次具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms; 5.5. 捎带应答
我们知道TCP是全双工的
双方通信的过程中主机B接收消息的同时也会向主机A发送消息即接收到消息的同时将ACK和待发送的消息捆绑一起发送给主机A。
这个概念是比较好理解的根据文字理解就好。 6. 如何理解 TCP面向字节流
当我们 创建一个TCP的socket 同时会在内核中创建一个 发送缓冲区 和一个 接收缓冲区
当调用write写入数据时时 数据会先写入发送缓冲区中如果发送的字节数太长会被拆分成多个TCP的数据包发出;如果发送的字节数太短, 就会先在缓冲区里等待在缓冲区长度合适 或 其他合适的时机发送出去接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;然后应用程序可以调用read从接收缓冲区拿数据;
由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如
写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;即当把字节数据发送给对方时如何提取数据完全由对端的上层决定发送端不考虑也不在乎即面向字节流可以整段提取或者分段提取应用层的角度看TCP按照报文序号传送数据这些数据被看作字节数据即字节流
6.1. 粘包问题
粘包问题中的 “包” , 是指的应用层的数据包我们知道在TCP的协议头中, 没有像 UDP协议段的 “报文长度” 字段, 但是有“序号”字段传输层的角度TCP传输数据通过一个个报文按照序号排序后放在缓冲区中站在应用层的角度传输的数据只是一串连续的字节数据从上面面向字节流我们知道接收方如何提取数据完全由自己的上层决定应用程序看到的只是一连串的字节数据, 不知道从哪里分段, 是一个完整的应用层数据包比如如果选择接收1000字节的数据提取到的是350350300此时就将一个数据包的内容给截断了。这种情况就叫做粘包问题 那么如何避免粘包问题—— 明确两个包的界限
如果接收端看到的只是一个完整的数据报自然无从下手只要有了明确的包与包的界限就很容易提取数据了 对于定长的包保证每次都按固定大小读取; 例如上图, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;对于变长的包 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置。对于变长的包 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序员自己定的, 只要保证分隔符不和正文冲突即可) 思考UDP会不会出现粘包问题
当然不UDP是面向数据报的UDP一个个将数据交付给应用层有很明显的界限以应用层的角度分析UDP传输要么接收完要么不接收不会出现接收一半的截断状况 6.2. TCP异常情况 状态 进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN. 和正常关闭没有什么区别。 机器重启: 与进程终止的情况相同。 机器掉电 / 网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset。 即使没有写入操作, TCP自身也内置了一个保活定时器会定期询问对方是否还在如果对方不在, 就把连接释放。应用层的某些协议, 也有一些这样的检测机制. 例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接 文章上面都介绍了哪些内容可以看看下面TCP的总结顺便思考自己是否理清了其内容。
7. TCP总结 可靠性
校验和序列号(按序到达)确认应答超时重发连接管理流量控制拥塞控制 提高性能
滑动窗口快速重传延迟应答捎带应答 其他
定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等) 8. 基于TCP的应用层协议
许多 基于TCP的应用层协议 被广泛用于各种网络应用中。这些协议利用TCP的可靠性和连接性来实现各种功能。 HTTP超文本传输协议用于在Web服务器和客户端之间传输超文本文档支持可靠的数据传输和连接性。 SMTP简单邮件传输协议用于在邮件服务器之间或邮件客户端与服务器之间传输电子邮件消息。 POP3邮局协议版本3用于从邮件服务器上获取电子邮件消息。 IMAP互联网消息访问协议类似于POP3也是用于从邮件服务器上获取电子邮件消息但提供了更丰富的功能如在服务器上管理邮件的状态。 FTP文件传输协议用于在客户端和服务器之间传输文件。 Telnet远程终端协议允许用户通过网络连接到远程主机并执行命令。 SSH安全外壳协议用于通过加密的方式在网络上安全地连接到远程主机。 DNS域名系统用于将域名解析为IP地址以便在网络上定位主机和服务。 9. TCP / UDP 对比
首先提出一个问题 我们知道 TCP是可靠连接而TCP是不可靠连接但可靠与否并非直接决定好坏什么时候使用哪一种连接方式是要分情况讨论的 TCP用于可靠传输和顺序交付的情形, 应用于文件传输, 重要状态更新等场景 适用于需要确保数据完整性和可靠性的应用因为TCP提供了重传、排序和确认等机制。对延迟要求不是特别敏感的应用因为TCP的流量控制和拥塞控制机制可能会引入一定的延迟。适用于数据量较大的传输因为TCP的滑动窗口机制可以有效地处理大量数据的传输。 UDP用于对高速传输和实时性要求较高可靠性要求不那么高 的通信领域, 如 音频、视频传输 适用于广播和多播场景因为UDP支持多播和广播传输。适用于短小的数据包传输因为UDP不提供重传和确认机制因此对于大量数据或需要可靠传输的数据不太适用。适用于需要较少的网络开销和较小的头部开销的场景因为UDP的头部开销较小没有TCP的连接建立和维护开销。 10. TCP相关实验 - listen的第二个参数理解
根据以上的内容我们进行一个思考在我们编写TCP编程的相关代码时 使用accept函数时accept会不会参与到三次握手的过程 答 不需要参与先建立好连接后accept直接获取建立好的连接。
根据上面的分析可以得到结论 不需要调用accept就可以建立连接 如果上层来不及调用accept且对端来了大量连接怎么办 提前建立好连接 答服务器本身要维护一个连接队列用于存储客户端的连接请求其中与listen的第二个参数有关。 我们首先通过代码验证上面的结论 “不需要调用accept就可以建立连接”
写一个简单的套接字代码 Sock.hpp
#pragma once#include iostream
#include string
#include cstring
#include cerrno
#include cassert
#include unistd.h
#include memory
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include ctype.husing std::string;class Sock
{
private:// 将listen的第二个参数设为1const static int gbacklog 1; // 监听队列的最大数量
public:Sock() {}~Sock() {}int Socket() // 创建监听socket{int listenSock socket(AF_INET, SOCK_STREAM, 0);if(listenSock 0) // 失败{// logMessage(FATAL, socket error);exit(2);}// logMessage(NORMAL, create socket successfully);return listenSock;}void Bind(int sock, uint16_t port, string ip 0.0.0.0){struct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET;local.sin_port htons(port);// local.sin_addr.s_addr inet_addr(ip.c_str());inet_pton(AF_INET, ip.c_str(), local.sin_addr);if(bind(sock, (struct sockaddr*)local, sizeof(local)) 0){// logMessage(FATAL, bind error | %d : %s, errno, strerror(errno));exit(3);}}void Listen(int sock){if(listen(sock, gbacklog) 0){// logMessage(FATAL, listen error | %d : %s, errno, strerror(errno));exit(4);}// logMessage(NORMAL, init server successfully);}int Accept(int sock, string* ip, uint16_t* port){// 接收连接请求struct sockaddr_in src;socklen_t len sizeof(src);int serviceSock accept(sock, (struct sockaddr*)src, len);if(serviceSock 0){// logMessage(FATAL, accept error | %d : %s, errno, strerror(errno));exit(5);}if(port) *port ntohs(src.sin_port);if(ip) *ip inet_ntoa(src.sin_addr);return serviceSock;}// 连接bool Connect(int sock, const string server_ip, const uint16_t server_port){struct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family AF_INET;server.sin_port htons(server_port);server.sin_addr.s_addr inet_addr(server_ip.c_str());if(connect(sock, (struct sockaddr*)server, sizeof(server)) 0){// logMessage(NORMAL, connect to %s successfully, server_ip.c_str());return true;}elsereturn false;}// 主动关闭连接void Close(int sock){ }
};main.cc
#include Sock.hpp// 不进行accept
int main()
{// 创建一个Socket对象Sock sock;// 创建一个监听Socket并且绑定到8080端口int listenSock sock.Socket();sock.Bind(listenSock, 8080);// 仅创建一个监听状态的套接字// 开始监听sock.Listen(listenSock);while(true){sleep(1);}return 0;
}当开启进程后此时发现进程进入了 监听状态
[usrfolder]:# ./TcpServer[usrfolder]# netstat -nltp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 12889/./TcpServer 此时如果我们 用其他主机对当前主机进行telnet命令连接
[rootiZ0jl4dw0m30ln5nvqiew5Z verifyListenParameters]# netstat -ntp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 1 172.22.99.98:8080 8.130.140.119:23124 ESTABLISHED
tcp 0 1 172.22.99.98:8080 8.130.140.119:56191 ESTABLISHED 如果此时再开一个连接会出现
[rootiZ0jl4dw0m30ln5nvqiew5Z verifyListenParameters]# netstat -ntp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 1 172.22.99.98:8080 8.130.140.119:23124 ESTABLISHED
tcp 0 1 172.22.99.98:8080 8.130.140.119:56191 ESTABLISHED
tcp 0 1 172.22.99.98:8080 8.130.140.119:44191 SYN_RECV 此时第三个连接的状态为SYN_RECV我们知道SYN_RECV表示已经收到连接但暂时不处理。 因为 Linux内核协议栈为一个tcp连接管理使用两个队列 半链接队列用来保存处于SYN_SENT和SYN_RECV状态的请求 全链接队列accpetd队列用来保存处于established状态但是应用层没有调用accept取走的请求
而全连接队列的长度会受到 listen 第二个参数的影响。 全连接队列满了的时候, 就无法继续让当前连接的状态进入 established 状态了。
此时我们就可以解释 listen的第二个参数的意义 底层全连接的长度 listen的第二个参数 1