# issue 7 TCP回声服务器和客户端

一、TCP/IP协议栈

为什么需要理解协议栈?

学习C/C++就是要懂底层的原理。不然永远是"调包侠"

根据数据传输方式的不同,基于网络协议(这里是指基于TCP/IP协议)的套接字一般分为TCP 套接字和UDP 套接字。因为TCP 套接字是面向连接的,因此又称基于流(stream)的套接字。 TCP 是TransmissionControl Protocol(传输控制协议)的简写,意为"对数据传输过程的控制"。

第一层次:数据链路层

如图所示:链路层是物理链接领域标准化的结果,也是最基本的领域,专门定义LAN、WAN、MAN 等网络标准。若两台主机通过网络进行数据交换,则需要下图所示的物理连接,

链路层就负责这些标准。

第二层次:IP 层

准备好物理连接后就要传输数据。为了在复杂的网络中传输数据,首先需要考虑路径的

选择。向目标传输数据需要经过哪条路径?解决此问题就是IP 层,该层使用的协议就是IP。

IP 是面向消息的、不可靠的协议。每次传输数据时会帮我们选择路径,但并不一致。如

果传输中发生路径错误,则选择其他路径;但如果发生数据丢失或错误,则无法解决。换言

之,IP 协议是无法应对数据错误的。因此,又要下放一层。

第三层次:TCP/UDP 层

IP 层解决数据传输中的路径选择问题,只需照此路径传输数据即可。TCP 和UDP 层以

IP 层提供的路径信息为基础完成实际的数据传输,故该层又称传输层(Transport)。

IP 层只关注1 个数据包(数据传输的基本单位)的传输过程。因此,即使传输多个数据

包,每个数据包也是由IP 层实际传输的,也就是说传输顺序及传输本身是不可靠的。若只

利用IP 层传输数据,则有可能导致后传输的数据包B 比先传输的数据包A 提早到达。另外,

传输的数据包A、B、C 中有可能只收到A 和C,甚至收到的C 可能已损毁。

若添加TCP 协议则按照下图的对话方式进行数据交换。

第四层次:应用层

前面三个层次,套接字通信过程中都是自动处理的。为了"使程序员从这些细节中解放

出来"。选择数据传输路径、数据确认过程都被隐藏到套接字内部。前面三个层次都是为了

给应用层提供服务的。

二、TCP服务端和代码实现

服务的创建的基本流程(最简单的)

cpp 复制代码
void run_client()
{
	int client = socket(PF_INET, SOCK_STREAM, 0);
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr)); //清零 防止意外
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	servaddr.sin_port = htons(33005);
	int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));
	if (ret == 0) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		char buffer[1024] = "hello , here is client\n";
		write(client, buffer, strlen(buffer));
		memset(buffer, 0, sizeof(buffer));
		read(client, buffer, sizeof(buffer));
		std::cout << buffer;
	}
	else {
		printf("%s(%d):%s       %d\r\n", __FILE__, __LINE__, __FUNCTION__, ret);
	}
	close(client);
	std::cout << "client done!" << std::endl;
}
void lession63_() {
	int server, client;
	struct sockaddr_in seraddr, cliaddr; //服务端和客户端的地址结构体
	socklen_t cliaddrlen;//客户端的地址结构体大小
	//const char* message = "hello world!\n";

	//1.创建服务器套接字
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
	server = socket(PF_INET, SOCK_STREAM, 0);//TCP协议  什么是TCP,就是IPv4中面向了流的套接字
	if (server < 0) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		std::cout << "create socket failed!" << std::endl;
		return;
	}

	//2.绑定服务器套接字和地址结构体

	// 设置SO_REUSEADDR选项,允许端口复用



	memset(&seraddr, 0, sizeof(seraddr));//清零
	seraddr.sin_family = AF_INET;        //地址协议族
	seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	seraddr.sin_port = htons(33005);




	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
	int on = 1;
	if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
		perror("setsockopt");
		close(server);
		return;
	}
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);

	int ret = bind(server, (struct sockaddr*)&seraddr, sizeof(seraddr));
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
	if (ret == -1) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		std::cout << "bind failed!" << std::endl;
		close(server);
		return;
	}
	ret = listen(server, 3);
	if (ret == -1) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		std::cout << "listen failed!" << std::endl;
		close(server);
		return;
	}
	char buffer[1024];
	while (1) {
		memset(buffer, 0, sizeof(buffer));
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		cliaddrlen = sizeof(cliaddr);
		client = accept(server, (struct sockaddr*)&cliaddr, &cliaddrlen/*&cliaddrlen*/);
		if (client == -1) {
			std::cout << "accept failed!" << std::endl;
			close(server);
			return;
		}
		//printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		read(client, buffer, sizeof(buffer));
		ssize_t len = write(client, buffer, strlen(buffer)); //返回值就是实际写入的字节数
		if (len != (ssize_t)strlen(buffer)) {
			std::cout << "write failed!" << std::endl;
			close(server);
			return;
		}
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		close(client);
	}
	close(server);
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);

}
void lession63()
{
	pid_t pid = fork();
	if (pid == 0) {
		//开启客户端
		sleep(2);//让服务端先启动	
		run_client();
		run_client();
	}
	else if (pid > 0) {
		//开启服务端
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		lession63_();
		int status = 0;
		wait(&status);//避免子进程成为僵尸进程
	}
	else {
		std::cout << "fork failed!" << pid << std::endl;
	}
}

三、connect函数和TCP客户端

connect函数:

#include<sys/socket.h>
int connect(int sock,struct sockaddr*servaddr, socklen_t addrlen);

→成功时返回0,失败时返回-1。

● sock 客户端套接字文件描述符。

● servaddr 保存目标服务器端地址信息的变量地址值。

●addrlen 以字节为单位传递已传递给第二个结构体参数servaddr 的地址变量长度。

客户端套接字地址信息在哪?

实现服务器端必经过程之一就是给套接字分配IP 和端口号。但客户端实现过程中并未出现
套接字地址分配,而是创建套接字后立即调用conect 函数。难道客户端套接字无需分配IP

和端口?

答案:当然不是!

网络数据交换必须分配IP 和端口。既然如此,那客户端套接字何时、何地、如何分配

地址呢?

.何时? 调用connect 函数时。

.何地? 操作系统,更准确地说是在内核中。

.如何? IP 用计算机(主机)的IP,端口随机。

客户端的IP 地址和端口在调用connect 函数时自动分配,无需调用标记的bind 函数进

行分配。

这就是与服务端的不同。

TCP客户端

四、客户端代码实现和联调

基于TCP服务端/客户端的函数调用关系

五、迭代服务器

前面的普通服务器TCP 的缺点:一次服务。只能给一个客户端服务。

迭代服务器比较原始,它的原型可以描述成:

这个改动就是,当处理完一个客户端后,会再次进入accept,等待连接。

也就是说,这个程序是一个一个处理各个客户端发来的连接的,比如一个客户端发来一个

连接,那么只要它还没有完成自己的任务,那么它就一直会占用服务器的进程直到处理完毕

后服务器关闭掉这个socket。可以循环服务多个客户端。

六、回声服务器实现

回声服务器:将从客户端收到的数据原样返回给客户端,即"回声"。

cpp 复制代码
void run_client65()
{
	int client = socket(PF_INET, SOCK_STREAM, 0);
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr)); //清零 防止意外
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	servaddr.sin_port = htons(33005);
	int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));
	while (ret == 0) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		char buffer[256] = "";

		fputs("input message(Q to quit):", stdout);//输出提示符
		fgets(buffer, sizeof(buffer), stdin/*标准输入流*/);//读入一行
		if ((strcmp(buffer, "Q\n") == 0) || (strcmp(buffer, "q\n") == 0)) {
			break;
		}

		size_t len = strlen(buffer);
		size_t send_len = 0;
		while (send_len < len); {
			ssize_t ret = write(client, buffer + send_len, len - send_len);//发给服务器
			if (ret <= 0)
			{
				fputs("write failed!\n", stdout);
				close(client);
				std::cout << "client done!" << std::endl;
				return;
			}
			send_len += (size_t)ret;
		}
		memset(buffer, 0, sizeof(buffer));
		size_t read_len = 0;
		while (read_len < len)
		{
			ssize_t ret = read(client, buffer + read_len, len - read_len);
			if (ret <= 0)
			{
				fputs("read failed!\n", stdout);
				close(client);
				std::cout << "client done!" << std::endl;
				return;
			}
			send_len += (size_t)ret;
		}

		std::cout << "from server:" << buffer;
	}
	close(client);
	std::cout << "client done!" << std::endl;
}
void server65() {
	int server, client;
	struct sockaddr_in seraddr, cliaddr; //服务端和客户端的地址结构体
	socklen_t cliaddrlen;//客户端的地址结构体大小

	//1.创建服务器套接字
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
	server = socket(PF_INET, SOCK_STREAM, 0);//TCP协议  什么是TCP,就是IPv4中面向了流的套接字
	if (server < 0) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		std::cout << "create socket failed!" << std::endl;
		return;
	}

	//2.绑定服务器套接字和地址结构体
	memset(&seraddr, 0, sizeof(seraddr));//清零
	seraddr.sin_family = AF_INET;        //地址协议族
	seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	seraddr.sin_port = htons(33005);

	//printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
	int on = 1;
	if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
		perror("setsockopt");
		close(server);
		return;
	}
	//printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);

	int ret = bind(server, (struct sockaddr*)&seraddr, sizeof(seraddr));
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
	if (ret == -1) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		std::cout << "bind failed!" << std::endl;
		close(server);
		return;
	}
	ret = listen(server, 3);
	if (ret == -1) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		std::cout << "listen failed!" << std::endl;
		close(server);
		return;
	}
	char buffer[1024];
	for (int i = 0; i < 2; i++) {
		memset(buffer, 0, sizeof(buffer));
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		cliaddrlen = sizeof(cliaddr);
		client = accept(server, (struct sockaddr*)&cliaddr, &cliaddrlen/*&cliaddrlen*/);
		if (client == -1) {
			std::cout << "accept failed!" << std::endl;
			close(server);
			return;
		}
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		//ssize_t len=read(client, buffer, sizeof(buffer));
		ssize_t len = 0;
		while ((len = read(client, buffer, sizeof(buffer))) > 0) {
			len = write(client, buffer, len); //返回值就是实际写入的字节数
			if (len != (ssize_t)strlen(buffer)) {
				std::cout << "write failed!    len:" << len << "  buffer:" << buffer << std::endl;
				close(server);
				return;
			}
			memset(buffer, 0, len);
		}

		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		close(client);
	}
	close(server);
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
}
void lession65()
{
	pid_t pid = fork();
	if (pid == 0) {   //子进程
		//开启服务端
		server65();
	}
	else if (pid > 0) {
		for (int i = 0; i < 2; i++)
			run_client65();
		int status = 0;
		wait(&status);//避免子进程成为僵尸进程
	}
	else {
		std::cout << "fork failed!" << pid << std::endl;
	}
}

七、回声服务器存在的问题和解决

cpp 复制代码
write(sock, message,strlen(message));
str_len= read(sock, message,BUF_SIZE-1);
message[str_len]= 0;
printf("Message from server:%s",message);

以上代码有个错误假设∶

"每次调用read、write 函数时都会以字符串为单位执行实际的I/O 操作(读文件是输入,写文件是输出)。"

当然,每次调用write 函数都会传递1 个字符串,因此这种假设在某种程度上也算合理。

但是我们之前讲过:"TCP 不存在数据边界"的内容吗?

上述客户端是基于TCP 的,因此,多次调用write 函数传递的字符串有可能一次性传递

到服务器端。此时客户端有可能从服务器端收到多个字符串,这不是我们希望看到的结果。

还需考虑服务器端的如下情况∶

"字符串太长,需要分2 个数据包发送!"

我们的回声服务器端/客户端给出的结果是正确的。但这只是运气好罢了!只是因为收发

的数据小,而且运行环境为同一台计算机或相邻的两台计算机,所以没发生错误,可实际上

仍存在发生错误的可能。

八、回声服务器实战:计算器的网络实现(上)

这里进行客户端的实现:

cpp 复制代码
void run_client66()
{
	int client = socket(PF_INET, SOCK_STREAM, 0);
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr)); //清零 防止意外
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	servaddr.sin_port = htons(33005);
	int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));
	char buffer[255 * 4] = "";
	while (ret == 0) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);


		fputs("Operand count:", stdout);//输出提示符
		int opnd_cnt = 0;
		scanf("%d", &opnd_cnt);
		if (opnd_cnt <= 1 && opnd_cnt >= 256) {
			fputs("opnd_cnt error,too small or too big!\n", stdout);
			close(client);
			std::cout << "client done!" << opnd_cnt << std::endl;
			return;
		}
		buffer[0] = (char)opnd_cnt;         //服务器此处需要解释为无符号
		for (int i = 0; i < opnd_cnt; i++) {
			scanf("%d", buffer + 1 + i * 4);
		}
		fgetc(stdin);
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		fputs("Operator:", stdout);
		buffer[1 + opnd_cnt * 4] = (char)fgetc(stdin);
		//buffer[1 + opnd_cnt * 4] = (char)fgetc(stdin);
		//fgetc(stdin); // 清除换行符

		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);

		size_t len = opnd_cnt * 4 + 2;
		size_t send_len = 0;
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		while (send_len < len) {
			printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
			ssize_t ret = write(client, buffer + send_len, len - send_len);//发给服务器
			if (ret <= 0)
			{
				fputs("write failed!\n", stdout);
				close(client);
				std::cout << "client done!" << std::endl;
				return;
			}
			send_len += (size_t)ret;
		}
		memset(buffer, 0, sizeof(buffer));

		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		size_t read_len = 0;
		while (read_len < 4)
		{
			printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
			ssize_t ret = read(client, buffer + read_len, len - read_len);
			if (ret <= 0)
			{
				fputs("read failed!\n", stdout);
				close(client);
				std::cout << "client done!" << std::endl;
				return;
			}
			read_len += (size_t)ret;
		}

		printf("from server:%d\n", *(int*)buffer);
	}
	close(client);
	std::cout << "client done!" << std::endl;
}

九、回声服务器实战:计算器的网络实现(下)

这里实现服务端:(包含计算函数和主函数)

cpp 复制代码
int calculate(int count, int oprand[], char op) {
	int result = 0;
	switch (op) {
	case '+':
		for (int i = 0; i < count; i++) {
			result += oprand[i];
		}
		break;
	case '-':
		result = oprand[0];
		for (int i = 1; i < count; i++) {
			result -= oprand[i];
		}
		break;
	case '*':
		result = oprand[0];
		for (int i = 1; i < count; i++) {
				result *= oprand[i];
		}
		break;
	default:
		break;
	}
	return result;
}
void server66() {
	int server, client;
	struct sockaddr_in seraddr, cliaddr; //服务端和客户端的地址结构体
	socklen_t cliaddrlen;//客户端的地址结构体大小

	//1.创建服务器套接字
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
	server = socket(PF_INET, SOCK_STREAM, 0);//TCP协议  什么是TCP,就是IPv4中面向了流的套接字
	if (server < 0) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		std::cout << "create socket failed!" << std::endl;
		return;
	}

	//2.绑定服务器套接字和地址结构体
	memset(&seraddr, 0, sizeof(seraddr));//清零
	seraddr.sin_family = AF_INET;        //地址协议族
	seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	seraddr.sin_port = htons(33005);

	//printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
	int on = 1;
	if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
		perror("setsockopt");
		close(server);
		return;
	}
	//printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);

	int ret = bind(server, (struct sockaddr*)&seraddr, sizeof(seraddr));
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
	if (ret == -1) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		std::cout << "bind failed!" << std::endl;
		close(server);
		return;
	}
	ret = listen(server, 3);
	if (ret == -1) {
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		std::cout << "listen failed!" << std::endl;
		close(server);
		return;
	}
	char buffer[1024];
	for (int i = 0; i < 2; i++) {
		memset(buffer, 0, sizeof(buffer));
		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		cliaddrlen = sizeof(cliaddr);
		client = accept(server, (struct sockaddr*)&cliaddr, &cliaddrlen/*&cliaddrlen*/);
		if (client == -1) {
			std::cout << "accept failed!" << std::endl;
			close(server);
			return;
		}
		//printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		//ssize_t len=read(client, buffer, sizeof(buffer));
		ssize_t len = 0;
		len = read(client, buffer, 1);
		int result;
		if (len > 0) {
			for (unsigned int i = 0; i < ((unsigned)buffer[0] & 0xFF); i++) {
				read(client, buffer + 1 + i * 4, 4);
			}
			read(client, buffer + 1 + ((unsigned)buffer[0] & 0xFF) * 4, 1);
			result = calculate((int)buffer[0] & 0xFF, (int*)(buffer + 1), buffer[1 + ((unsigned)buffer[0] & 0xFF) * 4]);
			write(client, &result, 4);
			std::cout << "result" << result << std::endl;
		}
		while ((len = read(client, buffer, sizeof(buffer))) > 0) {
			len = write(client, buffer, len); //返回值就是实际写入的字节数
			if (len != (ssize_t)strlen(buffer)) {
				std::cout << "write failed!    len:" << len << "  buffer:" << buffer << std::endl;
				close(server);
				return;
			}
			memset(buffer, 0, len);
		}

		printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
		read(client, buffer, 1);
		close(client);
	}
	close(server);
	printf("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
}
void lession66()
{
	pid_t pid = fork();
	if (pid == 0) {   //子进程
		//开启服务端
		server66();
	}
	else if (pid > 0) {
		for (int i = 0; i < 2; i++)
			run_client66();
		int status = 0;
		wait(&status);//避免子进程成为僵尸进程
	}
	else {
		std::cout << "fork failed!" << pid << std::endl;
	}
}

十、TCP套接字的IO缓冲

我们知道,TCP 套接字的数据收发无边界。服务器端即使调用1 次write 函数传输40 字

节的数据,客户端也有可能通过4 次read 函数调用每次读取10 字节。但此处也有一些疑问,

服务器端一次性传输了40 字节,而客户端居然可以缓慢地分批接收。客户端接收10 字节后,

剩下的30 字节在何处等候呢?是不是像飞机为等待着陆而在空中盘旋一样,剩下30 字节也

在网络中徘徊并等待接收呢?

实际上,write 函数调用后并非立即传输数据,read 函数调用后也并非马上接收数据。

更准确地说,如下图所示,write 函数调用瞬间,数据将移至输出缓冲;read 函数调用瞬间,

从输人缓冲读取数据。

调用write 函数时,数据将移到输出缓冲,在适当的时候(不管是分别传送还是一次性

传送)传向对方的输入缓冲。这时对方将调用read 函数从输入缓冲读取数据。这些I/O 缓

冲特性可整理如下。

A: I/O 缓冲在每个TCP 套接字中单独存在。

B: I/O 缓冲在创建套接字时自动生成。

C: 即使关闭套接字也会继续传递输出缓冲中遗留的数据。

D: 关闭套接字将丢失输入缓冲中的数据。

那么,下面这种情况会引发什么事情?理解了I/O 缓冲后,其流程∶

"客户端输入缓冲为50 字节,而服务器端传输了100 字节。"

这的确是个问题。输入缓冲只有50 字节,却收到了100 字节的数据。可以提出如下解决方

案∶
填满输入缓冲前迅速调用read 函数读取数据,这样会腾出一部分空间,问题就解决了。

其实根本不会发生这类问题,因为TCP 会控制数据流。

TCP 中有滑动窗口(Sliding Window)协议,用对话方式呈现如下。

套接字A∶"你好,最多可以向我传递50 字节。"

套接字B∶"OK!"

套接字A∶"我腾出了20 字节的空间,最多可以收70 字节。

套接字B∶"OK!"

数据收发也是如此,因此TCP 中不会因为缓冲溢出而丢失数据。

相关推荐
人才程序员7 分钟前
详解QtPDF之 QPdfLink
开发语言·c++·qt·pdf·软件工程·界面·c语音
90wunch14 分钟前
驱动篇的开端
c++·安全
济南小草根20 分钟前
RabbitMQ学习-Eight
分布式·学习·rabbitmq
梅洪37 分钟前
008静态路由-特定主机路由
网络·网络协议·tcp/ip
名字不要太长 像我这样就好39 分钟前
【iOS】《Effective Objective-C 2.0》阅读笔记(一)
开发语言·笔记·学习·macos·ios·objective-c
小萌新~~~~1 小时前
在Scala中Array不可变的学习
开发语言·学习·scala
我是哈哈hh1 小时前
专题二十四_贪心策略(2)_算法专题详细总结
数据结构·c++·算法·leetcode·贪心算法·贪心
APItesterCris1 小时前
对于大规模的淘宝API接口数据,有什么高效的处理方法?
linux·服务器·前端·数据库·windows
加洛斯2 小时前
SpringBoot小知识(3):热部署知识
运维·服务器·spring boot