企业网站建设费用 珠海,网站备案 前置审批文件,如何制作手机版网站,建设工程施工合同下载目录 应用层HTTP协议认识URLurlencode和urldecode HTTP协议格式http请求格式http响应格式 HTTP的方法GET与POST的区别 HTTP的状态码HTTP常见HeaderCookie与Session 传输层在谈端口号端口号范围划分认识知名端口号netstatpidof UDP协议UDP协议端格式UDP的特点面向数据报UDP的缓冲… 目录 应用层HTTP协议认识URLurlencode和urldecode HTTP协议格式http请求格式http响应格式 HTTP的方法GET与POST的区别 HTTP的状态码HTTP常见HeaderCookie与Session 传输层在谈端口号端口号范围划分认识知名端口号netstatpidof UDP协议UDP协议端格式UDP的特点面向数据报UDP的缓冲区UDP使用注意事项 TCP协议TCP协议段格式确认应答(ACK)机制超时重传机制连接管理机制三次握手四次挥手理解TIME_WAIT理解CLOSE_WAIT 滑动窗口流量控制拥塞控制延迟应答捎带应答 面向字节流粘包问题TCP异常情况 应用层
HTTP协议
虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一.
认识URL
平时我们俗称的 “网址” 其实就是说的 URL URL全称为Uniform Resource Locator统一资源定位符是互联网上用于标识、定位和访问特定资源的一种标准化方法。它是一种全球通用的地址格式使得用户可以通过浏览器、应用程序或其他网络工具准确无误地找到并访问互联网上的各种信息和服务。 一个完整的URL通常由以下几个部分组成 协议Protocol指定了访问资源所使用的网络协议如HTTP超文本传输协议、HTTPS安全超文本传输协议、FTP文件传输协议等。这部分以“:”结束位于URL的最开始。 域名Domain Name即网站的地址用于识别和定位互联网上的服务器。 端口Port指定服务器上接收请求的服务端口默认情况下HTTP协议使用端口80HTTPS协议使用端口443。如果不指定端口浏览器会使用默认端口。端口与域名之间用“:”分隔。 路径Path指定了服务器上资源的具体位置。路径以“/”开头可以包含多级目录和文件名。 查询参数Query Parameters当需要向服务器传递额外数据或指定资源的特定版本时可以使用查询字符串。查询字符串紧跟在路径后面以“?”开始各参数之间用“”分隔。每个参数由键值对组成键与值之间用“”连接。 片段标识符Fragment Identifier用于指示网页内部特定位置的标签如HTML中的id属性。片段标识符以“#”开始后跟标识符名称。浏览器不会将片段标识符发送到服务器而是在页面加载后自行滚动到带有相应ID的元素处。 www.baidu.com表示的是服务器地址也叫做域名。 需要注意的是我们用IP地址标识公网内的一台主机但IP地址本身并不适合给用户看。比如说我们可以通ping命令获得www.baidu.com的域名解析后的IP地址。 域名与IP地址之间存在一对一或多对一的映射关系。一个域名可以映射到一个或多个IP地址上而一个IP地址也可以对应多个域名。这种映射通过DNSDomain Name System域名系统服务实现。
urlencode和urldecode
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现。 转义的规则如下: 将需要转码的字符转为16进制然后从右到左取4位(不足4位直接处理)每2位做一位前面加上%编码成%XY格式。 汉字也是要进行编码的。 urlencode工具 HTTP协议格式
http请求格式 下面的TCP服务器的功能会对浏览器发送过来的HTTP请求进行打印。
#include iostream
#include string.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h //struct sockaddr_in
#include unistd.h
#include sys/wait.h
#include stdlib.h
const static uint16_t port 8888;
int main()
{//创建套接字int listen_sock socket(AF_INET, SOCK_STREAM, 0);//AF_INET 表示网络通信//SOCK_STREAM 流式套接if(-1 listen_sock){std::cerr socket create error std::endl;return 1;}//绑定struct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET; // 通信方式local.sin_port htons(port); // 端口号local.sin_addr.s_addr htonl(INADDR_ANY); // ip地址if(-1 bind(listen_sock,(struct sockaddr*)local, sizeof(local))){std::cerr socket bind error std::endl;return 2;}//监听if(listen(listen_sock, 32) -1){std::cerr socket listen error std::endl;return 2;}while(true){//获取链接struct sockaddr_in client;socklen_t len sizeof(client);int sock accept(listen_sock, (struct sockaddr*)client, len);if (sock -1){std::cerr accept error std::endl;return 3;}int ret fork();if(ret -1){std::cerr fork create error std::endl;return 4;}else if(ret 0){waitpid(-1,nullptr,0);}else if (ret 0){int _ret fork();if (_ret -1){std::cerr fork fork create error std::endl;return 5;}else if (_ret 0){close(listen_sock);exit(0);}//孤儿进程会被1号进程领养char buffer[4096];ssize_t s recv(sock, buffer, sizeof(buffer) - 1, 0); // 我们认为我们能一次读完buffer[s] \0;std::cout --------------------------------------- std::endl;std::cout buffer std::endl;std::cout --------------------------------------- std::endl;close(sock);exit(0);}close(sock);}return 0;
}浏览器向我们的服务器发起HTTP请求后因为我们的服务器没有对进行响应此时浏览器就会认为服务器没有收到然后再不断发起新的HTTP请求因此虽然我们只用浏览器访问了一次但会受到多次HTTP请求。 URL当中的/不能称之为我们云服务器上根目录这个/表示的是web根目录这个web根目录可以是你的机器上的任何一个目录这个是可以自己指定的不一定就是Linux的根目录。 如果浏览器在访问我们的服务器时指明要访问的资源路径那么此时浏览器发起的HTTP请求当中的url也会跟着变成该路径。
http响应格式 #include iostream
#include string.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h //struct sockaddr_in
#include unistd.h
#include sys/wait.h
#include stdlib.h
const static uint16_t port 8888;
const std::string SEP \r\n;
int main()
{//创建套接字int listen_sock socket(AF_INET, SOCK_STREAM, 0);//AF_INET 表示网络通信//SOCK_STREAM 流式套接if(-1 listen_sock){std::cerr socket create error std::endl;return 1;}//绑定struct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET; // 通信方式local.sin_port htons(port); // 端口号local.sin_addr.s_addr htonl(INADDR_ANY); // ip地址if(-1 bind(listen_sock,(struct sockaddr*)local, sizeof(local))){std::cerr socket bind error std::endl;return 2;}//监听if(listen(listen_sock, 32) -1){std::cerr socket listen error std::endl;return 2;}while(true){struct sockaddr_in client;socklen_t len sizeof(client);int sock accept(listen_sock, (struct sockaddr*)client, len);if (sock -1){std::cerr accept error std::endl;return 3;}int ret fork();if(ret -1){std::cerr fork create error std::endl;return 4;}else if(ret 0){waitpid(-1,nullptr,0);}else if (ret 0){int _ret fork();if (_ret -1){std::cerr fork fork create error std::endl;return 5;}else if (_ret 0){close(listen_sock);exit(0);}//孤儿进程会被1号进程领养char buffer[4096];ssize_t s recv(sock, buffer, sizeof(buffer) - 1, 0); // 我们认为我们能一次读完buffer[s] \0;std::cout --------------------------------------- std::endl;std::string response HTTP/1.0 200 OK SEP;//状态行response SEP;//空行response hello Http;//有效载荷//有效载荷部分通常是以网页的方式进行呈现的send(sock,response.c_str(),response.size(), 0);std::cout --------------------------------------- std::endl;close(sock);exit(0);}close(sock);}return 0;
}http如何将报头与有效载荷如何分离的 逐行读取从网络流中按行读取数据。每读取到一行检查是否为键值对形式的报头行。识别空行继续读取直到遇到一个仅由\r\n组成的行即空行。这个空行标志着报头部分的结束。截断报头将读取到空行之前的所有数据视为报头对其进行解析提取出各个报头字段及其对应的值。读取有效载荷空行之后的数据被视为有效载荷。 http如果请求协议版本与http响应协议版本如果不同服务器是如何响应的那 当客户端发起HTTP请求时会在请求行中明确指定所使用的HTTP版本。这是客户端向服务器告知其协议能力的方式。服务器接收到请求后识别并依据客户端声明的HTTP版本进行响应处理。服务器会调整响应的格式以符合客户端所使用的版本规范确保双方能够正常通信避免版本差异带来的问题。HTTP协议中客户端与服务器关于版本信息的交互并非双向交流而是客户端单向告知其版本服务器通过使用相同版本构建响应进行确认。
HTTP的方法
方法说明支持的HTTP协议版本GET获取资源1.0、1.1POST传输实体主体1.0、1.1PUT传输文件1.0、1.1HEAD获得报文首部1.0、1.1DELETE删除文件1.0、1.1OPTIONS询问支持的方法1.1TRACE追踪路径1.1CONNECT要求用隧道协议连接代理1.1LINK建立和资源之间的联系1.0UNLINK断开连接关系1.0
GET与POST的区别
GET POST GETPOST差别他们各自的应用场景是什么? GET方法提交参数不私密。POST提交参数比较私密一些。这两种方法都不是安全。 所有的登陆注册支付等行为都要使用POST(不是只用)方法提参。 url: http请求行字符串一般都会有大小的约束正文理论上可以非常大。
HTTP的状态码
类别原因短语1XXInformational信息性状态码接收的请求正在处理2XXSuccess成功状态码请求正常处理完毕3XXRedirection重定向状态码需要进行附加操作以完成请求4XXClient Error客户端错误状态码服务器无法处理请求5XXServer Error服务器错误状态码服务器处理请求出错
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
重定向又可分为临时重定向和永久重定向其中状态码301表示的就是永久重定向而状态码302和307表示的是临时重定向。 临时重定向 当客户端如浏览器收到302状态码时它会自动访问新的URL但仍然保留对原URL的请求历史。后续对该资源的请求客户端可能会再次尝试访问原URL除非得到明确指示或缓存失效。 永久重定向 收到301状态码时客户端不仅会自动跳转到新的URL还会更新其内部记录如浏览器的历史记录、书签将原URL替换为新的URL。后续请求会直接指向新的URL除非用户手动清除缓存或有其他更新。
HTTP常见Header
Content-Type数据类型text/html等。Content-Length正文的长度。Host客户端告知服务器所请求的资源是在哪个主机的哪个端口上。User-Agent声明用户的操作系统和浏览器的版本信息。Referer当前页面是哪个页面跳转过来的。Location搭配3XX状态码使用告诉客户端接下来要去哪里访问。Cookie用于在客户端存储少量信息通常用于实现会话session的功能。
Cookie与Session
HTTP本身是无状态的这意味着它在处理请求和响应时不会保留任何关于之前交互的上下文信息。 以下是无状态特性的详细解释 每个请求独立处理在无状态协议下每个HTTP请求被视为独立的事务服务器不依赖于任何关于客户端过去行为的知识来处理当前请求。客户端每次发起请求时必须在请求中包含所有必需的信息以便服务器能够理解请求的目的并生成适当的响应。
但是我们在使用浏览器的访问CSDN(或其他需要登录的网站)只要首次登录过下次在访问时我们发现CSDN已经处于登录状态了就算你把CSDN网站关了甚至是重启电脑当你再次打开CSDN网站时还是是登录状态。 HTTP是超文本传输协议HTTP不直接做这个工作但是HTTP提供了这个功能因为用户需要—会话保持。
cookie是什么呢 浏览器在访问需要登录的服务的vip资源时首先浏览器向服务器发送请求服务器收到了浏览器的请求如果服务器检测到浏览器没有登录对浏览器的响应就是要求浏览器进行登录。浏览器登录上用户名与密码后在向服务发送请求服务器会对收到用户密码做认证只要认证通过服务器在对浏览器的响应中会将选项Set-Cooke: 私人信息(用户名密码等)携带到Http响应里。当浏览器收到的响应里Set-Cooke: ,会将Set-Cooke: 的右值进行本地保存(内存级文件级)。以后浏览器在访问相同的网站时浏览器会在后续的Http请求中会携带Set-Cooke: 保存的历史cooke信息不用用户进行手动操作这是浏览器自动做的。 如果你浏览器当中保存的cookie信息被非法用户盗取了那么此时这个非法用户就可以用你的cookie信息以你的身份去访问你曾经访问过的网站我们将这种现象称为cookie被盗取了。 Sessionid是什么呢
非法用户虽然还能盗取了你的cooke以你的身份也去访问对应的目标网站了这个问题还是会存在。但是我们不用担心用户的个人的私人信息被泄露了因为用户私人的信息已经被服务器维护起来了浏览器与服务器用sessionid进行交互。
传输层
传输层主要任务提供传输策略。
在谈端口号
端口号标识本主机上的唯一的网络进程。 在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过netstat -n查看);
端口号范围划分
端口号是2个字节16位整数uint16_t
0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的
认识知名端口号
有些服务器是非常常用的, 为了使用方便, 人们约定一些常用的服务器, 都是用以下这些固定的端口号:
ssh服务器, 使用22端口ftp服务器, 使用21端口telnet服务器, 使用23端口http服务器, 使用80端口https服务器, 使用443端口
netstat
netstat是一个用来查看网络状态的重要工具. 语法netstat [选项] 功能查看网络状态 常用选项
n 拒绝显示别名能显示数字的全部转化成数字l 仅列出有在 Listen (监听) 的服務状态p 显示建立相关链接的程序名t (tcp)仅显示tcp相关选项u (udp)仅显示udp相关选项a (all)显示所有选项默认不显示LISTEN相关
pidof
在查看服务器的进程id时非常方便. 语法pidof [进程名] 功能通过进程名, 查看进程id
UDP协议
UDP协议端格式 16位UDP长度, 表示整个数据报(UDP首部UDP数据)的最大长度;如果校验和出错, 就会直接丢弃如果UDP的报文有残缺UDP所对应的检验和就无法通过UDP就将报文丢弃。
这种报文(报文报头 有效载荷)是如何进行封装与解包的那
UDP的特点
无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接;不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息;面向数据报: 不能够灵活的控制读写数据的次数和数量
面向数据报
应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并; 用UDP传输100个字节的数据:
如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节
UDP的缓冲区
UDP没有真正意义上的 发送缓冲区. 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;
UDP的socket既能读, 也能写, 这个概念叫做 全双工
UDP使用注意事项
我们注意到, UDP协议首部中有一个16位的最大长度. 也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部). 然而64K在当今的互联网环境下, 是一个非常小的数字. 如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装;
TCP协议 客户端和服务端客户端可以给服务器发送消息。服务器当然也可以给客户端以同样的模式发送消息。TCP通信的本质其实是把自己发送缓冲区的内容拷贝到对方的接收缓冲区对方把自己发送缓冲区的内容拷贝到自己的接收缓冲区。因为是成对的所以发和收发和收可以同时进行所以我们称TCP协议是全双工 TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制。
TCP协议段格式
报头 20字节标准报头 选项
TCP报头当中各个字段的含义如下
源/目的端口号 表示数据是从哪个进程来, 到哪个进程去。32位序号/32位确认序号分别代表TCP报文当中每个字节数据的编号以及对对方的确认是TCP保证可靠性的重要字段。4位首部长度表示该TCP报头的长度以4字节为单位。6位保留字段TCP报头中暂时未使用的6个比特位。16位窗口大小进行流量控制的保证TCP可靠性机制和效率提升机制的重要字段。自己接收缓冲区的剩余空间大小进行流量控制(增大或减小)16位检验和由发送端填充采用CRC校验。接收端校验不通过则认为接收到的数据有问题。检验和包含TCP首部TCP数据部分16位紧急指针标识紧急数据在报文中的偏移量需要配合标志字段当中的URG字段统一使用。选项字段TCP报头当中允许携带额外的选项字段。
6位标志位: 表示服务端在同一时刻收到的各种各样的报文请求根据不同的报文提供不同对应的处理动作,这六个标志位都只占用一个比特位为0表示假为1表示真。
URG: 紧急指针是否有效ACK: 确认号是否有效PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段 TCP如何将报头与有效载荷进行分离 TCP报文段开始的部分是固定的通常包括20字节的标准报头。当TCP接收到一个报文段时首先读取这前20个字节。并从中提取出4位的首部长度首部长度 * 4 标准报头 选择字段的大小。如果首部长度 * 4 的等于20字节就证明选项没有携带数据此时首部长度的二进制位0101(十进制为5)这也是首部长度的最小值。如果首部长度 * 4 的大于20字节首部长度 * 4 - 20字节(标准报头长度)剩下的都是选项携带的数据大小。一旦确定了整个TCP头部的大小包括任何选项就可以据此分离出报头部分和跟随其后的有效载荷用户数据。 URG TCP缓冲区的数据必须要按序到达TCP的缓冲区采用的是队列结构。这包括TCP的发送缓冲区和接收缓冲区。数据按照到达的顺序被添加到缓冲区中并且也是按照这个顺序被处理或发送出去。TCP报文包含序号TCP会按照序号进行排序按序号由小到大的入队列。这样就保证了上层拿到的数据是有序的。 当TCP报文中URG标志位为1,则16位紧急指针有效,这表明该报文段携带了紧急数据。紧急指针的存在主要是为了通知接收端存在需要优先处理的数据紧急指针的数据不用进入队列但并不直接导致网络层面的优先传输。 16位紧急指针是一个偏移量它告诉接收端紧急数据在哪里。 RST 当通信双方三次握手结束后双方开始正常通信时其中服务器因为一些原因释放客户的链接。这时客户端不知道客户端像以前一样向服务器发送请求服务器在收到请求后查看建立的链接发现没有与这个客户端建立链接就会对这个客户的响应的报文中的6个标志位中RST置1要求重新建立连接。这时客户端就会将原来的链接释放掉然后重新在发送三次握手。
确认应答(ACK)机制
本质是确认是否丢包
当我发送的数据如果对方没有收到那么我作为发送方我也得知道对方没收到如果对方收到了我要知道我们收到了没收到我要知道他没收到。所以对于我们所做的所有工作我们都要有反馈。这一套机制整体叫 可靠性。 TCP将每个字节的数据都进行了编号. 即为序列号. 确认序号X: 就算X-1之前的应答有的没有收到但是只有收到确认序号X那证明X-1之前的报文已经全部收到了下次发请从X编号开始发送。确认应当可以允许少量的ACK应答丢失更细粒度的确认丢失的原因。 为什么TCP有 序号 和 确认序号不能将着两个字段合并成一个字段吗 为什么会有这个问题那在将客户端中从几字节到几字节的数据发送给服务端服务端在将确认序号响应给客户端表示收到了数据在着双方通信时都空着一个字段不是序号字段就是确认序号字段如果将这两个字段合并不是就节省了空间吗但是如果响应只包含确认序号那不是双方的交互效率太低了吗反而浪费了时间。因此响应不仅可以包含服务端对客户端的响应还可以包含服务端对客户端的请求压缩成请求/响应这样的方式称为稍带应答所以TCP要有 序号 和 确认序号两个字段。
超时重传机制 主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B; 如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发;
数据在没有收到应答时是不能清除的以保证超时重传。
但是, 主机A未收到B发来的确认应答, 也可能是因为ACK丢失了; 因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉. 这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果.
那么, 如果超时的时间如何确定?
最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”.但是这个时间的长短, 随着网络环境的不同, 是有差异的.如果超时时间设的太长, 会影响整体的重传效率;如果超时时间设的太短, 有可能会频繁发送重复的包;
TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间.
Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍.如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接
连接管理机制
许多客户端与服务器建立链接一定在OS服务器内同时存在多个建立好的连接OS要不要把这些已经建立好的连接管理起来要管理就要先描述在组织。OS内为了管理连接维护的数据结构创建维护连接是有成本的。 在正常情况下, TCP要经过三次握手建立连接, 四次挥手断开连接
三次握手 最后一个ACK可能会因为一些不可抗拒的因素而导致对方无法收到此时链接客户端会认为链接建立好了客户端就会进入的ESTABUSHED状态但服务器会认为链接没建立好此处就会存在两个结论第一个当我们在第三次握手的时候最后一个报文丢失其实就是在赌概率。没有人说链接建立的过程必须得成功也可能失败。那么只要他成功通信就行了不成功我们要RST要求对方重新建立链接。第二个链接建立并不一定成功。注意在正常的网络通信过程最后一个报文丢失的都是小概率事件。 所以那么我们为什么不是四次握手呢或者是偶数次握手了 就是在我们主动建立链接的时候。最后一个报文谁发的那么他的链接就先建立。而其中如果是奇数次握手一定是客户端来主动发起最后一个ACK。如果是偶数次握手。一定是服务器端主动发起我们的最后一次握手你应该发现了最后一个报文它一定是要在网络当中进行传输要花时间的所以如果是奇数次握手。那么它最后一个报文一定是由我们所对应的叫做客后端最后一次发起偶数次握手它的最后一个报一定是由服务端主动发起的奇数次握握手也就直接决定了那么一定是客户端先把连接建立好。然后我服务器才给你把连接建立好。而偶数次握手是我的服务器端先建立好链接此时把这个不确定性就带给了服务器因为最后一次ACK那么客户端有没有收到服务器并不确认。所以那么我们为什么不是四次握手呢或者是偶数次握手了那么主要原因是因为那么我们链接握手一定会发生异常一定有概率。尤其是对于服务器它一旦面临多个客户端那么出现链接异常的情况一定是家常便饭的事情。所以链接一旦建立异常比如说N次握手我们最不担心N-1次之前的报文丢失就是因为N-1次之前所有的报文都会有应答而且链接N次握手并没有完成双方并没有开始维护这个成本但如果是偶数次握手呢那么最后一次我们发送ACK那么承担链接建立异常的这种成本就可能会嫁接到服务器端。那服务器端是面对的是多个客户端所以几乎就会导致服务器端维护这种异常链接会成为必然那么对服务来讲这就特别不好。 为什么要三次握手呢
没有明显的设计漏洞一但建立的链接出现异常成本嫁接到客户端服务的成本较低。3次握手是检验双方通信最小次数。TCP建立链接时并不清楚客户端和服务器将怎么通信所以客户端和我们对应的服务器它必须得先验证双方通信信道是通畅的。因为TCP协议在通讯时是全双工通信必须得保证双方通信的信道是通畅的。
四次挥手 为什么是四次挥手 主要的是争得通信双方同意。因为TCP连接是全双工的可以同时进行数据的双向传输。这意味着连接的关闭需要分别关闭两个方向的数据传输通道确保数据的完全传输和资源的正确释放因此需要四个步骤来确保双方都知晓连接的关闭情况且不会丢失数据其中每两个的步骤就是关闭一个方向的通信。同时四次挥手是关闭双方通信的最少次数。 第二次挥手和第三次挥手不会合并原因在于这两个步骤各自承担着不同的功能并且需要独立的确认机制来保证连接的可靠关闭。每个步骤独立确认的原则这是为了确保连接关闭的可靠性和双方状态的一致性。
注意客户端和服务端都可以发起断开链接请求通常主要是客户端发起断开链接请求。
理解TIME_WAIT
TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态.。
理解CLOSE_WAIT
对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭 socket, 导致四次挥手没有正确完成. 这是一个 BUG. 只需要加上对应的 close 即可解决问题。简单的说就是内存泄漏。每个连接都会占用服务器的维护相应的数据结构最终就会导致服务器可用资源越来越少。还有对造成文件描述符泄漏以外可能也会导致连接资源没有完全释放这其实也是一种内存泄漏的问题。
滑动窗口
可以将发送缓冲区当中的数据分为三部分 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值. 上图的窗口大小就是4000个字节(四个段).发送前四个段的时候, 不需要等待任何ACK, 直接发送;收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;操作系统内核为了维护这个滑动窗口, 需要开辟发送缓冲区来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉;窗口越大, 则网络的吞吐率就越高;
如何实现滑动窗口 TCP发送缓冲区都看作一个字符数组而滑动窗口实际就可以看作是两个数组下标限定的一个范围比如我们用start指向滑动窗口的左侧end指向的是滑动窗口的右侧此时在win_start和win_end区间范围内的就可以叫做滑动窗口。 滑动窗口只能向右移动因为左边的数据已经发送了。滑动窗口只能变大不是变小。滑动窗口的大小是由对方的TCP报文中16位窗口大小来确定的所以滑动窗口的大小是浮动的。滑动窗口的大小变成0表示对方不能在接收数据了。滑动窗口的大小怎么更新依据对方的接收能力(报文中的窗口大小)来确定。
流量控制
TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制
接收端(通信双方可以互为接受端)将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;窗口大小字段越大, 说明网络的吞吐量越高;接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后, 就会减慢自己的发送速度;如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端
双方在第一次通信时如何确定发送数据的多少那 双方在三次握手期间已经至少有过一次的请求与响应报头交换了就可以双方的接收与发送缓冲区的大小客户端在三次握手建立链接时是不会携带数据的。
一旦发送窗口为0或者接收窗口为0我们双方就要进行窗口探测和窗口更新来进行尽快重新进行形成共识然后尽快再发送。窗口探测与窗口更新这两种方案是同时运用的。
拥塞控制
两台主机在网络通信时出现个别数据包丢包的情况是很正常的可以通过快重传或超时重发进行数据包补发。如果出现了大量丢包此时就不能认为是正常现象了可能是发生网络阻塞了。因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜的。 TCP引入慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据。
慢启动开始时拥塞窗口很小随着数据包的成功接收它会指数增长。这个阶段被称为慢启动因为初始速度较慢但随后迅速增加。拥塞避免当拥塞窗口达到一个阈值sshtresh进入拥塞避免阶段。在这个阶段拥塞窗口的增长不再是指数级的而是线性的这样可以防止网络过载。乘法减小如果出现丢包现象通常由超时或快速重传触发则认为网络可能已经拥塞。此时sshtresh被设置为当前的网络拥塞时阻塞窗口的一半拥塞窗口重新被设置为一个较小的值通常是1或2。这称为乘法减小因为它将拥塞窗口和sshtresh都减少到原来的一部分主机在进行网络通信时不断的进行这样的循环
滑动窗口的大小 min(对端主机的接收能力(16窗口大小)拥塞串口的大小)。
延迟应答
如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小.
假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;
一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
不是所有的数据包都可以延迟应答。
数量限制每个N个包就应答一次。时间限制超过最大延迟时间就应答一次这个时间不会导致误超时重传
具体的数量和超时时间, 依操作系统不同也有差异; 一般N为2, 超时时间取200ms。
捎带应答
捎带应答 Piggybacking ACKs 是TCP协议中一种优化数据传输效率的方法。该方法利用了这样一个事实在全双工通信中双方通常都会有机会既作为发送方又作为接收方。当一端需要发送数据给对方时可以在该数据包的头部中包含对之前接收到数据的确认信息ACK而不是单独发送一个ACK包。这样做可以减少网络中的数据包数量降低网络拥塞提高整体的通信效率。
面向字节流
创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区和一个 接收缓冲区;
调用write时, 数据会先写入发送缓冲区中;如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;然后应用程序可以调用read从接收缓冲区拿数据;另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做全双工
由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如.
写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节.读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次.
粘包问题
首先要明确, 粘包问题中的 “包” , 是指的应用层的数据包。在TCP的协议头中, 没有如同UDP一样的 “报文长度” 这样的字段, 但是有一个序号这样的字段.。站在传输层的角度, TCP是一个一个报文过来的. 按照序号排好序放在缓冲区中。站在应用层的角度, 看到的只是一串连续的字节数据。那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包。 如何避免TCP粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界。
对于定长的包, 保证每次都按固定大小读取即可。对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置。对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可)。 UDP是不存在粘包问题。
对于UDP, 如果还没有上层交付数据, UDP的报文长度仍然在. 同时, UDP是一个一个把数据交付给应用层. 就有很明确的数据边界。站在应用层的站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收. 不会出现半个的情况。
TCP异常情况
进程终止 进程终止操作系统会回收其占用的资源包括内存、文件描述符等, 仍然可以完成四次挥手. 和正常关闭没有什么区别。机器重启/关机 当我们选择重启/关机时操作系统会先关闭所有进程然后再进行关机或重启因此机器重启或关机和进程终止的情况是一样的此时双方操作系统也会正常完成四次挥手然后释放对应的连接资源。机器掉电/网线断开
接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset. 即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.如果这时客户端又重新插上网线如果客户端的链接还在是可以通信的。如果客户端没有链接了这时服务器发起请求或响应客户端就会向服务器发送RST