文章目录
计算机网络基础知识与常见题目。
OSI七层模型
应用层 |
表示层 } 数据
会话层 |
传输层:分段
网络层:分组数据报
数据链路层:帧
物理层:比特流
TCP/IP四层模型
应用层
运输层
网际层
网络接口层
五层
应用层:http、ftp、smtp、telnet
传输层:tcp、udp
网络层:IP
数据链路层:
物理层:IEEE802.2、Ethernet v.2
|--------------------------------------------------------------------------------------|----|-----------------
| Version 4bits | IHL 4bits | 区分服务8位 | 总长度16位 | | 首部
|--------------------------------------------------------------------------------------| |
| 标识 | 标志2位 | 片偏移 | 固定部分
|--------------------------------------------------------------------------------------| 20个字节
| 生存时间TTL | 上层协议 | 首部校验和 | |
|--------------------------------------------------------------------------------------| |
| 32位源IP地址 | |
|--------------------------------------------------------------------------------------| |
| 32位目的IP地址 | |
|--------------------------------------------------------------------------------------|----|
| 可选字段(长度可变) | 填充 |
|--------------------------------------------------------------------------------------|-----------------------
| 数据部分 |
|--------------------------------------------------------------------------------------|
|-------------------------------------------------------------------------------------|
| 16位源端口号 | 16位目的端口号 |
|-------------------------------------------------------------------------------------|
| 序列号 |
|-------------------------------------------------------------------------------------|
| 确认号(acknowledgement number) |
|-------------------------------------------------------------------------------------|
| 4位头部长度|6位保留| 6个标志位 | 16位窗口大小 |
|-------------------------------------------------------------------------------------|
| 16位校验和 | 16位紧急指针 |
|-------------------------------------------------------------------------------------|
| 选项信息 最多40字节 |
|-------------------------------------------------------------------------------------|
1.超时重传 顾名思义,如果检测到某一个数据包的定时器超出时间RTO(Retransmission Timeout超时重传时间)了,就需要重新发送该数据包了,RTO是由RTT计算出来的。RTT(Round-Trip Time 往返时延)这是从发送一个消息直到收到确认的时间。下面是RTO的计算方式:
其中 SRTT 是计算平滑的RTT ,DevRTR 是计算平滑的RTT 与 最新 RTT 的差距。在 Linux 下,α = 0.125,β = 0.25, μ = 1,∂ = 4。这是通过实验选定的系数。 如果对数据重传之后,再设定的超时时间是原来的2倍。 2.快速重传 等着计时器超时会浪费一些不必要的时间,如果发送方发送1、2、3、4、5数据包,当1丢失后,接收方收到234数据包,会回复三次ACK=1,发送方收到三次相同的ack,就知道1数据包丢了,于是重新发送1号数据包。 但是如果1号2号数据包都丢失了,345数据到达接收方时,接收方还是回复三次ACK=1,这时如果只重传1号数据包是不够的。于是有了SACK方法。 3.SACK方法 SACK(Selective Acknowledgment 选择性确认),是在接收方回复的ACK中,TCP头部的”选项”字段里,添加SACK字段,用来标识哪些数据被接收了。例如当发送方发送1-5数据,1、2号数据丢失,当接收方收到3号数据,回复ACK=1,SACK=3。接收方收到4号数据,回复ACK=1,SACK=3-4。接收方收到5号数据,回复ACK=1,SACK=3-5.这样发送方明白3-5已经发送成功,只需要重传1-2号数据。 SACK是解决”丢失多个发送方->接收方的数据包”问题。 4.Duplicate SACK D-SACK是在接收方回复ACK确认的时候,借助TCP头部”选项”里的SACK字段,标识哪些数据被重复发送了。例如,当发送方发送1-5数据,接收方回复的ACK=1和ACK=2丢失了,超时之后发送方会重复发送1、2数据,此时接收方回复ACK=3,SACK=1-2。这时发送方看到ACK是3,大于1、2,说明1-2数据是被重复发送了。 D-SACK是解决”丢失多个接收方->发送方的ACK确认”问题。
发送方应当根据接收方的处理能力来发送数据,如果接收方的应用程序读取数据速度下降,接收窗口的可用空间就会变小,于是发送方对应的会减小发送窗口的大小。 1.如果接收方的应用程序没有及时读取缓存,接收窗口不断下降,最终下降到0,这样发送窗口也被调整为0了。之后发送方应当发送探测报文,不然可能会死锁下去。为了解决这个死锁问题,TCP 为每个连接设有一个持续定时器,只要TCP 连接一方收到对方的零窗口通知,就启动持续计时器。如果持续计时器超时,就会发送窗口探测(Window probe) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。 2.如果接收方直接减小了缓存大小,也就是接收窗口的大小,发送方已经发送的一部分数据,还没确认,可能就因为降低窗口大小,被丢弃了。 3.接收方处理能力下降后,有可能出现这样一种情况,接收方通知发送方一个小的窗口大小,然后发送方发送很小的数据。这样可能只为了几个字节的数据就加上几十个自己的协议头部,开销还是挺大的。TCP使用的解决方法是不让接收方通知小窗口&不让发送方发送小数据。如果接收方的接收窗口小于min(MSS,缓存的一半),就直接通告0窗口,发送方判断的接收方窗口如果大于MSS或者自己储存的数据量大于MSS(Nagle算法),才开始发送数据。
如果收到对5个字节数据的ACK确认,发送窗口就会往右滑动5个字节。
参考:https://mp.weixin.qq.com/s/Tc09ovdNacOtnMOMeRc_uA
首先要知道拥塞窗口的概念,对应的还有个发送窗口的概念。发送窗口swnd’(这是参考值)的大小是根据接收方的接收能力来定的。而拥塞窗口cwnd是根据线路的拥堵程度确定的。实际的发送窗口大小是swnd=min(swnd’,cwnd)。以下几个算法便是用来设置拥塞窗口的值:
当拥塞程度高的时候,发送方发送abcd四个数据包,全部丢了。等发送方的计时器超时了,就知道数据包都丢了或者说接收方回复的ACK确认都丢了。这时候使用超时重传算法,将慢启动门限ssthresh设置为cwnd/2,cwnd设置为1.这样的结果就是检测到消息超时了,就切换到慢启动算法上了。
1.三次握手
三次握手本质上是要建立一个对序列号的共识(双方对一件事情共同认可),因为之后要保证数据的可靠和顺序。
四次握手有点多余,可以简化:
四次握手是这样的:
client>>server:SYN=a
client<<server:ACK=a+1
client<<server:SYN=b
client>>server:ACK=b+1
前两次握手,双方对client的序号产生了共识,后两次握手,双方对server的序号产生共识,这样tcp双向的传输通道建立。第二次和第三次握手可以直接放一块发送,这样简化为三次就可以了。
两次握手不够,会出问题:
两次握手:
client>>server:SYN=a
client<<server:ACK=a+1,SYN=b
(1)如果第二次握手的数据丢失了,server会以为成功建立了连接,但是client没有收到确认,所以会尝试重新发送SYN请求。server收到第二次SYN请求,以为是要再次建立连接就回复ACK,于是server就维护两个连接了。这样二次握手可能会导致server端单方面建立许多并不成功的连接。 (2)如果第一次握手的信息package1在网络中滞留了,client检测超时后会重新发送SYN,server回复ACK确认,这样成功建立连接了。但这时已经失效的package1又到达了server,server不知道这是失效的请求,又会回复ACK建立连接。当client接收到第二个ACK,知道server端用失效的SYN建立了连接,只能干着急。 2.四次挥手
因为TCP连接是全双工的,客户端想要断开连接,服务器回复确认。这样就断开了客户端往服务器方向的连接,但是服务器可能还需要给客户端发送数据,所以第二次第三次挥手不能合并。 3.补充阅读
第一个包,即A发给B的SYN 中途被丢,没有到达B
A会周期性超时重传,直到收到B的确认
第二个包,即B发给A的SYN +ACK 中途被丢,没有到达A
B会周期性超时重传,直到收到A的确认
第三个包,即A发给B的ACK 中途被丢,没有到达B.A发完ACK,单方面认为TCP为 Established状态,而B显然认为TCP为Active状态:
a. 假定此时双方都没有数据发送,B会周期性超时重传,直到收到A的确认,收到之后B的TCP 连接也为 Established状态,双向可以发包。
b. 假定此时A有数据发送,B收到A的 Data + ACK,自然会切换为established 状态,并接受A的 Data。
c. 假定B有数据发送,数据发送不了,会一直周期性超时重传SYN + ACK,直到收到A的确认才可以发送数据。
4.参考
上半部分是TCP三路握手过程的状态变迁,下半部分是TCP四次挥手过程的状态变迁。
CLOSED:起始点,在超时或者连接关闭时候进入此状态,这并不是一个真正的状态,而是这个状态图的假想起点和终点。
LISTEN:服务器端等待连接的状态。服务器经过 socket,bind,listen 函数之后进入此状态,开始监听客户端发过来的连接请求。此称为应用程序被动打开(等到客户端连接请求)。
SYN_SENT:第一次握手发生阶段,客户端发起连接。客户端调用 connect,发送 SYN 给服务器端,然后进入 SYN_SENT 状态,等待服务器端确认(三次握手中的第二个报文)。如果服务器端不能连接,则直接进入CLOSED状态。
SYN_RCVD:第二次握手发生阶段,跟 3 对应,这里是服务器端接收到了客户端的 SYN,此时服务器由 LISTEN 进入 SYN_RCVD状态,同时服务器端回应一个 ACK,然后再发送一个 SYN 即 SYN+ACK 给客户端。状态图中还描绘了这样一种情况,当客户端在发送 SYN 的同时也收到服务器端的 SYN请求,即两个同时发起连接请求,那么客户端就会从 SYN_SENT 转换到 SYN_REVD 状态。
ESTABLISHED:第三次握手发生阶段,客户端接收到服务器端的 ACK 包(ACK,SYN)之后,也会发送一个 ACK 确认包,客户端进入 ESTABLISHED 状态,表明客户端这边已经准备好,但TCP 需要两端都准备好才可以进行数据传输。服务器端收到客户端的 ACK 之后会从 SYN_RCVD 状态转移到 ESTABLISHED 状态,表明服务器端也准备好进行数据传输了。这样客户端和服务器端都是 ESTABLISHED 状态,就可以进行后面的数据传输了。所以 ESTABLISHED 也可以说是一个数据传送状态。
上面就是 TCP 三次握手过程的状态变迁。结合第一张三次握手过程图,从报文的角度看状态变迁:SYN_SENT 状态表示已经客户端已经发送了 SYN 报文,SYN_RCVD 状态表示服务器端已经接收到了 SYN 报文。
下面看看TCP四次挥手过程的状态变迁。结合第一张四次挥手过程图来理解。
FIN_WAIT_1:第一次挥手。主动关闭的一方(执行主动关闭的一方既可以是客户端,也可以是服务器端,这里以客户端执行主动关闭为例),终止连接时,发送 FIN 给对方,然后等待对方返回 ACK 。调用 close() 第一次挥手就进入此状态。
CLOSE_WAIT:接收到FIN 之后,被动关闭的一方进入此状态。具体动作是接收到 FIN,同时发送 ACK。之所以叫 CLOSE_WAIT 可以理解为被动关闭的一方此时正在等待上层应用程序发出关闭连接指令。前面已经说过,TCP关闭是全双工过程,这里客户端执行了主动关闭,被动方服务器端接收到FIN 后也需要调用 close 关闭,这个 CLOSE_WAIT 就是处于这个状态,等待发送 FIN,发送了FIN 则进入 LAST_ACK 状态。
FIN_WAIT_2:主动端(这里是客户端)先执行主动关闭发送FIN,然后接收到被动方返回的 ACK 后进入此状态。
LAST_ACK:被动方(服务器端)发起关闭请求,由状态2 进入此状态,具体动作是发送 FIN给对方,同时在接收到ACK 时进入CLOSED状态。
CLOSING:两边同时发起关闭请求时(即主动方发送FIN,等待被动方返回ACK,同时被动方也发送了FIN,主动方接收到了FIN之后,发送ACK给被动方),主动方会由FIN_WAIT_1 进入此状态,等待被动方返回ACK。
TIME_WAIT:从状态变迁图会看到,四次挥手操作最后都会经过这样一个状态然后进入CLOSED状态。共有三个状态会进入该状态
*由CLOSING进入:同时发起关闭情况下,当主动端接收到ACK后,进入此状态,实际上这里的同时是这样的情况:客户端发起关闭请求,发送FIN之后等待服务器端回应ACK,但此时服务器端同时也发起关闭请求,也发送了FIN,并且被客户端先于ACK接收到。
*由FIN_WAIT_1进入:发起关闭后,发送了FIN,等待ACK的时候,正好被动方(服务器端)也发起关闭请求,发送了FIN,这时客户端接收到了先前ACK,也收到了对方的FIN,然后发送ACK(对对方FIN的回应),与CLOSING进入的状态不同的是接收到FIN和ACK的先后顺序。
*由FIN_WAIT_2进入:这是不同时的情况,主动方在完成自身发起的主动关闭请求后,接收到了对方发送过来的FIN,然后回应 ACK。
参考:https://blog.csdn.net/wenqian1991/article/details/40110703
主动关闭连接的那一方最后会进入TIME_WAIT状态,这个状态可以解决两个问题:
|-------------------------------------------------------------------------------------|---
| 32位源IP地址 |
|-------------------------------------------------------------------------------------| UDP伪首部
| 32位目的IP地址 | (伪首部是计算校验和用的)
|-------------------------------------------------------------------------------------|
| 0 | 8位协议 | 16位UDP长度 |
|-------------------------------------------------------------------------------------|-------------------------
| 16位源端口号 | 16位目的端口号 |
|-------------------------------------------------------------------------------------|
| 16位UDP长度 | 16位UDP校验和 | UDP首部
|-------------------------------------------------------------------------------------|
| 数据 |
|-------------------------------------------------------------------------------------|---
http://leungyukshing.cn/archives/QUIC.html
TCP | UDP |
---|---|
面向连接的、可靠的传输 | 无连接、不可靠的传输 |
面向字节流,有一个缓冲区来储存字节序列 | 面向报文的,来一个报文就发送 |
只能是点对点的 | 可以一对一、一对多、多对多 |
有三次握手、确认、拥塞控制、流量控制等机制来保证可靠传输 | 没有这些机制 |
占用资源多,速度慢 | 速度快 |
适合对数据可靠性要求严格的场景,如传输文件、网页 | 适合对时间要求严格的 |
1.http请求可以分为三部分,第一部分是请求行,第二部分是请求头部,由一个个的key-value组成,第三部分是请求包体。
请求方法 URL 协议版本 <回车符换行符>
头部字段1 : 值1 <回车符换行符>
... : ...
头部字段n : 值n <回车符换行符>
<空行>
请求包体
样例:
GET/sample.Jsp HTTP/1.1
Accept:image/gif.image/jpeg,*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding:gzip,deflate
username=jinqiao&password=1234
2.http相应格式也分为三部分,分别是状态行、响应头部、响应包体。
协议版本 状态码 状态码描述 <回车符换行符>
头部字段1 : 值1 <回车符换行符>
... : ...
头部字段n : 值n <回车符换行符>
<空行>
响应包体
样例:
HTTP/1.1 200 OK
Date: Mon, 10 Aug 2020 09:45:14 GMT
Server: Apache/2.4.6
Last-Modified: Wed, 04 Dec 2013 11:03:37 GMT
ETag: 1386155017
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
Accept : 浏览器(或者其他基于HTTP的客户端程序)可以接收的内容类型(Content-types),例如 Accept: text/plain Accept-Charset:浏览器能识别的字符集,例如 Accept-Charset: utf-8 Accept-Encoding:浏览器可以处理的编码方式,注意这里的编码方式有别于字符集,这里的编码方式通常指gzip,deflate等。例如 Accept-Encoding: gzip, deflate Accept-Language:浏览器接收的语言,其实也就是用户在什么语言地区,例如简体中文的就是 Accept-Language: zh-CN Authorization:在HTTP中,服务器可以对一些资源进行认证保护,如果你要访问这些资源,就要提供用户名和密码,这个用户名和密码就是在Authorization头中附带的,格式是“username:password”字符串的base64编码,例如:Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==中,basic指使用basic认证方式,QWxhZGRpbjpvcGVuIHNlc2FtZQ==使用base64解码就是Aladdin:open sesame Cookie:浏览器向服务器发送请求时发送cookie,或者服务器向浏览器附加cookie Content-Length:一个请求的请求体的内存长度,单位为字节(byte)。请求体是指在HTTP头结束后,两个CR-LF字符组之后的内容,常见的有POST提交的表单数据,这个Content-Length并不包含请求行和HTTP头的数据长度。 Content-MD5:使用base64进行了编码的请求体的MD5校验和。例如:Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== User-Agent:通常就是用户的浏览器相关信息。例如:User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/12.0 …
1xx 代表请求已被接受,需要继续处理。但由于HTTP/1.0中没有定义任何1xx状态码,所以很少使用。
2×× 代表请求已成功被服务器接收、理解、并接受。
200 OK 请求成功
3xx 一般是用来重定向
302 暂时性转移
301 永久性转移
305 使用代理,所请求的资源必须通过代理访问
4xx 请求错误,一般是客户端导致的错误
400 Bad Request 当前请求有误,无法被理解
401 Unauthorized 当前请求需要用户验证
403 Forbidden 服务器已经理解请求,但是拒绝执行,比如访问了没有权限的目录。
404 Not Found 服务器没有找到所请求的资源
5xx 服务器内部错误
500 Internal Server Error :服务器正在执行请求时发生错误。
503 Service Unavailable :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。
??
文件传输协议 ftp 21
安全shell ssh 22
远程登录协议 telnet 23
传输邮件服务 smtp 25
文本传输协议 http 80
访问远程服务器上的邮件 pop3 110
安全的超文本传输服务 https 443
dns 53
tcp client tcp server
socket socket
bind bind
connect listen
send accept
receive receive
send
========================
int socket(int protofamily, int so_type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int send(int sockfd, const void *msg, int len, int flags);
int recv(int sockfd, void *buf, int len, unsigned int flags);
翻译成中文的意思就是一个线程里面一个循环。一般可以让线程A接收新连接,然后将新连接的socket传给B1、B2、B3…来负责收发数据。一般的代码结构如下:
void thread_func(void* thread_arg)
{
//这里做一些需要的初始化工作
while (退出标志){
//步骤一:利用select/poll/epoll等IO复用技术,分离读写事件
//步骤二:处理读事件或写事件
//步骤三:做一些其他的事情
}
//这里做一些需要的清理
}
####
欢迎与我分享你的看法。 转载请注明出处:http://taowusheng.cn/