数据总在两端进行,一个客户端,一个服务器端
连接建立起来,数据双向流动,这叫双工,你可以发给我我也可以发给你
既然服务器端是被动的接受的,那么客户端必须得知道服务器的地址
我浏览器要访问的淘宝网,我需要知道淘宝网的服务器地址(IP 192.168.88.155); 以及淘宝服务器的姓名(端口号 unsigned short 范围0-65535)
只需要服务器的ip 端口,客户端不必要
OSI七层网络模型:物链网传会表应(国际规定)
tcp ip 应传网链
TCP、IP 四层协议
应用层 网络层 传输层 数据链路
TCP / IP :协议比喻:
把人看成要发送的数据包:人出门上街,我们把外面的街道看成网络,我们人出门上街,就等于把数据包发送到互联网上去
人 ------》数据包
街道------》互联网
人上街道---》数据包发送到互联网上
人不能光腚上街,人要先穿内衣内裤(TCP一层);讨一个衬衣衬裤(IP),还得套外衣外套(以太网帧),可以出门了
TCP --内衣
IP--衬衣
以太网帧--外套
要发送 abc 这三个字母出去网络
加个tcp头
加个ip头
加个以太网帧头 /尾
这样满足条件才可以发送
服务器端:
192.168.1.100【IPv4】
服务器关闭通信描述符,此时客户端read函数返回失败,退出while 同时也关闭通信文件描述符
TCP udp的区别:
TCP传输控制协议;流式传输,大品牌内衣内裤,有售后
udp 报式传输协议 小品牌,无售后
TCP 可靠的面向链接的协议:数据包丢失的话操作系统底层会感知并且帮助你重新发送数据包;
UDP:不可靠无连接的协议;
TCP可靠必须耗费更多的系统资源确保数据传输,得到好处就是可靠 、、不断线,传输给对方的数据,一定正确的,不丢失,不重复,按顺序到达对端
Udp:不可靠协议:发送速度特别快,无法保证数据可靠性
TCP :TCP三次握手详解,telnet,wireshark示范
最大传输单元MTU :
例子
好的,用通俗的例子来解释MTU就像是在解释邮包的大小限制。想象一下,你要寄一个包裹,那么:
1. **MTU是什么**:
- 就像邮局规定一个包裹的最大尺寸一样,MTU规定了网络上单个数据包的最大尺寸。例如,以太网的MTU通常是1500字节,这就像邮局说你寄的包裹最长不能超过1.5米。
2. **为什么重要**:
- 如果你的包裹太大,邮局会要求你把它分成多个小包裹。同样,如果数据包超过了MTU,网络设备会把它分成更小的包进行传输。这种分包会增加传输时间和处理开销,就像寄多个小包裹比寄一个大包裹更费事。
3. **设置和调整**:
- 假设你经常寄包裹,你会发现某个尺寸的包裹(比如1米长的包裹)是最经济和高效的,这个尺寸就是你选择的“最佳MTU”。网络管理员会根据网络环境调整MTU,以确保传输效率最高。
4. **分段和重组**:
- 如果你寄的包裹超过了邮局的最大尺寸限制,邮局会要求你把包裹分成多个部分,这就像网络把大的数据包分段传输。分段的每一部分都要独立处理和传输,然后在收件人处重新组装成一个完整的包裹。
5. **常见问题**:
- 想象一下,如果邮局没有告诉你包裹超大,而是直接退回或丢失包裹,你会很困惑。这就像在网络中,如果没有正确处理超过MTU的数据包,可能会导致数据丢失或传输失败。
通过这个邮包的例子,可以更容易理解MTU的概念和重要性。确保合适的MTU就像确保包裹的尺寸适合邮局的规定,可以让传输过程更加顺畅和高效。
每个数据包包含的数据最多可以有多少个字节1.5k左右
你要发送100k数据包,操作系统会把你这个100k拆分成若干个数据包,每个数据包大概在1.5k之内大概拆解成68个包;对端重组;
我们只需要有拆包,组包;
这68个包各自的传送路径不同,可能由于路由器,交换机,原因再次分片;
TCP包头结构:红色的就是,20个字节
源端口,目标端口
关注syn位,ack位
TCP包,可能没有数据,设置标志位来控制
、、回忆日志
1 打开日志文件 2多次反复写信息 3关闭日志文件
------------------
TCP数据包的收发也分三大步骤
1建立TCP连接--> 三次握手 保证数据可靠传输
2多次发送接收数据包
3关闭TCP连接
客户端给服务器发送了一个SYN标志位置位的无包体TCP数据包,SYN被置位,就表示发起TCP链接,协议就这么定
服务器收到了这个SYN标志位置位的数据包,服务器给客户端返回一个SYN和ACK标志位都被置位的无包体TCP数据包,协议就这么定的;
客户端收到服务器发送回来的教据包之后,再次发送ACK置位的教据包,服务器端收到这个数据包之后,客户端和服务器端的TCP链接就正式建立!
//后续就可以进行数据收发了
三次握手很大程度上是为了防止恶意的人坑害别人而引入的一种ICP连接验证机制;
//问题来了 为啥是三次握手,不是两次握手
原因很多都是为了保证数据稳定可靠的收发
例子:
客户端发送syn--------->
<------------服务器回复并验证ip端口是否正确
ack---------->客户端能验证说明就是可靠传输;
如果只有两次握手,那么不能确定是不是有问题就连接了
telnet工具
一款命令行运行的的客户端TCP通讯工具,可以连接到服务器端,往服务器端发送数据,也可以接受从服务器发送过来的信息;类似nginx_client.c
该工具能够方便的测试服务器端的TCP端口是否通,是否能正常收发数据,所以是一个非常实用的重要的工具
telnet 192.168.88.130 8989 //ip地址加端口
wireshark 监控数据包
是个软件:分析网络包
TCP状态转换
服务器绑定只能绑定一次
使用netstat查询网络相关信息
-a //表示显示所有
-n //显示成数字的内容全部显示为数字
-p//显示段落对应的程序名
netstat -apn | grep 9000
首先是服务器正常打开,然后客户端链接并且关闭客户端,此时服务器端状态变为TIME_WAIT状态,那么在这个状态下,重启服务器会报错地址已经被使用,bind()失败是为什么?
只要客户端连接到服务器,并且服务器把客户端关闭。那么服务器就会产生一条对9000监听端口的状态为TIME_WAIT的连接;
在TCP/IP协议中,当一个连接关闭后,参与连接的任一端(通常是主动关闭的一端)会进入`TIME_WAIT`状态。这是为了确保所有在连接关闭前可能还在网络中传输的分段都被正确接收并丢弃。这段时间通常为2倍的最大段寿命(Maximum Segment Lifetime, MSL),一般为4分钟。
### `TIME_WAIT`状态的作用
1. **确保延迟的分段被丢弃**:在连接关闭后,网络中可能还存在一些延迟到达的分段。`TIME_WAIT`状态确保这些分段在旧连接完全终止前不会被错误地应用到新的连接上。
2. **确保被动关闭一端收到最终的ACK**:在`FIN`/`ACK`握手过程中,`TIME_WAIT`状态确保被动关闭一端能确认收到最终的ACK。
### 服务器重启时`bind()`失败的原因
当服务器在`TIME_WAIT`状态下重启,并尝试重新绑定到相同的IP地址和端口时,会遇到`bind()`失败并报错“地址已经被使用(Address already in use)”。
这是因为在`TIME_WAIT`状态下,操作系统还在维护该端口的状态,防止在短时间内重新使用同一地址和端口,以避免网络中可能存在的旧数据包与新的连接发生冲突。
### 解决方法
1. **等待`TIME_WAIT`状态结束**:在重新启动服务器前,等待`TIME_WAIT`状态结束(通常是4分钟)。
2. **使用`SO_REUSEADDR`套接字选项**:设置`SO_REUSEADDR`套接字选项,允许在`TIME_WAIT`状态下重新绑定端口。这样可以强制操作系统允许端口重新绑定,但仍需小心处理以避免潜在的旧数据包冲突。
以下是设置`SO_REUSEADDR`选项的示例代码(以C语言为例):
//端口复用技术
int server_socket;
int opt = 1;
// 创建套接字
server_socket = socket(AF_INET, SOCK_STREAM, 0);
// 设置SO_REUSEADDR选项
if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
// 绑定套接字
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_socket, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
总结
服务器重启时,因先前连接处于
TIME_WAIT
状态,导致bind()
失败的原因是操作系统在维护该端口的状态。使用SO_REUSEADDR
选项可以解决这个问题,但需要注意可能的旧数据包冲突问题。
三次握手: 首先客户端发起连接向服务器端发送SYN=1置位的数据包,此时客户端变成SYNSENT,然后服务器端收到并且回复ACK,SYN=1,此时服务器变为SYNRCVD,然后客户端收到ACK,并且也发送ACK过去自己变成ENSHTABLED,服务器端收到ACK也变成ENSTABLED 此时三次握手成功,连接建立,双向通信
四次挥手:要点 2MSL 加设服务器关闭连接,那么服务器会给客户端发送FIN ACK的包。此时服务器状态变为FINWAIT1 ,然后客户端收到包,会返回给服务器一个ACK此时客户端 变成CLOSEWAIT,服务器收到ACK变成FINWAIT2,然后此时客户端向服务器发送一个FIN ACK包,这时客户端变成了LASTACK,服务器收到FIN之后回复ACK之后变成TIME_WAIT ,此时客户端收到回复的ACK,变成了CLOSE;
C++ 解释一下这个图 前三行是三次握手建立连接 中间第四行是发送的长度为22的数据包 最后四行是四次挥手的断开链接的数据包
TIME_WAIT:
2msl 一般是4分钟数据包的生命周期
最后一个ACK包没收到,还可以再发送,有了TIME_WAIT更好的去发送
优点:
可靠的实现TCP全双工的终止
如果服务器最后发送的ACK应答丢失,那么客户端一定会重新发送FIN,这样因为有TIME_WAIT的存在,才会重新发送ACK给客户端。
如果没有TIME_WAIT状态,那么客户端重新发FIN的时候服务器端回复的不是ACK包而是RST包客户端就会保错
当关闭连接时,如果我们这个发送缓冲区有数据,那么操作系统会很优雅的把发送缓冲区的数据发送完毕之后才会发FIN包表示连接关闭;
RST:包一般表示异常关闭,如果发生异常,一般都会导致丢失数据包;
端口复用
C++ int listen(int sockfd,int backlog)
backlog : 监听套接字队列 对于一个调用监听的套接字,系统会维护给这个套接字两个队列
- 未完成连接队列 //当客户端发生三次握手的第一次syn包给服务器的时候,服务器就会再未完成队列中创建一个跟这个syn包对应的一项 //可以看成一个半连接,这个半连接的状态从LISTEN变为SYN_RCVD状态,同时给客户端返回第二次握手 SYN,ACK包 //这个时候服务器端是在等待完成的第三次握手
2.已完成连接队列 //当第三次握手完成后,,这个连接就变成了ESTABLISHED,每个已经完成三次握手的客户端,都放在这个队列中作为一项(从未完成连接拿走,不是拷贝,是移动)
曾经的backlog:已完成队列和未完成队列的总值不超过backlog
(1)问题: 客户端调用connect()的函数的时候什么时候返回? 收到三次握手的第二次握手包,也就是服务器端返回的 syn ack 包的时候,这时候半连接建立了返回,经过了客户端的一个RTT
(2) RTT是两端的半连接的时间
![image.png](https://flowus.cn/preview/d6a5019a-8aa9-4422-8fe6-feb77a4d28d9)
**如果速度快,三次握手187ms最快**
(3)如果一个恶意客户,迟迟不发送三次握手的第三个包,那么这个连接就建立不起来,那么处于SYN_RCVD的这一项(未完成队列中),就会一直停留未完成队列中,停留大概75s,超过时间干掉;
### accept函数
C++ accept()函数就使用从已完成连接队列中的队首(队头)取出来一项(已经完成三次握手的TCP连接)返回给进程
如果已完成队列里是空呢,那么accept函数会阻塞等待,一直等到已完成连接队列有一项才会被唤醒
所以从编程的角度,我们要尽快的用accept()把已完成队列中的取走
accept返回的是一个套接字,这个套接字代表那个已经用三次握手建立起来的TCP连接 也就是通信文件描述符cfd。因为accept是从已完成队列取的数据; 服务器程序,必须严格区分两个套接字: 监听lfd,服务器程序存在,lfd也就一直存在; 当客户端连接的进来,操作系统会为每个建立三次握手的客户端再创建一个套接字(cfd);通信套接字,从已完成连接的队列取出来的
```
思考题:
(1.)如果已完成未完成队列的和达到了backlog,满了,此时再有客户端发送SYN包请求,服务器什么反应?
实际上服务器会忽略SYN包,不予回应,客户端这边发现没有回应,过一会会重新发送SYN包
(2.)从连接被扔到已完成连接队列的时候,到accept从已完成的队列取出的时候有一个时间差,就是刚来,但是还没被取出,这时候客户端发来数据怎么办
这个数据会保存在已经连接的套接字对应的接收缓冲区里
SYN攻击
匿名地址连续向服务器发送syn包,然后,服务器端并没有发送回ack,也没有收到ack,导致未连接的队列会满掉大于backlog之和,进而导致真正的syn包没有收到
然后backlog又规定了改掉了,是已连接的队列的总值;
尽快需要把已完成的队列里面的连接取走,尽快留出空闲为止给后续已完成三次握手的条目用,那么这个已完成队列一般不会满 backlog300 500左右
阻塞非阻塞IO
阻塞iO:
非阻塞IO:
非阻塞一直读取,如果没有数据会返回EWOULDBLOCK也可能是EAGAIN.不停的调用accept,recvfrom来检查数据是否到来,非常累;