网络通讯协议 网络分层/TCP/IP/简单http服务器/,可靠传输

网络通讯需要注意(协议 + IP地址) + OSI + TCP/IP + 通讯传输流程
局域网, 广域网,互联网,域域网,因特网
port端口: 在一台主机上标识一个进程
协议: 双方通讯的协定
网络协议: 网络通信唤醒数据的约定格式
通信协议标准: 网络互联的前提

协议分层:
协议封装,便于使用
对服务,接口,协议进程明确的划分
OSI七层模型:
通信特点:对等通信
对等通信,为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目的端的对等层进行通信,这种通信方式称为对等层通信。在每一层通信过程中,使用本层自己协议进行通信.
TCP/IP协议
物理层: 负责光电信号的传输; Ethernet; 以太网协议; 集线器(信号放大)
链路层: 负责相邻设备之间的数据帧传输; Ethernet(以太网)数据的结束开始; 交换机
网络层: 负责地址管理与路由选择; IP; 路由器
传输层: 负责端与端之间的数据传输; TCP UDP
应用层: 负责应用程序之间的数据沟通; http ftp(传输文件) SYMP DNS
在这里插入图片描述IP地址
IPV4: unit_t – 在网络唯一标识一台主机
IPV6: uchar ip[16] – 不向兼容
DHCP: 动态地址分配
NAT: 地址替换-- 实现多人共IP上网
使用点分十进制展示IP地址: 127.0.0.1 本地回环IP
每条数据包括: SRC IP DEST IP --标识了这条数据从哪来到哪去
port端口
0 ~ 65535–其中0 ~1024不推荐使用-在主机上标识一个进程
每条数据中包括: sip sport dip dport proto(五元组–标识一条通讯)

网络字节序
在下方tcpsocket.cpp程序中体现

应用层

http协议(超文本传输协议)
URl
俗称"网址"
按照网址顺序依次为:
协议方案名 -> 登录信息 -> 服务器地址 -> 服务器端口号 -> 带层次的文件夹路径 -> 查询字符串 -> 片段标识符
urlencode和urldecode
/ ? %等字符在url中被赋予特殊意义在遇到此类字符需要进行转义
转义规则:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式,例如: ‘+‘被转义为’%2B’
urldecode就是urlencode的逆过程
http协议格式
http请求

  • 首行: [方法] + [url] + [版本]
  • Header: 请求的属性,冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body: 空行后面的内容都是Body.Body允许为空字符串. 如果Body存在, 则在Header中会有一个
    Content-Length属性来标识Body的长度;

http响应

  • 首行: [版本号] + [状态码] + [状态码解释]
  • Header: 请求的属性,冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body: 空行后面的内容都是Body.Body允许为空字符串. 如果Body存在, 则在Header中会有一个 Content-Length属性来标识Body的长度;如果服务器返回了一个html页面, 那么html页面内容就是在 body中.

http的方法

最常用的是GET方法和POST方法
http的状态码
在这里插入图片描述
最常见的状态码,比如200(OK),404(Not Found), 403(Forbidden),301(永久重定向,location所指向的位置)302(Redirect,临时 重定向), 504(Gateway time-out) 500(服务器内部错误) 502(Bad Gateway)
http的常见header

  • Content-Type: 数据类型(text/html等)
  • Content-Length: Body的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的;
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
    封装实现最简单的http服务器
1 /**********************************************************
  2  * Author        : yang
  3  * Email         : wk_eternity@163.com
  4  * Last modified : 2019-07-22 15:54
  5  * Filename      : httpServer.cpp
  6  * Description   : 使用封装的tcpsocket完成实现一个最简单的http服务器 
  7 *       返回一个简单页面:
  8 *       <html><body><h1>Hello World</h1></body></html>
  9 *       http是传输层协议,在传输层使用tcp协议实现;默认端口80
 10 *       tcp服务器的基础上进行http协议数据的解析
 11 *       http是一个短连接,在http1.1中实现了长连接
 12 *       http数据的解析过程:
 13 *           1. 获取http头部(首行+头部)
 14 *               首行中包含url可以直到客户端请求什么资源,GET请求还可以获取到提交的数据
 15 *               首行中包含的协议版本:拿到版本就可以针对不同版本``的特性进行处理                                                                                     
 16 *           2. 解析头部
 17 *               可以获取到正文有多长,正文是什么类型的数据
 18 *           3. 获取正文进行处理(通常将正文交给子进程处理)
 19 *       问题:如何获取头部--如何保证获取一个完整的头部
 20  * *******************************************************/
 21 #include <iostream>
 22 #include <sstream>
 23 #include "TcpSocket.hpp"
 24 
 25 int main(int argc, char *argv[])
 26 {
 27     if (argc != 3) {
 28         std::cout<< "./httpserver ip port\n";
 29         return -1;
 30     }
 31     std::string ip = argv[1];
 32     uint16_t port = atoi(argv[2]);
 33 
 34     TcpSocket sock;
 35     CHECK_RET(sock.Socket());
 36     CHECK_RET(sock.Bind(ip, port));
 37     CHECK_RET(sock.Listen());
 38     while(1) {
 39         TcpSocket clisock;
 40         if (sock.Accept(clisock) == false) {
 41             continue;
 42         }
 43         std::string buf;
 44         clisock.Recv(buf);
 45         std::cout << "req:["<< buf <<"]\n";
 46 
 47         std::string body;
 48         body = "<html><body><h1> Everything will be ok!</h1></body></html>";
 49         std::stringstream ss;
 50         ss << "HTTP/1.1 502 Bad GateWay\r\n";
 51         ss << "Content-Length: " << body.size() <<"\r\n";
 52         ss << "Content-Type: text/html\r\n";
 53         ss << "Location: http://www.baidu.com\r\n";
 54         ss << "\r\n";
 55         std::string header = ss.str();
 56 
 57         clisock.Send(header);
 58         clisock.Send(body);
 59         clisock.Close();
 60     }
 61     sock.Close();
 62     return 0;
 63 }

TcpSocket.hpp

 | |  1 #include <stdio.h>
 | |  2 #include <string>
 | |  3 #include <string.h>
 | |  4 #include <unistd.h>
 | |  5 #include <sys/socket.h>
 | |  6 #include <stdlib.h>
 | |  7 #include <netinet/in.h>
 | |  8 #include <arpa/inet.h>
 | |  9 #include <errno.h>
 | | 10 #include <iostream>
 | | 11 //创建socket文件描述符(tcp/udp,客户端+服务端)
 | | 12 //  int socket(int domain, int type, int protocol);                                                                                                              
 | | 13 //建立连接 (TCP, 客户端)
 | | 14 //  int connect(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);
 | | 15 //绑定端口号 (TCP/UDP, 服务器)
 | | 16 //  int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
 | | 17 //  socklen_t address_len);
 | | 18 //开始监听socket (TCP, 服务器)
 | | 19 //  int listen(int socket, int backlog);
 | | 20 //建立连接 (TCP, 客户端)
 | | 21 //  int connect(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);  
 | | 22 // 接收请求 (TCP, 服务器)
 | | 23 //  int accept(int socket, struct sockaddr* address,  socklen_t* address_len);
 | | 24 //名称                  目的
 | | 25 //AF_INET           IPv4网络通信
 | | 26 //AF_INET6          IPv6网络通信
 | | 27 //AF_PACKET          链路层通信 
 | | 28 //AF_UNIX, AF_LOCAL   本地通信
 | | 29 
 | | 30 //type
 | | 31 //SOCK_ STREAM      字节流套接字
 | | 32 //SOCK DGRAM        数据报套接字
 | | 33 //SOCK_ .SEQPACKET  有序分组套接字
 | | 34 //SOCK_ RAW         原始套接字
 | | 35                                                                                                                                                                  
 | | 36 //protocol                                                                                                                                                       
 | | 37 //IPPROTO_TCP       TCP传输协议                                                                                                                                  
 | | 38 //IPPTOTO_UDP       UDP传输协议                                                                                                                                  
 | | 39 //IPPROTO_SCTP      STCP传输协议                                                                                                                                 
 | | 40 //IPPROTO_TIPCTCP   TIPC传输协议                                                                                                                                 
 | | 41                                                                                                                                                                  
 | | 42 #define CHECK_RET(q) if((q) == false){return -1;}                                                                                                                
 | | 43                                                                                                                                                                  
 | | 44 typedef struct calculator_info_t {                                                                                                                               
 | | 45     int num1;                                                                                                                                                    
 | | 46     int num2;                                                                                                                                                    
 | | 47     char str[30];                                                                                                                                                
 | | 48     char op;                                                                                                                                                     
 | | 49 }calculator_info;                                                                                                                                                
 | | 50                                                                                                                                                                  
 | | 51 class TcpSocket                                                                                                                                                  
 | | 52 {                                                                                                                                                                
 | | 53 public:                                                                                                                                                          
 | | 54     TcpSocket(): _sockfd(-1){                                                                                                                                    
 | | 55     }                                                                                                                                                            
 | | 56     void SetSockFd(int fd){                                                                                                                                      
 | | 57         _sockfd = fd;                                                                                                                                            
 | | 58     }                                                                                                                                                            
 | | 59     int GetSockFd(){                                                                                                                                             
 | | 60         return _sockfd;                                                                                                                                          
 | | 61     }                                                                                                                                                            
 | | 62     bool Socket(){                                                                                                                                               
 | | 63         _sockfd =  socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);                                                                                                      
 | | 64         if(_sockfd < 0){                                                                                                                                         
 | | 65             perror("socket errno\n");                                                                                                                            
 | | 66             return false;                                                                                                                                        
 | | 67         }                                                                                                                                                        
 | | 68         return true;
 | | 69     }                                                                     
 | | 70     bool Bind(std::string &ip, uint16_t port){                            
 | | 71         struct sockaddr_in addr;                                          
 | | 72         //struct sockaddr_in {                                            
 | | 73         //short int sin_family; //地址族,AF_xxx 在socket编程中只能是AF_IN
 | | 74         //unsigned short int sin_port; // 端口号 (使用网络字节顺序)     
 | | 75         //struct in_addr sin_addr; //存储IP地址 4字节                     
 | | 76         //unsigned char sin_zero[8]; // 总共8个字节,实际上没有什么用,只>
 | | 77         //};                                                              
 | | 78         addr.sin_family = AF_INET;                                        
 | | 79         //网络字节顺序与本地字节顺序之间的转换函数:                      
 | | 80         //    htonl()–“Host to Network Long”                          
 | | 81         //    ntohl()–“Network to Host Long”                          
 | | 82         //    htons()–“Host to Network Short”                         
 | | 83         //    ntohs()–“Network to Host Short”                         
 | | 84         addr.sin_port = htons(port);                                      
 | | 85         addr.sin_addr.s_addr = inet_addr(ip.c_str());                     
 | | 86         //数字的字节序转换: 主机字节序转换为网络字节序                    
 | | 87         //4个字节的数据                                                   
 | | 88         // uint32_t htonl(uint32_t hostlong);                             
 | | 89         //2个字节的数据(不可用4字节转换)                                  
 | | 90         // uint16_t htons(uint16_t hostshort);                            
 | | 91         //数字的字节序转换: 网络字节序转换为主机字节序                    
 | | 92         // uint32_t ntohl(uint32_t netlong);                              
 | | 93         // uint16_t ntohs(uint16_t netshort);                             
 | | 94         //将点分十进制IP地址字符串转换为网络字节序IP地址                  
 | | 95         // in_addr_t inet_addr(const char *cp);                           
 | | 96         // int inet_pton(int af, const char *src, void *dst);             
 | | 97         //将网络字节序转换为字符串点分式十进制IP地址                      
 | | 98         // const char *inet_ntop(int af, const void *src, char *dst, sockl
 | | 99         // char *inet_ntoa(struct in_addr in);                            
 | |100                                                                           
 | |101         socklen_t len = sizeof(struct sockaddr_in);
 | |102         int ret = bind(_sockfd, (const struct sockaddr *)&addr,len);
 | |103         if(ret < 0){                                                      
 | |104             perror("bind errno\n");                                       
 | |105             return false;                                                 
 | |106         }                                                                 
 | |107         return true;                                                      
 | |108     }                                                                     
 | |109     bool Listen(int backlog = 10){                                        
 | |110         // int listen(int socket, int backlog);                           
 | |111         //backlog:最大并发连接数--内核中已完成连接队列的最大节点数        
 | |112         int ret = listen(_sockfd,backlog);                                
 | |113         if(ret < 0){                                                      
 | |114             perror("listen errno\n");                                     
 | |115             return false;                                                 
 | |116         }                                                                 
 | |117         return true;                                                      
 | |118     }                                                                     
 | |119     bool Connect(std::string &ip,uint16_t port){                          
 | |120         //int connect(int sock, const struct sockaddr *addr,  socklen_t ad
 | |121         //addr: 要连接的服务器地址信息                                    
 | |122         struct sockaddr_in addr;                                          
 | |123         addr.sin_family = AF_INET;                                        
 | |124         addr.sin_port = htons(port);                                      
 | |125         addr.sin_addr.s_addr = inet_addr(ip.c_str());                     
 | |126         socklen_t len = sizeof(struct sockaddr_in);                       
 | |127                                                                           
 | |128         int ret = connect(_sockfd,(struct sockaddr*)&addr, len);          
 | |129         if(ret < 0){                                                      
 | |130             perror("connect errno\n");                                    
 | |131             return false;                                                 
 | |132         }                                                                 
 | |133         else{                                                             
 | |134             std:: cout<<"connect success\n";                              
 | |135             return true;
 | |136         }
 | |137     }                                                                     
 | |138     bool Accept(TcpSocket &csock, struct sockaddr_in *addr = NULL){       
 | |139         //在一个套接字上接受一个连接                                      
 | |140         //int accept(int s, struct sockaddr *addr, socklen_t *addrlen);   
 | |141         //addr: 客户端地址信息                                            
 | |142         //len: 输入输出参数,既要指定接受长度,还要接收实际长度             
 | |143         //返回值: 为新客户新建的socket套接字描述符                        
 | |144         //通过这个返回的描述符可以与指定的客户端进行通信                  
 | |145         struct sockaddr_in _addr;                                         
 | |146         socklen_t len = sizeof(struct sockaddr_in);                       
 | |147         int newfd = accept(_sockfd,(struct sockaddr*)&_addr, &len);       
 | |148         if (newfd < 0){                                                   
 | |149             perror("accept errno\n");                                     
 | |150             return false;                                                 
 | |151         }                                                                 
 | |152         if (addr != NULL){                                                
 | |153             memcpy(addr, &_addr, len);                                    
 | |154         }                                                                 
 | |155         csock.SetSockFd(newfd);                                           
 | |156         //_socket--仅用于接受新客户端连接请求                             
 | |157         //newfd--专门用于与客户端进行通信                                 
 | |158         return true;                                                      
 | |159     }                                                                     
 | |160     bool Recv(std::string &buf){                                          
 | |161         char tmp[4096] = {0};                                             
 | |162         //ssize_t recv(int sockfd, void *buf, size_t len, int flags);     
 | |163         //flags:0--默认阻塞接受 MSG_PEEK-获取数据但是不从缓冲区移除       
 | |164         //返回值:实际接受的数据长度 失败:-1 断开连接: 0                   
 | |165         int ret = recv(_sockfd, tmp,4096,0);                              
 | |166         if (ret < 0){                                                     
 | |167             perror("recv error");                                         
 | |168             return false;                                                 
 | |169         }
 | |170         else if (ret == 0){
 | |171             printf("peer shutdown\n");                                    
 | |172             return false;                                                 
 | |173         }                                                                 
 | |174         buf.assign(tmp, ret);                                             
 | |175         return true;                                                      
 | |176     }                                                                     
 | |177     bool Send(std::string &buf){                                          
 | |178         //int send(int s, const void *msg, size_t len, int flags);        
 | |179         int ret = send(_sockfd, buf.c_str(), buf.size(), 0);              
 | |180         if (ret < 0){                                                     
 | |181             perror("send error");                                         
 | |182             return false;                                                 
 | |183         }                                                                 
 | |184         return true;                                                      
 | |185     }                                                                     
 | |186     bool Close(){                                                         
 | |187         close(_sockfd);                                                   
 | |188         _sockfd = -1;                                                     
 | |189     }                                                                     
 | |190 //    ~TcpSocket() {}                                                     
 | |191 private:                                                                  
 | |192     int _sockfd;                                                          
 | |193 }; 

传输层

端口号
在TCP/IP协议中,用"源IP"“目的IP”“目的端口号”“协议号”,这样一个五元组来标识一个通信(用netstat -n 查看)
范围划分

  • 0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.

  • 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.

  • 一个进程是否可以bind多个端口号?
    可以因为一个进程可以打开多个文件描述符,而每个文件描述符都对应一个端口号,所以一个进程可以绑定多个端口号

  • 一个端口号是否可以被多个进程bind?
    不可以如果进程先绑定一个端口号,然后在fork一个子进程,这样的话就可以是实现多个进程绑定一个端口号,但是两个不同的进程绑定同一个端口号是不可以的
    netstat
    查看网络状态

  • n 拒绝显示别名,能显示数字的全部转化成数字

  • l 仅列出有在 Listen (监听) 的服務状态

  • p 显示建立相关链接的程序名

  • t (tcp)仅显示tcp相关选项

  • u (udp)仅显示udp相关选项

  • a (all)显示所有选项,默认不显示LINSTEN相关
    pidof
    通过进程名,查看进程ID
    UDP协议
    UDP协议端格式
    在这里插入图片描述

    16位UDP长度,表示整个数据报(UDP首部 + UDP数据)的最大长度
    如果校验和出错,就会直接丢弃
    UDP的特点
    无连接–知道对端的IP和端口直接进行传输,不需要建立连接
    不可靠–没有确认机制,没有重传机制,如果因网络故障无法传输成功,也没有任何错误信息
    面向数据报: 不能够灵活的控制读写数据的次数和数量
    面向数据报
    应用层的报文会按照原样发送,不会拆分,不会合并
    如果发送端调用一次send,发送多少,接收端必须一次调用recv接收多少,不能分次循环调用
    UDP的缓冲区
    UDP没有真正意义上的发送缓冲区,调用sendto会直接交给内核,由内核直接将数据传给网络层协议进行后续的传输动作
    UDP有接受缓冲区,但是这个接受缓冲区不能保证收到的UDP报和发送的UDP报顺序一致;如果缓冲区满了,在达到的UDP数据就会被丢弃.
    UDP的socket既能读也能写,是为全双工通讯
    UDP使用注意事项
    UDP首部有一个16位的最大长度,意味着UDP一次传输最大长度64K(包含UDP首部)
    一般来讲要在应用层进行手动分包,多次发送,在接收端手动拼装.
    基于UDP的应用层协议

  • NFS: 网络文件系统

  • TFTP: 简单文件传输协议

  • DHCP: 动态主机配置协议

  • BOOTP: 启动协议(用于无盘设备启动)

  • DNS: 域名解析协议

TCP协议

TCP全称为 “传输控制协议(Transmission Control Protocol”).
TCP协议段格式
在这里插入图片描述

  • 源/目的端口号: 表示数据从哪个进程来,到哪个进程去
  • 32位序号:32位的序列号由接收端计算机使用,重新分段的报文成最初形式。当SYN出现,序列码实际上是初始序列码(Initial Sequence Number,ISN),而第一个数据字节是ISN+1。这个序列号(序列码)可用来补偿传输中的不一致。
  • 32位确认序号:32位的序列号由接收端计算机使用,重组分段的报文成最初形式。如果设置了ACK控制位,这个值表示一个准备接收的包的序列码。
  • 4位TCP报文长度: 表示该TCP有多少个32位bit(有多少个4字节);所以TCP头部最大长度是15 * 4 = 60
  • 6位标志位:
    URG: 紧急指针是否有效
    ACK: 确认号是否有效
    PSH: 提示接收端应用程序立即从TCP缓冲区把数据读走
    RST: 对方要求重新建立连接,携带RST标识的称为复位报文段
    SYN: 请求建立连接,带有SYN标识的称为同步报文段
    FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
  • 16位窗口大小:用来表示想收到的每个TCP数据段的大小。TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接收的字节。窗口大小是一个16字节字段,因而窗口大小最大为65535字节。
  • 16位校验和:发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也包含TCP数据部分.
  • 16位紧急指针:标识哪部分数据是紧急数据;
    三次握手四次挥手
    在这里插入图片描述

三次握手:
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

四次挥手
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。

(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。

(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。

(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

可靠传输

确认应答(ACK)机制
协议字段中的序号/确认序号
因为TCP为了实现可靠传输牺牲了部分传输性能,并且可能因为ACK确认应答丢失也要重传数据.
每一个ACK都带有对应的确认序列号,即为告诉发送端,收到哪些数据,下一次从哪开始发
超时重传机制
前提: 每条数据的确认回复都必须按序回复,若前面的数据没有收到,则不会对后面的数据进行回复(乱序的情况)
1.意味着: 若收到一条回复,表示ack确认序号之前的数据全部安全到达,不会因为前面数据的ACK丢失而重传数据
2.若前面数据丢失,则接受党收到后发的数据,立即重发请求,并且连发三次,若发送方连续收到三条重传请求,则认为数据丢失,进行重传
连接管理机制
TIME_WAIT状态
在server的TCP连接没有完全断开之前不允许重新监听, 某些情况下可能是不合理的

  • 服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很大数量的客户端来请求).
  • 这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务器端主动清理掉), 就会产生大量TIME_WAIT连接.
  • 由于我们的请求量很大, 就可能导致TIME_WAIT的连接数很多, 每个连接都会占用一个通信五元组(源ip,源端口, 目的ip,目的端口, 协议). 其中服务器的ip和端口和协议是固定的. 如果新来的客户端连接的ip和端口号和TIME_WAIT占用的链接重复了, 就会出现问题.
    使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符
    滑动窗口
    一次发送多条数据
  • 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值.
  • 发送第一个窗口,不需要等待任何ACK, 直接发送;
  • 收到第一个ACK后,滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;
  • 操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区来记录当前还有哪些数据没有应答; 只有确 认应答过的数据, 才能从缓冲区删掉;
  • 窗口越大, 则网络的吞吐率就越高;

流量控制
为防止接收端缓冲区因处理数据速度有限而被打满,引入流量控制
TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;
窗口大小字段越大, 说明网络的吞吐量越高;
接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
发送端接受到这个窗口之后, 就会减慢自己的发送速度;
如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数
据段, 使接收端把窗口大小告诉发送端.
注意 TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位;为扩展16位窗口字段
拥塞控制
拥塞控制为了避免因为网络状况不好导致通讯初始,大量的数据报丢失重传,降低性能
慢启动, 快增长
发送端控制一个拥塞窗口大小, 在进行数据传输时候, 进行网络探测式的发送,若网络状况良好则发送的数据快速增长,达到阈值(窗口大小)时,则不再继续增长; 若传输过程中丢包,则重新初始化拥塞窗口

延迟应答
接收方收到数据后并不立即进行确认回复,而是等待一段时间; 因为这段时间内,有可能用户已经RECV将缓冲区中的数据取走,窗口就可以尽可能的保证最大大小;保证传输吞吐量
捎带应答(应用场景不太多)
接收方对每一条数据的确认回复,都需要发送一个TCP数据包; 但是空报头的传输会降低性能.
因此会考虑在即将要发送的数据包中包含有确认信息(可以少发一个确认的空报头)
捎带应带机制(产生场景不太多)
分区
面向字节流
创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区和一个接收缓冲区;

  • 调用write时, 数据会先写入发送缓冲区中;
  • 如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
  • 如果发送的字节数太短,就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
  • 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区; 然后应用程序可以调用read从接收缓冲区拿数据;
  • TCP的一个连接,既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可 以写数据. 为全双工

粘包问题
包:应用层中的数据包
TCP中没有UDP中的"报文长度"字段,但是有序号字段
传输层: TCP是根据报文依次按照序号存入缓冲区
应用层: 只是一连串的字节数据
所以从应用层角度来说不知道数据包的开始结束位置.
解决粘包问题(明确两个包的边界)

  • 对于定长的包, 保证每次都按固定大小读取即可; 例如上面的Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
  • 对于变长的包, 可以在包头的位置, 约定一个包总长度的字段,从而就知道了包的结束位置;
  • 对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔

TCP异常情况
进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN. 和正常关闭没有什么区别.
机器重启: 和进程终止的情况相同.
机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset. 即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.
应用层的某些协议, 也有一些这样的检测机制. 例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接.
基于TCP应用层协议
HTTP
HTTPS
SSH
Telnet
FTP
SMTP

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值