当前位置: 首页 > article >正文

网络(二)协议

目录

1. 协议了解;

2. HTTP协议;

3.UDP协议;

4.TCP协议;


1. 协议认识:

1.1 协议:

        为了使数据在网络上能够从源到达目的,网络通信的参与方必须遵循相同的规则.

1.2 序列化和方序列化:

序列化: 将数据转化成可以存储或者传输的形式, 

方序列化: 将包装好的形式, 拆解为数据的.

序列化和方序列化的目的是: 方便网络数据的发送和接收, 方序列化将转化成二进制的数据变回原来的数据形式供给上层进行读取.

 1.3 实现协议:

(1) 服务器初始化:

        这里写一个网络计算器来实现. 服务器的实现: 创建套接字, 绑定, 监听, 连接的工作.

int main(int argc, char* argv[])
{
	if (argc != 2)
    {
		cerr << "Usage: " << argv[0] << " port" << endl;
		exit(1);
	}
	int port = atoi(argv[1]);

	//创建套接字
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock < 0){
		cerr << "socket error!" << endl;
		exit(2);
	}

	//绑定
	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);
	
	if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
		cerr << "bind error!" << endl;
		exit(3);
	}

	//监听
	if (listen(listen_sock, 5) < 0){
		cerr << "listen error!" << endl;
		exit(4);
	}

	//启动服务器
	struct sockaddr peer;
	memset(&peer, 0, sizeof(peer));
	for (;;)
    {
		socklen_t len = sizeof(peer);
		int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
		if (sock < 0){
			cerr << "accept error!" << endl;
			continue;
		}
		pthread_t tid = 0;
		int* p = new int(sock);
		pthread_create(&tid, nullptr, Routine, p);
	}
	return 0;
}
(2) 协议规定:

        请求和响应的协议进行自定义.

typedef struct request
{
    int x;//左操作数
    int y;//右操作数
    char op;//操作符
}request_t;


typedef struct response
{
    int code;//计算状态;
    int result;//计算结构;
}response_t;
(3) 客户端:

发送消息: sockfd:套接字, buf: 发送的数据, len:发送字节数, flag: 发送方式.

接收消息:

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        cerr << "Usag: " << argv[0] << "server_ip server_port" << endl;
        exit(1);
    }
    string server_ip = argv[1];
    int server_port = atoi(argv[2]);

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        cerr << "socket error!" << endl;
        exit(2);
    }

    struct sockaddr_in peer;
    memset(&peer, '\0', sizeof(peer));
    peer.sin_family = AF_INET;
    peer.sin_port = htons(server_port);
    peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
    if(connect(sock, (struct sockaddr*)&peer, sizeof(peer)) < 0)
    {
        cerr << "connect failed!" << endl;
        exit(3);
    }

    while(true)
    {
        request_t rq;
		cout << "请输入左操作数# ";
		cin >> rq.x;
		cout << "请输入右操作数# ";
		cin >> rq.y;
		cout << "请输入需要进行的操作[+-*/%]# ";
		cin >> rq.op;
        send(sock, &rq, sizeof(rq), 0);
		
		//接收请求响应
		response_t rp;
		recv(sock, &rp, sizeof(rp), 0);
		cout << "status: " << rp.code << endl;
		cout << rq.x << rq.op << rq.y << "=" << rp.result << endl;
    }
    return 0;
}
(4) 服务器执行:
void* Routine(void* arg)
{
    pthread_detach(pthread_self());
    int sock = *(int*)arg;
    delete (int*)arg;

    while(true)
    {
        request_t rq;
        ssize_t size = recv(sock, &rq, sizeof(rq), 0);
        if(size > 0)
        {
            response_t rp = {0, 0};
            switch(rq.op)
            {
            case '+':
				rp.result = rq.x + rq.y;
				break;
			case '-':
				rp.result = rq.x - rq.y;
				break;
			case '*':
				rp.result = rq.x * rq.y;
				break;
			case '/':
				if (rq.y == 0)
                {
					rp.code = 1; //除0错误
				}
				else
                {
					rp.result = rq.x / rq.y;
				}
				break;
			case '%':
				if (rq.y == 0)
                {
					rp.code = 2; //模0错误
				}
				else
                {
					rp.result = rq.x % rq.y;
				}
				break;
			default:
				rp.code = 3; //非法运算
				break;
            }
            send(sock, &rp, sizeof(rp), 0);
        }
        else if(size == 0)
        {
            cout << "service done" << endl;
            break;
        }
        else
        {
            cout << "read error" << endl;
            break;
        }
    }
    close(sock);
    return nullptr;
}

 2. HTTP协议:

2.1  介绍:

        是一个超文本传输协议, 一个请求应答的协议.

(1) URL:

        同一资源定位符, 一般说的网址就是这个, 是因特网的万维网服务程序上用于指定信息位置的表示方法。

协议方案名: 代表请求时候使用的协议, http/https/dns等协议.

登录消息: 登录认证信息, 包括用户名和密码. 这部分一般都是省略的.

服务器地址: 也就是域名, WWW.baidu.com, 一般是使用ip地址进行标识但是不利于用户进行使用DNS进行域名解析得到ip.

服务器端口号: 给服务器绑定对应的端口号找到对应的进程. 一般省略.

带层次的文件路径:  要访问的资源所在的路径.

查询字符串: 是请求时提供的额外的参数,这些参数是以键值对的形式,通过&符号分隔开的

片段标示符: 是片段标识符,是对资源的部分补充.

 2.2 HTTP协议的格式:

        HTTP是基于应用层服务, 也是采用c/s模式, 就是client和server.请求和响应的工作模式.

int main()
{
	//创建套接字
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock < 0){
		cerr << "socket error!" << endl;
		return 1;
	}
	//绑定
	struct sockaddr_in local;
	memset(&local, 0, sizeof(local));
	local.sin_family = AF_INET;
	local.sin_port = htons(8081);
	local.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
		cerr << "bind error!" << endl;
		return 2;
	}
	//监听
	if (listen(listen_sock, 5) < 0){
		cerr << "listen error!" << endl;
		return 3;
	}
	//启动服务器
	struct sockaddr peer;
	memset(&peer, 0, sizeof(peer));
	socklen_t len = sizeof(peer);
	for (;;)
    {
		int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
		if (sock < 0)
        {
			cerr << "accept error!" << endl;
			continue;
		}
		if (fork() == 0)
        { //爸爸进程
			close(listen_sock);
			if (fork() > 0)
            { //爸爸进程
				exit(0);
			}
			//孙子进程
			char buffer[1024];
			recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
			cout << "--------------------------http request begin--------------------------" << endl;
			cout << buffer << endl;
			cout << "---------------------------http request end---------------------------" << endl;
			
			close(sock);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

int main()
{
	//创建套接字
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock < 0){
		cerr << "socket error!" << endl;
		return 1;
	}
	//绑定
	struct sockaddr_in local;
	memset(&local, 0, sizeof(local));
	local.sin_family = AF_INET;
	local.sin_port = htons(8081);
	local.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
		cerr << "bind error!" << endl;
		return 2;
	}
	//监听
	if (listen(listen_sock, 5) < 0){
		cerr << "listen error!" << endl;
		return 3;
	}
	//启动服务器
	struct sockaddr peer;
	memset(&peer, 0, sizeof(peer));
	socklen_t len = sizeof(peer);
	for (;;){
		int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
		if (sock < 0){
			cerr << "accept error!" << endl;
			continue;
		}
		if (fork() == 0){ //爸爸进程
			close(listen_sock);
			if (fork() > 0){ //爸爸进程
				exit(0);
			}
			//孙子进程
			char buffer[1024];
			recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
			cout << "--------------------------http request begin--------------------------" << endl;
			cout << buffer << endl;
			cout << "---------------------------http request end---------------------------" << endl;
			
#define PAGE "index.html" //网站首页
			//读取index.html文件
			ifstream in(PAGE);
			if (in.is_open()){
				in.seekg(0, in.end);
				int len = in.tellg();
				in.seekg(0, in.beg);
				char* file = new char[len];
				in.read(file, len);
				in.close();
				
				//构建HTTP响应
				string status_line = "http/1.1 200 OK\n"; //状态行
				string response_header = "Content-Length: " + to_string(len) + "\n"; //响应报头
				string blank = "\n"; //空行
				string response_text = file; //响应正文
				string response = status_line + response_header + blank + response_text; //响应报文
				
				//响应HTTP请求
				send(sock, response.c_str(), response.size(), 0);

				delete[] file;
			}
			close(sock);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

2.3 HTTP常见的方法:

比较常用就是GET和POST方法:

GET: 是通过url进行传输参数, 参数传递有限, 在url数据私密性差, 

POST: 是通过正文传递参数的, post可以传递很多参数, post在正文里面相对数据私密一些, 

 2.4 HTTP的状态码:

        200是ok, 404是not find, 403是请求权限不够, 302重定向.

 

int main()
{
	//创建套接字
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock < 0){
		cerr << "socket error!" << endl;
		return 1;
	}
	//绑定
	struct sockaddr_in local;
	memset(&local, 0, sizeof(local));
	local.sin_family = AF_INET;
	local.sin_port = htons(8081);
	local.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
		cerr << "bind error!" << endl;
		return 2;
	}
	//监听
	if (listen(listen_sock, 5) < 0){
		cerr << "listen error!" << endl;
		return 3;
	}
	//启动服务器
	struct sockaddr peer;
	memset(&peer, 0, sizeof(peer));
	socklen_t len = sizeof(peer);
	for (;;){
		int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
		if (sock < 0){
			cerr << "accept error!" << endl;
			continue;
		}
		if (fork() == 0){ //爸爸进程
			close(listen_sock);
			if (fork() > 0){ //爸爸进程
				exit(0);
			}
			//孙子进程
			char buffer[1024];
			recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
			cout << "--------------------------http request begin--------------------------" << endl;
			cout << buffer << endl;
			cout << "---------------------------http request end---------------------------" << endl;
			
			//构建HTTP响应
			string status_line = "http/1.1 307 Temporary Redirect\n"; //状态行
			string response_header = "Location: https://www.csdn.net/\n"; //响应报头
			string blank = "\n"; //空行
			string response = status_line + response_header + blank; //响应报文
			
			//响应HTTP请求
			send(sock, response.c_str(), response.size(), 0);

			close(sock);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

2.6 常见HTTP的Header:

Host: 需要告诉http对应的服务器的ip和port. 给其他服务器进行使用.

User-Agent:代表的是客户端对应的操作系统和浏览器的版本信息。

Referer: 代表的是你当前是从哪一个页面跳转过来的

 2.7 cookie和session:

        HTTP是无状态协议, 在进行用户输入密码时候为啥不需要重复输入, 因为使用cookie进行实现的. 只需要第一次进行输入用户名和密码就可以进行设置cookie. 这样用户账号和密码就保存在cookie中了,  后续所有认证都是自动认证了.

        SessionID: 一般单独使用cookie是很不安全的, 就需要额外使用sessionID, 浏览器认证通过之后给一个sessionID, 每当服务器认证的时候都会使用sessionID进行比较.

上述的cookie和sessionID都有被被盗走的可能, 这样有ip地址, 如果短时间ip跨区域很快就容易被识别出来, 以及对于更高权限需要重新输入密码和账户, 进行重新认证, 其次就是使用sessionID也有过期机制的.

 

int main()
{
	//创建套接字
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock < 0){
		cerr << "socket error!" << endl;
		return 1;
	}
	//绑定
	struct sockaddr_in local;
	memset(&local, 0, sizeof(local));
	local.sin_family = AF_INET;
	local.sin_port = htons(8081);
	local.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
		cerr << "bind error!" << endl;
		return 2;
	}
	//监听
	if (listen(listen_sock, 5) < 0){
		cerr << "listen error!" << endl;
		return 3;
	}
	//启动服务器
	struct sockaddr peer;
	memset(&peer, 0, sizeof(peer));
	socklen_t len = sizeof(peer);
	for (;;){
		int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
		if (sock < 0){
			cerr << "accept error!" << endl;
			continue;
		}
		if (fork() == 0){ //爸爸进程
			close(listen_sock);
			if (fork() > 0){ //爸爸进程
				exit(0);
			}
			//孙子进程
			char buffer[1024];
			recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
			cout << "--------------------------http request begin--------------------------" << endl;
			cout << buffer << endl;
			cout << "---------------------------http request end---------------------------" << endl;

#define PAGE "index.html" //网站首页
			//读取index.html文件
			ifstream in(PAGE);
			if (in.is_open()){
				in.seekg(0, in.end);
				int len = in.tellg();
				in.seekg(0, in.beg);
				char* file = new char[len];
				in.read(file, len);
				in.close();

				//构建HTTP响应
				string status_line = "http/1.1 200 OK\n"; //状态行
				string response_header = "Content-Length: " + to_string(len) + "\n"; //响应报头
				response_header += "Set-Cookie: huajiahhh\n"; //添加Set-Cookie字段
				string blank = "\n"; //空行
				string response_text = file; //响应正文
				string response = status_line + response_header + blank + response_text; //响应报文
				
				//响应HTTP请求
				send(sock, response.c_str(), response.size(), 0);

				delete[] file;
			}
			close(sock);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

2.8 HTTPS:

        在应用层和传输层加了一个加密层, 只有在应用层没有加密, 下层都加密了.

对称加密和非对称加密: 后面专门详细解答这个加密相关的知识.

 3. UDP协议:

3.1  传输层:

        确保数据可以可靠到达目的地址,  TCP/IP协议中的五元组来确定主机的一个进程, 含有源ip地址, 源端口号, 目的ip地址, 目的端口号, 协议号.

  • 先提取出数据当中的目的IP地址和目的端口号,确定该数据是发送给当前服务进程的。
  • 然后提取出数据当中的协议号,为该数据提供对应类型的服务。
  • 最后提取出数据当中的源IP地址和源端口号,将其作为响应数据的目的IP地址和目的端口号,将响应结果发送给对应的客户端进程。
 端口号和协议号:

端口号: TCP/UDP报文中, 长度16位置, 是用来标识一个主机的进程的. 位于传输层.

协议号:  存在与ip报头中, 进行指明传递给那种协议, 让ip找到将数据给传输层的哪个协议. 长度是8位, 存在传输层和网络层之间.

 iostat命令和pidof命令:

        iostat用于输出磁盘IO和CPU的统计信息

-c: cpu数据;

-d: 磁盘数据;

-x: 详细详细;

-V: 版本信息;

-p: 磁盘分区情况;

        pidof: 使用进程显示进程id.

 3.2 介绍UDP协议:        

        UDP协议格式: 16位源端口号: 数据从哪里来, 16位目的端口号:数据到哪里去,

16位udp长度: 数据加报文的长度, 16位udp检验和: UDP报文的检验和出错,就会直接将报文丢弃. 加上数据. 

UDP如何决定将有效载荷交付给上层的哪一个协议?

        因为udp有端口号, 可以通过目的端口号找到应用层对于应用的进程id. 报文分用就是根据报文前8个字节找到目的端口号, 再找到对应上层的进程id进行交付的.

 3.2 UDP的特点:

        无连接: 只要绑定之后直接可以进行通信, 不需要像tcp一样进行连接.

        不可靠: 没有tcp的超时重传, 确认机制, 无法在网络出现问题的时候给应用层进行错误信息.

        面向数据报: 只能按照数据一块块读取, 无法根据字节流去读取, 就无法控制信息的数量和次数. 数据报: 有多长的报文就原样发送, 不会进行拆分, 也不会合并.

 3.3 UDP缓冲区:

        UDP没有发送缓冲区, 直接发送给内核再给网络协议进行处理;  但是有接收缓冲区, 不能保证收到的UDP报的顺序和发送UDP报的顺序一致, 如果缓冲区满了就抛弃了. UDP既可以写也可以读就是全双工的.

3.4 UDP报文大小:

        UDP: 16位大小, 2^16就是65536字节, 然后除1024就是64k.如果小于64k使用udp比较合适, 大于就要分包使用了.

 3.5 UDP的应用:

NFS:网络文件系统。

TFTP:简单文件传输协议。

DHCP:动态主机配置协议。

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

DNS:域名解析协议。

 

4.TCP协议:

4.1 介绍TCP:

        TCP是传输控制协议, HTTP、HTTPS、FTP、SSH,甚至MySQL底层使用的也是TCP.

为啥已经有TCP协议这种可靠的协议了还现需要UDP协议呢?

        虽然TCP很可靠, 但是TCP需要更多的成本来维护其数据的可靠性, 来维护不可靠的因素, UDP不可靠传输, 意味着数据可以传输, 可靠性无法保证, 但是UDP实现比较简单, 维护起来也容易, 成本较低, TCP虽然靠谱但是可能效率没有UDP高的, 比较简单传输要求不需要那么准确的可以使用UDP, 要求传输数据可靠性高的就使用TCP, 只是使用场景的区别, 没有好坏区别.

 4.2 TCP协议的格式:

源端口号/目的端口号: 数据从哪里来到哪里去;

序号和确认序号: TCP报文当中字节的编号和对方的确认编号, 保证数据的可靠性.

首部长度: TCP报头长度.

窗口大小: 保证TCP可靠性机制和效率提升机制的重要字段.

检验和: 采用CRC校验。接收端校验不通过,则认为接收到的数据有问题.

6位标志位: URG: 紧急指针是否有效。 ACK: 确认序号是否有效。

PSH: 提示接收端应用程序立刻将TCP接收缓冲区当中的数据读走。

RST: 要求对方重新建立连接.

SYN: 请求与对方建立连接.

FIN: 通知对方,本端要关闭.

 4.3 序号和确认序号:

        数据的可靠性是保证发送出去的数据被接收到的信息返回了. 确认应答机制就是TCP保证数据可靠性的关键.

32位序号: 如果信息都是串行那么效率必定很慢, 并行的话那么数据到达对端主机时间肯定不一样回来响应也不一样. 顺序就不会一样的, 那么就是使用序号来进行保证顺序性.

如果3000个数据的话, 那么使用三个TCP报文.  编号就是0-1001-2001-3001.

32位确认序号: 告诉对端哪些数据我收到了, 你的数据下次从哪里开始发起.

确认序号就是1001, 2001, 3001. 来确认1-1000, 1001-2000, 2001-3000数据我收到了.

如果报文丢失了怎么办?

如果1001到2000丢失了, 那么只收到1-1001就是确认序号, 给主机提示下次从1001开始发送消息进行重新传送.

为啥要有两套确认应答机制?

        因为TCP是全双工的, 双方都有消息想要发给对方就采用两套确认应答机制, 一套无法满足对方都发消息呢. 可以判断哪段报文的丢失.

4.4 窗口大小:

        是TCP的发送缓冲区和接收缓冲区; 当调用write/send是先写入发送缓冲区, 在read/recv先是放到接收缓冲区, read/write也不是在磁盘中读数据, 而是缓冲区中读取数据. 如何发送和接收就是TCP主机控制的. 

为啥要有发送缓冲区和接收缓冲区?

传输的过程中可能会发生错误就需要发送缓冲区暂时保存数据, 进行超时重传, 等到数据被完全接收后就进行新的数据覆盖.

接收缓冲区就是和UDP一样不浪费资源和数据, 进行保存读取.

窗口大小:

        就是当前主机接收数据的大小, 告诉当前主机接收缓冲区的容量还有多少. 可以根据窗口大小来调整发送的速度. 窗口越大接收能力强, 可加快速度进行读取数据, 窗口小反之. 其次就是窗口为0就满了.

 4.5 6个标志位:

        除了发送的普通消息, 还有建立连接, 断开连接等消息, 六个标志位只需要0/1标识就是6位数据1个字节即可.

SYN: 一个连接建立的请求报文;

ACK: 对收到的报文进行确认;

FIN: 一个连接断开的请求报文;

ROG: 来找到紧急数据;

PSH: 对方尽快将你的接收缓冲区当中的数据交付给上层;

RST: 重新建立连接;

 4.6 确认应答机制和超时重连机制:

        确认应答机制: 将每个字节的数据都进行编号, 使用序号和确认序号例如数组下标一样, 进行确认收到信息, 收到那么下标就可以找到, 没收到返回确认序号进行超时重传.

        超时重传机制: 一般是有一个定时器, 如果没有收到确认应答报文就进行重新传递信息.

丢包: 发送端丢了和接收端丢了的两种可能. 超时重传接收方收到重复信息也会根据确认序号进行排除.

 4.7 连接管理机制:

        TCP是面向连接的, 那么就有三次握手和四次挥手. 上一篇有具体步骤.

为啥要三次握手?

        三次握手是验证双方通信信道的最小次数, 第三次是要确认第二次发送的应答和建立连接是被收到的. 这是最小需要的通信握手次数. 而且第一次可以证明发送端可以正常发, 第三次可以证明接收端可以收和发送, 第三次可以证明发送端可以收的能力. 最重要的是第三次握手可以保证出问题在客户端, 不会影响服务器. 因为服务器只有等第三次客户端发来响应才可以建立主机的连接, 丢包的话客户端维持一个短暂的连接, 将风险转移给客户端.

三次握手的状态变化:

        开始都是closed状态; 服务器接收客户端信息就是LISTEN状态, 客户端第一次握手变成SYN_SENT, 第二次握手服务器变SYN_RCVD状态, 第二次客户端接收到响应变成ESTABLISH状态, 最后第三次握手服务器也变成ESTABLISH状态.

为啥要四次挥手?

        TCP是全双工的, 不仅要关闭客户端, 服务器也要关闭. 第二次和第三次不可以一起, 服务器不一定会马上发起第三次挥手,因为服务器可能还有某些数据要发送给客户端,只有当服务器端将这些数据发送完后才会向客户端发起第三次挥手. 

四次挥手的变化:

        开始都是ESTABLISH状态, 客户端第一次挥手变成客户端的状态变为FIN_WAIT_1, 服务器收到客户端消息之后变成CLOSE_WALT. 

当服务器没有消息给客户端就发送ACK给客户端变成LAST_ACK, 客户端接收到变成FIN_WAIT_2, 第三次挥手将最后响应给服务器变成TIME_WAIT, 最后服务器收到进行关闭, 客户端一段时间后也关闭了. 第三次挥手减少资源浪费, 大量close_wait占用资源,需要进行彻底释放资源. TIME_WAIT客户端没有立刻closed因为如果最后一次挥手没收到还可以进行超时重传.

 4.8 流量控制和滑动窗口.

        流量控制: 根据接收端的解释能力来进行控制发送, 就是流量控制, 窗口大小就是进行发送数据的控制. 第一次如何找到对方的窗口大小, 在三次握手时候就找到对方的接收能力了. 

        滑动窗口: 连续发送多个数据, 发送缓冲区中可以将数据进行并发发送, 使用到滑动窗口来维护已发和未发数据. 滑动窗口的大小和对方窗口大小和自己拥塞窗口大小的最小值.

滑动窗口实现是使用数组和两个指针进行的.

快重传: 当发送端连续收到三次同样的应答就会触发快重传, 不用定时器直接进行传递.

超时重传: 如果没有进行三次重复应答, 就只能使用超时重传, 那么就需要进行定时器触发执行重传.

 4.9 拥塞控制:

        出现少量丢包是很正常的, 但是如果大量丢包就不正常了, 就可能是网络问题或者其他的.

前面流量控制: 保证对端接收能力, 防止缓冲区被打满, 丢失数据;

滑动窗口: 将数据进行并行发送, 提高发送的效率和可靠性.

拥塞控制: 网路问题时候, 可能导致网络拥塞, 数据发送缓慢.

解决方法: 先停止发送数据, 然后进行慢启动, 进行探路, 再发送数据, 确定网络正常之后再进行指数增长发送数据, 之后再进行线性增长如此循环.

 4.10 延迟应答和捎带应答:

         延迟应答: 在窗口大小比较小的时候, 等待一段时间给窗口大小变大些在进行数据传输, 就是延迟应答, 提高数据传输效率, 

        捎带应答: 发送响应的时候ACK可以等一起进行发送过去, 做顺风车的形式就是捎带应答.

 4.11 面向字节流和粘包问题:

        TCP是发送以字节流的形式, 将数据变成一个个字节的形式发送到对方缓冲区.

粘包问题: 就是由于面向字节流, 在读取数据的时候, 都是一串串的字节流, 不知道数据的开始和结束就是粘包问题.

问题解决: 定长字节流或者变长字节流加正文长度, 或者包和包之间有明确特殊符号.

 4. 12 TCP异常情况:

(1) 进程终止: 客户端突然进程终止, 进程终止释放文件描述符, TCP底层还是可以发送FIN.正常进行四次挥手.

(2) 机器重启: 操作系统会进行杀掉全部进程, 也是正常四次挥手.

(3) 掉线/网络断开: 短时间之内不知道客户端断开连接了, 但是也不会一直进行连接, 多次发送ACK如果没有应答就关闭.

 4.13 TCP对应状态:

        为啥之前服务器退出但是还是不能绑定端口, 就是TIME_WAIT还没有结束, 释放资源, 要服务器接收客户端最后的ACK才可以真正释放.

listen的第二个参数: num, 全连接队列, 对于num+1个客户端请求就是服务器变SYN_RCVD, 一直没有给客户端进行响应. 就是没有被accept调用的客户端.

为啥要维护连接队列?

        因为处理多个客户端的请求, 直接向上连接即可, 不需要进行等待客户端的请求再进行连接.

 4.14 TCP和UDP区别:

        UDP: 用户数据报协议, 一种无需建立连接的、不可靠的、面向数据报的传输层通信协议。无连接绑定就可以发送数据.

        TCP: 传输控制协议, 一种面向连接的、可靠的、基于字节流的传输层通信协议,  出现丢包, 乱序问题都可以解决.


http://www.kler.cn/a/516687.html

相关文章:

  • 在 Windows 11 中为 SMB 3.x 文件共享协议提供 RDMA 支持
  • el-dialog内容大于高度时可滑动
  • 百度APP iOS端磁盘优化实践(上)
  • BGP(1)邻居建立,路由宣告
  • easyexcel读取写入excel easyexceldemo
  • 计算机网络之物理层
  • GIT的常规使用
  • 【MySQL — 数据库增删改查操作】深入解析MySQL的create insert 操作
  • docker 启动镜像命令集合
  • Java 大视界 -- Java 大数据中的异常检测技术与应用(61)
  • ESP8266 OTA固件启动日志里分区解析【2M flash】
  • 【Java实现 通过Easy Excel完成对excel文本数据的读写】
  • 递归的本质
  • Rman还原
  • Yii框架中的Cart组件:实现购物车功能
  • GC(垃圾回收)的分类
  • 使用 Elasticsearch 导航检索增强生成图表
  • linux-centosubuntu本地源配置
  • 蓝桥杯练习日常|c/c++竞赛常用库函数
  • 使用Python爬虫获取1688店铺所有商品信息的完整指南
  • C#高级:常用的扩展方法大全
  • ubuntu系统docker环境搭建
  • STM32调试手段:重定向printf串口
  • 重载C++运算符
  • salesforce FIELD_FILTER_VALIDATION_EXCEPTION
  • LVGL+FreeRTOS实战项目:智能健康助手(蓝牙模块篇)