96SEO 2026-02-19 12:51 7
如上图所示FTPSSHSMTPHTTPFTP等类型的服务器其实就是在一台机器上运行着的不同进程这些是他们的名字。

每一个进程都有一个端口号如上图中的TCP21TCP22等等TCP后面的数字就是端口号当数据从网络中传输到应用层后根据端口号将数据交给对应的进程。
如上图网络通信中的数据1和2是客户端A的两个浏览器页面(两个进程)向服务器发送的数据所以TCP/IP协议中源IP地址都是客户端A的IP地址目标IP地址都是服务器的IP地址源端口号分别是2001和2002表示两个页面的两个进程目标端口号也都是服务端的HTTP进程端口号是80。
数据3是客户端B的一个浏览器页面(一个进程)向服务器发送的数据源IP地址就是客户端B的IP地址目标IP是服务端的IP地址源端口号是这个页面进程的端口号目标端口号是服务器HTTP进程的端口号80。
协议号这些通信协议的编号通过指定的协议号可以指定使用哪个协议。
在之前编写套接字代码的时候代码中指定的端口号是一个uint16_t类型的数据是一个16位的无符号整数它的范围是0~65535也就意味着端口号的范围是0~65535。
0~1023知名端口号如HTTPFTPSSH等这些广为使用的应用层协议他们的端口都是固定的。
1024~65535操作系统动态分配的端口号客户端程序的端口号,
应用层协议本质上就是进程协议规定是通过进程的执行来体现的所以应用层协议
就好比110对应的是报警电话119是火警电话120对应的是急救电话…。
0~1023这些端口号都有对应的协议不能随便更改就像报警电话不会随便更改一样。
我们自己写程序的时候如果使用到端口号要避开0~1023这些知名端口号。
1024~65535这些端口号可以由我们指定去绑定某个进程也可以让操作系统从这些端口号中为某个进程随机分配一个。
在前面写套接字代码的时候服务器上的进程常都是绑定的端口号是7070需要使用bind系统调用去绑定。
客户端的套接字中虽然也需要为进程绑定端口号但是并没有显式绑定(调用bind绑定)而是在调用send时让操作系统自己从1024~65535中随机指定一个端口号绑定。
从端口号到进程必须是唯一的也就是说根据一个端口号只能找到一个进程并且一个进程可以有多个端口号。
但是一个端口号不能绑定多个进程。
netstat指令前面用过好几次就是用来查看当前机器上网络状态的。
学了HTTP和HTTPS协议对协议有很多的理解了前面在Sock套接字编程写了UDP和TCP协议下面站在传输层的角度再看看UDP和TCP协议。
如上图所示就是UDP协议的格式前八个字节属于协议报头部分之后的才是有效载荷。
UDP协议其实就是前八个字节也就是两个int类型的数据其中第一个int类型数据的前16位0~15存放的是源端口号后16位16~31是目的端口号第二个int类型数据的前16位0~15存放的是UDP长度后16位16~31存放的是UDP校验和。
UDP协议是传输层协议是由操作系统维护的而Linux操作系统又是由C语言写的所以UDP协议报头的结构化数据伪代码是
};UDP协议报头本质上就是一个位段结构体如上面代码所示使用位段将两个unsigned
16位UDP长度整个UDP数据段(报文)的长度包括报头和有效载荷UDP数据段最大就是216
64KB。
TCP的报头是没有报文长度的因为TCP是面向字节流的
16位校验和由操作系统进行校验如果发送方和接收方的校验和不一致说明出错就会丢弃整个UDP数据段。
如上图将用户层的数据(字符串)使用sendto发送到UDP套接字中时其本质上就是将用户层buffer中的字符串复制到UDP套接字的内核缓冲区中。
hdr指针指向内核缓冲区最起始位置(UDP报头)start指针指向内核中有效载荷的起始位置。
strcpy(start,buffer,len);//将用户缓冲区中数据复制到内核缓冲区有效载荷处。
(struct
xxx;//赋值源端口操作系统会在内核中使用malloc创建一个内核缓冲区这个缓冲区最大是64KB前8个字节属于UDP的报头也就是struct
udp_hdr类型对象再偏移报头字节数(sizeof(udp_hdr))用start指向有效载荷存储的起始位置。
再将内核缓冲区中的字符串使用strcpy复制到内核中存放有效载荷的位置再将内核缓冲区强转成struct
udp_hdr类型将报头的4部分填充最后将整个缓冲区的数据发送到对端。
UDP的解包和分用比较简单对端收到UDP数据段以后直接将有效载荷拿到并且放入到接收缓冲区中只需要从整个数据段的头部偏移sizeof(struct
分用时将绑定的端口号作为key值在操作系统维护的网络通信进程哈希表中查找到对应的进程将有效载荷交给进程即可。
无连接只需要指定目的IP和目的端口就可以直接进行传输不需要建立连接。
不可靠没有确认机制没有重传机制如果因为网络故障该数据段无法发到对方UDP协议层也不会给应用层返回任何错误信息。
面向数据报不能够灵活控制读写数据的次数和数量。
UDP发送数据的方式就像寄信一样只管发出去至于对方能不能收到完全不关心。
UDP发送数据段时是一次性将内核缓冲区中的数据全部发送出去。
接收端会一次性接收发送端发送的所有数据不能分多次接收。
就像快递你发快递只能一个一个发不能先发半个然后再发半个收快递也是不能先收半个然后再收半个只能一个个完整的快递进行收发。
如上图所示UDP协议没有真正意义上的发送缓冲区当客户端的用户层将数据使用sendto发送的时候操作系统将数据拷贝到上面伪代码中的那个数组中然后将整个数组的数据全部发送。
但是UDP协议存在接收缓冲区服务端的操作系统将客户端发送来的数据存放在接收缓冲区中如上图绿色框所示当接收缓冲区满了或者在适当的时候操作系统会将数据再交给用户层。
因为没有发送缓冲区所以UDP协议发送数据时用户层一调用sendto数据就被发送出去了没有停留。
根据上图虽然没有发送缓冲区但是双方都有接收缓冲区客户端发送数据服务端接收数据的同时并不妨碍服务端也发送数据客户端接收数据。
前面说过UDP数据段最长不能超过64KB所以如果传输的数据超过了64KB就需要在应用层手动的分包将数据分成多个64KB大小的数据包然后多次发送并且在接收端手动拼装。
这些协议在传输层都是使用的UDP协议。
当然也包括我们自己写UDP程序时自定义的应用层协议。
如上图所示TCP协议报头的长度固定20个字节不包括选项和有效载荷这里也不会讲解选项部分有兴趣可以自行了解。
TCP协议报头同样也是一个结构化数据和UDP一样只是结构体中的成员变量大小和类型不同而已。
16位源端口号发送方进程的端口号。
16位目的端口号接收方进程的端口号。
32位序号/32位确认号:
表示该TCP头部有多少个32位bit有多少个4字节)也就是一个unsigned
TCP中的4位首部长度包含报头和选项报头部分固定20字节。
头部长度
设4位首部长度存的数字为X报头固定为20字节如果没有选项长度此时X
URG(URGent)紧急紧急指针是否有效。
下篇讲ACK(ACKnowledgment)确认确认号是否有效。
PSH(PuSH)推送提示接收端应用程序立即从TCP缓冲区把数据拿走。
下篇讲RST(ReSeT)复位对端要求重新建立连接携带RST标识的数据段称为复位报文段。
SYN(SYNchronization)
同步请求建立连接把携带SYN标识的数据段称为同步报文段。
FIN:FINish终止通知对端本段关闭了称携带FIN标识的为结束报文段。
16位窗口大小表示滑动窗口的大小后面详细讲解。
16位校验和发送端填充CRC校验.
则认为数据有问题此处的检验和不仅包含TCP首部也包含TCP数据部分。
16位紧急指针标识哪部分数据是紧急数据(带外数据)。
如上图所示TCP协议中通信双方都既有发送缓冲区又有接收缓冲区用户层在send数据之后操作系统会自动拼接TCP报头形成数据段然后放入到发送缓冲区中。
对端收到数据段后先放入接收缓冲区中等待用户层读取数据通信双方既可以发送数据也可以接收数据而且互不影响同样是全双工的方式。
至于什么时候将数据段从发送缓冲区发出去什么时候将数据从接收缓冲区交给应用层。
所以说TCP是传输控制协议它完全有自主决定权而且是面向字节流的发送数据接收数据以字节为单位在发送接收过程中不考虑是不是一个完整的数据段。
TCP不像UDP那样一次必须发送或者接收一个完整的数据段而是以字节为单位的发送或接收一个完整的数据段需要很多个字节。
这些流动的字节像河流一样所以说TCP是面向字节流的。
TCP的报头没有报文的长度。
在对端收到TCP数据段以后根据数据段中的TCP首部长度得到整个报头长度偏移sizeof(struct
tcp_hdr)后得到有效载荷的首地址直到下一个TCP数据段的起始全部都是有效载荷将这部有效载荷提取到接收缓冲区中就完成了解包。
那么又是怎么完成分用的呢也就是说该如何找到曾经bind端口号的特定进程呢
如上图所示应用层协议本质上就是一个一个的进程每一个进程都有pid网络协议还有一个port操作系统将网络通信进程的PCB用一个哈希表来维护。
键值对的形式存放在哈希表中其中port是key值pid是value。
在操作系统完成数据段的解包以后会从这个哈希表中根据TCP协议中的目的port查找对应pid值的PCB这个PCB维护的文件描述符表中有一个fd指向的是一个套接字如上图所示的3。
file结构体该结构体中的读写缓冲区其实就是TCP协议的接收和发送缓冲区。
file的接收缓冲区中如上图带箭头红色线条所示。
此时就完成了分用应用层像读取文件内容一样从fd
像平常通信或者说话一样不可靠性很大的原因就是因为距离变长了。
容易丢包所以操作系统单机内部不谈协议一旦涉及网络就要谈协议了
保证TCP可靠性的机制有很多保证可靠性的同时也会考虑效率下面一个个看看。
udp[N]本质上就是一个char*类型的数组每个元素大小是一字节TCP数据段就放在这个数组中包括报头选项以及有效载荷所以数据段中的每个数据都有一个编号就是数组的下标。
如上图所示主机A和主机B使用TCP协议进行通信主机A发送数据(1~1000)主机B收到后回复一个应答信号其值是1001。
上面通信过程中发送的数据编号范围是1~1000所以主机A在发送数据的时候将数字1000填入到报头中的32位序号中。
主机B在收到数据段以后将1001作为确认序号填入到报头中的32位确认序号中然后发送给主机A此时发送的数据段中没有有效载荷只有报头
确认序号中的1001表示主机B已经收到编号1001之前的所有数据。
主机A接收到确认应答后下次发送数据就从编号位1001处开始。
主机A收到确认应答信号后就可以保证刚刚发送的数据主机B收到了并且下次发送从确认序号处开始发送即可。
那么主机A在收到主机B的应答信号后如何确定这就是一个确认应答信号呢它有没有可能是一个正常的数据段但是并没有有效载荷呢
主机B在给主机A发送应答信号之前除了会填充32位的确认序号外还会将6个标志位的AKC置1。
当主机A收到确认应答信号后发现ACK的状态是1就知道这是一个确认应答信号而不是一个普通的数据段然后再区查看确认序号中的值来判断刚刚发送的数据主机B是否完全收到了。
从前面主机A和主机B的通信示意图可以看到在主机B给主机A发送了确认应答信号后主机A没有任何表示但是此时主机B就不知道它发送的应答信号主机A到底有没有收到。
确认应答机制只保证历史数据的可靠性最新数据(确认应答)无法保证可靠性。
当主机A给主机B发送数据后主机B收到了并且也有数据给主机A发送就可以将数据和确认应答信号作为一个数据段发送给主机A如上图所示。
此时除了要将报头中的确认序号ACK标志位填充外还要将有效载荷拼接数据段中。
当主机A收到数据段以后除了发现它是一个应答信号确定对方收到了字节刚刚发送的数据又发现它同样有有效载荷此时的有效载荷就是主机B发送过了的数据。
为什么要有两组序号呢一个是32位序号一个是32位确认序号只用一个不行吗
答案是为了实现全双工像上面讲的一样主机B给主机A发送的数据段中不仅起确认应答作用还有数据如果此时只有一个组序号主机A接收到数据段后就不能知道这个序号是确认序号还是数据的编号。
使用两组序号就将确认应答和数据的序号分离开了互不影响通信双方可以实现全双工通信。
如上图所示如果主机A给主机B发送数据之后由于网络拥堵等原因发生了丢包导致数据无法到底主机B所以主机B也不会给主机A一个应答信号。
如果主机A在一个特定时间间隔内没有收到B发来的确认应答就会进行重发这就是超时重传机制。
也可能是因为ACK丢失了主机B其实是收到了数据的但是此时主机A认为主机B没有收到所以就会触发超时重传机制主机A会再次重新发送刚刚数据。
因此主机B会收到很多重复数据所以TCP协议需要能够识别出哪些包是重复的包并且把重复的丢弃掉。
利用前面提到的序列号就可以很容易做到去重的效果这一切都是操作系统在做。
“确认应答一定能在这个时间内返回”但是这个时间的长短随着网络环境的不同是有差异的。
如果超时时间设的太长会影响整体的重传效率如果超时时间设的太短,
TCP为了保证无论在任何环境下都能比较高性能的通信因此会动态计算这个最大超时时间。
Unix和Windows也是如此)超时以500ms为一个单位进行控制每次判定超时重发的超时时间都是500ms的整数倍。
再进行重传。
以此类推以指数形式递增累计到一定的重传次数TCP认为网络或者对端主机出现异常强制关闭连接。
上图所示就是使用TCP协议通信的整个流程。
客户端使用socket创建套接字服务端使用socket创建套接字bind了端口号并且listen了套接字设置为监听状态。
第一次握手客户端给服务端发送数据段没有数据只有是将报头中的SYN同步标志位置一然后将只有报头的数据段发送给服务端表示请求建立连接。
当服务端收到客户端这个请求报文后就知道三次握手开始了。
第二次握手服务端在收到客户端的连接请求后返回应答信号和连接请求表示同意并且建立连接。
此时的数据段中同样没有数据只有报头并且六个标志位中的ACK确认和SYN被置一其中ACK是作为客户端发起连接请求的确认应答信号SYN是服务端向客户端发起的连接请求。
第三次握手客户端收到服务器端的数据段后将套接字的状态设为ESTABLISHED建立链接established确定的正式建立连接并且再给服务端一个ACK信号。
在客户端收到服务器的数据段后根据ACK标志位知道了服务器收到了自己的连接请求再根据SYN标志位知道了服务器向自己也发起了连接请求那么自己就可以正式建立连接了然后再给服务端一个应答信号让服务端也建立连接。
服务端收到这个最后应答信号后便知道客户端已经建立好连接了自己也将套接字设置为ESTABLISHED状态表示连接建立。
当客户端使用了connect系统调用后三次挥手就被发起了。
当服务端使用了accept系统调用后三次挥手的请求就被接收到了并且开始进行后续动作。
三次挥手的过程完全由操作系统来维护。
此时服务端和客户端双方的套接字都是ESTABLISHED状态双方正式连接连接。
根据三次挥手示意图中可以看到双方建立连接的过程中客户端比服务端先建立连接。
双方建立连接存在一个时间差申请者先建立。
当客户端发出带有SYN同步标志位的数据端后服务端收到后便建立连接将套接字设置为ESTABLISHED建立链接状态。
如果此时客户端发送了连接请求并且自己没有建立连接自己的系统中并没有建立用来连接套接字那么服务端就相当于维护着一个完全没有用处的套接字白白占用系统资源。
如果此时的客户端是一个不法份子他就可以频繁的向服务端发起三次握手请求fas并且自己不建立连接只让服务端建立连接那么服务端的系统资源很快就被占用完服务端也就奔溃了。
如上图所示客户端向服务端发起三次握手请求服务端收到后建立了连接并且给客户端发送了ACK应答信号和SYN连接请求让客户端也建立连接。
如果客户端同样频繁发起三次握手请求并且忽略服务端返回的数据端那么同样会发送SYN洪水。
当客户端发起三次握手请求后服务端收到以后并没有第一时间建立连接而是给客户端确认应答并且也向客户端发起连接请求。
客户端在收到服务端的数据段后需要先建立连接然后再向服务端发送确认应答表示自己已经建立好了连接。
服务端在收到应答信号后知道客户端已经建立了连接自己再建立连接。
三次握手能够保证服务端在连接连接的时候客户端已经建立好了连接。
所以如果是单一的客户端发起SYN洪水那么客户端需要和服务端付出同等的代价都需要建立连接。
一般情况下服务端的配置比客户端会高所以客户端耗不过服务端自己就先奔溃了。
如果有多台客户端向同一台服务端发起SYN洪水攻击那么服务端也是扛不住的但是这种安全问题就不是TCP协议该管的事情TCP仅能做的事情就是让自己的机制没有漏洞。
在三次握手的过程中客户端证明了自己有发送和接收数据的能力服务端也证明了自己有发送和接收数据的能力。
三次握手可以用最小的成本验证客户端和服务端之间的全双工通信信道是通畅的。
如果是四次挥手或者其它偶数次握手那么服务端就可能付出比客户端更多的代价。
再者更多次的握手根本就没有必要去用因为三次握手能解决问题就没有必要用更多次的握手否则会导致效率的降低、
而且三次握手不一定非得成功最担心的其实是最后一个ACK丢失但是有配套的解决方案就是超时重传机制。
三次握手最重要的是建立了连接结构体这些结构体同样被操作系统采用先描述再组织的方式管理了起来。
当客户端使用close系统调用后就向服务端发起了四次挥手请求四次挥手的作用是断开连接。
第一次挥手客户端向服务端发送数据段同样没有数据只是将FINfinish结束标志位置一表示要断开连接。
第二次挥手服务端收到客户端断开连接的请求后将自己的套接字设置为CLOSE_WAIT状态然后给客户端发送确认应答信号。
第三次挥手服务器的套接字处于CLOSE_WAIT状态时同样要向客户端发送断开连接的请求所以向客户端发送带有FIN信号的数据段。
通信是双方的事不能客户端说断开连接就断开连接只有双方都发起过断开连接的请求FIN连接才能断开。
第四次挥手客户端收到服务端发来的FIN数据后知道了服务端也要断开连接此时将自己的套接字设置为TIME_WAIT状态然后给服务端发送确认应答。
客户端收到服务端发来的断开连接请求后并没有第一时间断开而是先将状态设置为TIME_CLOSE状态等待一段时间后再断开连接。
服务端收到客户端的第四次挥手应答信号后知道了客户端已经收到了自己断开连接的请求所以就将自己的套接字关闭了。
被动断开连接的一方两次挥手完成后会进入CLOSE_WAIT状态。
为什么客户端在发起四次挥手请求到完成后并没有第一时间断开连接而是等待一段时间呢
这是为了保证最后一个ACK尽可能的被对方收到如果对应没有收到会触发超时重传机制此时未完全关闭的套接字还可以重写发送ACK信号。
双方在断开连接的时候网络中还有滞留的报文要保证滞留报文进行撤销。
MSL这段时间能够保证服务端触发一次超时重传并且客户端做出一次应答正好是两次传送数据的时间。
前面写套接字代码的时候服务端绑定了一个端口后进行网络通信然后使用ctrl
然后再运行也就是重启服务端绑定相同的端口但是此时就有可能会出现bind
服务端进程结束服务端主动断开连接它会有一段时间处于TIME_WAIT状态套接字没有完全关闭。
再次运行时无法绑定还没有关闭的套接字。
这样其实也存在很大危害比如在双十一当天淘宝服务器突然奔溃了需要立刻重启但是需要等待两个MSL时间(大概4分钟)在这段时间内已经和服务器建立连接的客户端就会因为超时而断开连接那么造成的损失是不可估量的。
setsockopt是一个系统调用函数用于设置套接字选项。
它可以在套接字创建后但在连接之前或之后设置选项。
使用系统调用setsockopt()设置socket描述符的选项SO_REUSEADDR为1此时就可以立刻重新绑定之前的端口号了。
这是在SOL_SOCKET层打开一个SO_REUSEADDR选项后面的选项可以不加设置地址复用功能允许重用本地地址的意思
c结束服务端进程后可以立刻重新启动并且绑定之前绑定过的端口号。
在前面写的使用TCP协议的HTTP应用程序中服务端不执行close也就是说即使客户端断开连接服务端也不会关闭套接字。
在TCP协议中对应着客户端发起四次挥手请求服务端给确认应答信号。
但是服务没有发出FIN信号没有请求断开连接也就是从第三次和第四次挥手没有了。
根据上面通信流程示意图此时服务端的套接字就会维持在CLOSE_WAIT状态。
当一段发起四次挥手断开连接后另一端必须也要发起FIN断开连接请求也就是调用close系统调用否则无法断开连接就会存在CLOSE_WAIT状态的套接字占用系统资源。
此篇讲完了端口号和UDP但重点在TCP的三次握手和四次握手下一篇还会讲讲TCP的其它机制。
下一篇8(传输层)TCP协议_续(流量控制滑动窗口拥塞控制紧急指针listen第二个参数。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback