TCP三次握手
-
客户端发送给服务端
客户端确认Client的发有效。服务端确认Client的发有效,Server的收有效
-
服务端发送给客户端
客户端确认Client的发、收有效,Server的发、收有效。服务端确认Client的发有效,Server的发、收有效
-
客户端发送给服务端
客户端确认Client的发、收有效,Server的发、收有效。服务端确认Client的发、收有效,Server的发、收有效
三次握手之后,客户端和服务器都能确定自己和对方的收、发都没有问题。
为什么要进行这么多次连接,是因为需要确定彼此是否可以接收也可以发送。后面的seq以及ack分别是序号以及确认号,是要确保数据的完整性以及顺序性。
对于后面带着的确认号以及序号的规则:
客户端以及服务端的初始值不一样,具体看分配。
当客户端发送第一次消息过去后,假设cseq为1000,那么这个时候服务端的sack的应答,要在这个cseq上进行加1,也就是1001(表示服务端已经接收到1个字节,并且希望下次收到的数据是从第1001个字节开始的)并且也要发送自己的请求连接标志位,这个时候就发送我们服务端的初始值。
当客户端接收到服务端的初始值以及连接信号时,我们会在服务端的初始值上+1,并且付上应答信号返回。
接下来就进入了通信,还是继续客户端的发送消息,这个时候由于我们没有接收到服务端所传送过来的消息,所以我们保留了上一次的cack,并且使用了sack的序号1001,并且规定了发100个字节的内容,真正的内容是1101。
之后服务端为了做好应答工作,返回sack为1101。
之后客户端继续发,那么我们就需要在1101后面继续往后发,所以需要发送1101(200),实则为1301,cack继续保持不变,那么服务端就需要返回1301,这样子就可以确保数据的完整性以及顺序性了。
只有收到SYN和FIN信号,确认信号sack才会+1。其余都是加上具体的数据位。
总结TCP的三次握手:
第一次握手:
-
客户端将SYN标志置为1。
-
生成一个随机的32位的序号seq=J,这个序号后面是可以携带数据(数据的大小)。
第二次握手:
-
服务器端接收客户端的连接:ACK置为1。
-
服务器会回发一个确认序号:ack=客户端的序号+数据长度(字节)+SYN/FIN(按一个字节算)
-
服务器端会向客户端发起连接请求:SYN = 1.
-
服务器会生成一个随机序号:seq = K
第三次握手:
-
客户端应答服务器的连接请求:ACK = 1。
-
客户端回复收到了服务器端的数据:ack = 服务端的序号 + 数据长度 + SYN/FIN(按一个字节算)
TCP四次挥手
a想和b断开连接,但吧不一定想,b可能想传完某些数据再断开连接,所以第2、3次挥手都是b发起的。2、3次挥手之间可能割了很长一段时间。可以理解成,b也要考虑一下再作出回答。
注意到最后的标志位TIME_WAIT,这里写着要经过两倍的报文段寿命,有个名词叫2MSL(Maximum Segment Lifetime),主动断开连接的一方,最后进入一次TIME_WAIT状态,这个状态会持续2MSL。
MSL:官方建议是2分钟,实际是30s,主要是保证TCP的安全性以及可靠性,保证对方可以在这个时间段内收到ACK的回应。
当 TCP 连接主动关闭方接收到被动关闭方发送的 FIN 和最终的 ACK 后,连接的主动关闭方必须处于TIME_WAIT 状态并持续 2MSL 时间。
这样就能够让 TCP 连接的主动关闭方在它发送的 ACK 丢失的情况下重新发送最终的 ACK。
主动关闭方重新发送的最终 ACK 并不是因为被动关闭方重传了 ACK(它们并不消耗序列号,被动关闭方也不会重传),而是因为被动关闭方重传了它的 FIN。事实上,被动关闭方总是重传 FIN 直到它收到一个最终的 ACK。
为什么TIME_WAIT等待的时间是2MSL?
-
确保b能接收到ACK信号
半关闭、端口复用
四次挥手的2、3次挥手之间就是半关闭状态。
当 TCP 链接中 A 向 B 发送 FIN 请求关闭,另一端 B 回应 ACK 之后(A 端进入 FIN_WAIT_2状态),并没有立即发送 FIN 给 A,A 方处于半连接状态(半开关),此时 A 可以接收 B 发送的数据,但是 A 已经不能再向 B 发送数据。
TCP通信并发
多进程实现并发服务器
对于一个终端聊天功能,它只能处理一个TCP会话,所以需要多线程以及多进程来处理并发的问题,主要的思路可以这么处理:
一个父进程,多个子进程。
-
父进程负责等待并接收客户端的连接。
-
子进程:完成通信,接受一个客户端连接,就创建一个子进程用于通信。
注意:
发送数据时,一定要:
1、recvBuf[1024] = {0};
或者2、write(fd,sendBuf,strlen(sendBuf)+1);
不然sprintf会乱码,最后执行2.把recvBuff中的“\n”也一起发送出去。
char recvBuf[1024] = {0}; //接收
char sendBuf[1024] = {0}; //发送
int i = 0;
int len = 0;
// 3.通信
while(1)
{
sprintf(sendBuf,"data:%d\n",i++);
//给服务端发送数据
write(fd,sendBuf,strlen(sendBuf));
//读取服务器端的数据
len = read(fd,recvBuf,sizeof(recvBuf));
if(len == -1)
{
perror("read");
exit(-1);
}else if(len > 0)
{
printf("recv server data: %s\n",recvBuf);
}else if(len == 0)
{
printf("server closed...\n");
break;
}
sleep(1);
}