TCP IP网络编程(五) 基于TCP的服务器端、客户端 (补充)

news2024/11/26 10:46:14

文章目录

    • 回声客户端的完美实现
      • 回声客户端出现的问题
      • 回声客户端问题解决方法
    • TCP原理
      • TCP套接字中的I/O缓冲
      • TCP内部工作原理1:与对方套接字的连接
      • TCP内部工作原理2:与对方主机的数据交换
      • TCP内部工作原理3:断开与套接字的连接
    • 总结

回声客户端的完美实现

回声客户端出现的问题

在上一节基于TCP的服务器端、回声客户端中,存在问题:

如果数据太大,操作系统就有可能把数据分成多个数据包发送到客户端,客户端有可能在尚未收到全部数据包时就调用read函数

问题出在客户端,而不是服务器端,先来对比一下客户端与服务器端I/O相关代码

服务器端

while((str_len = read(cknt_sock, message, BUF_SIZE)) != 0)
    write(clnt_sock, message, str_len);

客户端

write(sock, message, strlen(message))
str_len = read(sock, message, BUF_SIZE - 1);

客户端与服务器端都调用read和write函数,实际上回声客户端将100%接受自己传输的数据,只不过接收数据时单位出现问题

回声客户端传输的是字符串,而且是通过调用write函数一次性发送的,之后调用read函数,等待接收自己传输的字符串

这里的问题就出现在客户端收到所有字符串数据,需要等待多长时间?等待之后调用read函数是否可以一次性读取完毕?

回声客户端问题解决方法

因为可以提前确定接收数据的大小,所以这个问题不难解决,如果之前传输了100字节长的字符串,则在接收时循环调用read函数读取100字节结课

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len, recv_len, recv_cnt;
	struct sockaddr_in serv_adr;

	if(argc!=3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));
	
	if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("connect() error!");
	else
		puts("Connected...........");
	
	while(1) 
	{
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);
		
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
			break;

		str_len=write(sock, message, strlen(message));
		
		recv_len=0;
		while(recv_len<str_len)
		{
			recv_cnt=read(sock, &message[recv_len], BUF_SIZE-1);
			if(recv_cnt==-1)
				error_handling("read() error!");
			recv_len+=recv_cnt;
		}
		
		message[recv_len]=0;
		printf("Message from server: %s", message);
	}
	
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

在之前的示例中只调用了一次read函数,上述示例为了接收所有传输的数据而循环调用read函数

while(recv_len<str_len)
{
	recv_cnt=read(sock, &message[recv_len], BUF_SIZE-1);
	if(recv_cnt==-1)
		error_handling("read() error!");
	recv_len+=recv_cnt;
}

如果将上述代码改成:

while(recv_len != str_len)
{
	recv_cnt=read(sock, &message[recv_len], BUF_SIZE-1);
	if(recv_cnt==-1)
		error_handling("read() error!");
	recv_len+=recv_cnt;
}

接收的数据大小应该和传输的相同,所以recv_len中保存的数据等于str_len中保存的数据,即可结束while循环,读者们肯定认为这种循环写法更符合逻辑,但是可能因为某种异常情况导致死循环,所以尽可能的使用 while(recv_len<str_len) ,即使发生异常情况也不会导致无限循环

TCP原理

TCP套接字中的I/O缓冲

TCP套接字的数据收发无边界,服务器端即使调用一次write函数传输40字节的数据,客户端也有可能通过4次read函数调用每次读取10字节,但是问题又出现了,服务器端一次性传输了40字节,而客户端居然可以缓慢的分批接收,,客户端接收10字节后,剩下的30字节在什么地方等着呢?

事实上,write函数调用后并非立即传输数据,read函数调用后也并非马上接收数据。更准确的来说,write函数调用瞬间,数据将移到输出缓冲;read函数调用瞬间,从输入缓冲读取数据。

在这里插入图片描述

调用write函数时,数据将移到输出缓冲,在适当的时候传向对方的输入缓冲,这时对方调用read函数从输入缓冲读取数据

I/O缓冲特性如下

  • I/O缓冲在每个TCP套接字中单独存在
  • I/O缓冲在创建套接字时自动生成
  • 即使关闭套接字也会继续传递输出缓冲中遗留的数据
  • 关闭套接字将丢失输入缓冲中的数据

TCP内部工作原理1:与对方套接字的连接

TCP套接字从创建到消失所经过程分为三步:

1.与对方套接字建立连接

2.与对方套接字交换数据

3.断开与对方套接字的连接

TCP在实际通信过程中也会经过三次对话过程,因此该过程又称为三次握手

在这里插入图片描述

套接字是以全双工方式工作的,也就是说明套接字可以双向传递数据

1、 请求连接的主机A向主机B传递如下信息:

[SYN] SEQ: 1000, ACK: -

该消息中SEQ为1000,ACK为空,SEQ为1000的含义如下:

现在传递的数据包序号为100,如果接收无误,请通知我向你传递1001号数据包

2、 这是首次请求连接时使用的消息,又称SYN,表示收发数据前传输的同步消息,接下来主机B向A传递如下消息:

[SYN+ACK] SEQ: 2000, ACK: 1001

此时SEQ为2000,ACK为1001,而SEQ为2000的含义如下:

现在传递的数据包序号为2000,如果接收无误,请通知我向你传递2001号数据包

而ACK 1001的含义如下:

刚才传输的SEQ为1000的数据包接收无误,现在请传递SEQ为1001的数据包

对主机A首次传输的的数据包确认消息(ACK 1001)和为主机B传输数据做准备的同步消息(SEQ 200)捆绑发送,因此此种类型的消息又称为 SYN + ACK

3、 收发数据前向数据包分配序号,并向对方通报此序号,这都是为了防止数据丢失所做的准备。通过向数据包分配序号并确认,可以在数据丢失时马上查看并重传丢失的数据包,所以TCP可以保证可靠的数据传输。最后观察主机A向B传输的消息:

[ACK] SEQ : 1001, ACK: 2001

TCP连接过程中发送数据包时需要分配序号,在此之前的序号100的基础上+1,此时该数据包传递如下消息:

已正确收到传输的SEQ为2000的数据包,现在可以传输SEQ为2001的数据包

TCP内部工作原理2:与对方主机的数据交换

通过第一步三次握手过程完成了数据交换准备,下面就开始正式收发数据

在这里插入图片描述

首先主机A通过1个数据包发送100个字节的数据,数据包的SEQ为1200,主机B为了确认这点,向主机A发送了ACK1301消息

此时的ACK号为1301而非1201,原因在于ACK号的增量为传输的数据字节数,假设每次ACK号不加传输的字节数,这样虽然可以确认数据包的传输,但是不能明确100字节却不正确传递还是丢失,因此按照如下公式传递ACK消息:

ACK 号 — SEQ号 + 传递的字节数 + 1

与三次握手协议相同,最后+1是为了告知对方下次要传递的SEQ号

下面分析传输过程中数据包消失的情况

在这里插入图片描述

SEQ1301数据包向主机B传递100字节数据,但中间发生错误,主机B未收到,一段时间后,主机A仍未收到对于SEQ 1301的ACK确认,因此重传改数据包,为了完成数据包重传,TCP套接字启动计时器以等待ACK回答,相应计时器发生超时则重传

TCP内部工作原理3:断开与套接字的连接

先由套接字A向套接字B传递断开连接的消息,套接字B发出确认收到的消息,然后向套接字A传递可以断开连接的消息,套接字A同样发出确认消息

在这里插入图片描述

数据包内的FIN表示断开连接,双方各发送一次FIN消息后断开连接,此过程经历四个阶段,因此又称为四次握手

总结

这是《TCP/IP网络编程》专栏的第三篇文章,欢迎各位读者订阅!

更多资料点击 添加链接描述 欢迎各位读者去Star

⭐学术交流群Q 754410389 持续更新中~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/993202.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CRM软件管理系统的基本功能

CRM管理系统是企业运营的重要工具&#xff0c;它可以帮助企业管理客户关系&#xff0c;提升销售效率&#xff0c;大幅提高客户转化率&#xff0c;实现业绩增长。那么&#xff0c;CRM管理系统一般包含哪些功能呢&#xff1f;下面我们就来说说。 1、销售自动化 销售自动化顾名思…

【面试专题】Spring篇②

&#x1f4c3;个人主页&#xff1a;个人主页 &#x1f525;系列专栏&#xff1a;Java面试专题 目录 1.spring-bean的循环依赖 2.springMVC执行流程 3.Springboot自动配置原理 4.Spring框架常见的注解&#xff08;Spring&#xff0c;SpringMVC&#xff0c;SpringBoot&#x…

Ubuntu编译运行socket.io

本篇文章记录一下自己在ubuntu上编译运行socket.io的过程&#xff0c;客户端选用的是socket.io的c的库&#xff0c;编译起来倒不难&#xff0c;但是说到运行的话&#xff0c;对我来说确实是花了点功夫。毕竟程序要能运行起来才能更方便地去熟悉代码&#xff0c;因此今天我就记录…

MySQL——日志

日志的作用 1.用来排错 2.用来做数据分析 3.了解程序的运行情况&#xff0c;是否健康--》了解MySQL的性能&#xff0c;运行情况 分类 mysql很多有类型的日志&#xff0c;按照组件划分的话&#xff0c;可以分为 服务层日志 和 存储引擎层日志 &#xff1a; - 服务层…

数据治理-数据建模和设计

是什么&#xff1f; 数据建模是发现、分析和确定数据需求的过程&#xff0c;用一种称为数据模型的精确形式表示和传递这些数据需求。数据建模是数据管理的一个重要组成部分。建模过程中要求组织发现并记录数据组合的方式。 数据可以采用多种不同的模式来表示&#xff0c;其中最…

【广州华锐互动】电厂三维数字孪生大屏的功能和优势

在工业互联网的背景下&#xff0c;电厂三维数字孪生大屏系统正在逐渐成为电力行业的重要技术。通过创建电厂的虚拟模型&#xff0c;这个数字孪生系统可以实现对实际电厂的实时监控&#xff0c;预测维护需求&#xff0c;优化运营效率&#xff0c;甚至在某些情况下&#xff0c;能…

CrossEntropyLoss() 和 nn.BCEWithLogitsLoss() 举例说明区别

CrossEntropyLoss() 通常用于多分类任务&#xff0c;它接受一个包含类别标签的张量作为目标值&#xff0c;并且假设每个样本只属于一个类别。在多分类任务中&#xff0c;模型的最后一层输出是一个概率分布&#xff0c;表示每个类别的概率。CrossEntropyLoss() 计算模型输出与目…

微信“刷掌支付”上线,扫手就可以付款!你知道怎么开通了吗?

不用扫码&#xff01;不用刷卡&#xff01;隔空感应&#xff01; 刷掌就能支付 没错&#xff01; 新科技来咯~ 刷 掌 早在今年5月&#xff0c;微信刷掌支付正式面世&#xff0c;目前已应用于交通、健身、校园、零售、餐饮、办公、共享充电等多个场景&#xff0c;如北京地铁…

vue3+ts项目打包后的本地访问

注意&#xff1a;打包之后不可直接点击html访问&#xff0c;需要给项目安装本地服务&#xff01; 1、安装servenpm i -g serve 2、打包项目npm run build 生成dist文件夹 3、本地访问serve dist 运行service dist之后的控制台 可复制下方的地址运行打包后的项目&#xff0c;运行…

Spring系列文章:Spring中的设计模式

一、简单⼯⼚模式 BeanFactory的getBean()⽅法&#xff0c;通过唯⼀标识来获取Bean对象。是典型的简单⼯⼚模式&#xff08;静态⼯⼚模 式&#xff09;&#xff1b; 二、⼯⼚⽅法模式 FactoryBean是典型的⼯⼚⽅法模式。在配置⽂件中通过factory-method属性来指定⼯⼚⽅法&a…

英语单词(二)

1.int:整形 2.char:字符型 3.scanner:接受输入,扫描器 4.integer:整数,整形 5.type:类型 6.string:字符串类型 7.double:双精度浮点型

OpenRoads地形模型添加(增补)地形点

创建三维点&#xff0c;将创建的点对象添加到现有地形模型。 在ORD建模工作流&#xff1a; 地形、分析、点、分析点&#xff0c;在需要添加点的位置读出地模的高程&#xff08;图1&#xff09;&#xff1b; 图1 几何、平面、点添加高程点&#xff0c;特征设为地形随机点、高…

双线性插值以及计算

参考视频&#xff1a;图像处理-双线性插值_哔哩哔哩_bilibili 双线性插值 双线性插值是一种常用的图像处理和计算机图形学技术&#xff0c;用于在离散像素网格上平滑地估算介于两个相邻像素之间的数值。这种插值方法通常用于图像放大、旋转和变换等操作&#xff0c;以改善图像…

操作系统 第二章 进程管理:进程与线程、处理机调度

目录 1.进程与线程 1.1进程的概念、组成、特征 1.1.1概念 1.1.2组成 进程控制块&#xff08;PCB&#xff09; 程序段 数据段 1.1.3特征 1.2进程的状态与转换 1.2.1进程的五种状态 1.2.2进程的状态转换 1.3进程控制 1.3.1如何实现原语的“原子性” 1.3.2进程的创建…

intellij debug模式提示 : Method breakpoints may dramatically slow down debugging

最近在搞一个搭建一个项目 , 项目搭建完之后发现启动不了 , 一直都是正在加载中 并且提示Method breakpoints may dramatically slow down debugging&#xff0c;百度之后才知道是打了方法断点的原因 , 之前不小心打了一个断点 解决办法 : 点击如下图所示的按钮 然后把有断点…

15:00面试,15:06就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%,…

matlab之cell数组的详细用法

一、cell数组是什么&#xff1f; 在MATLAB中&#xff0c;Cell数组是一种特殊的数据结构&#xff0c;它可以存储不同类型的数据&#xff0c;包括数字、字符串、数组、结构体等。Cell数组是一种灵活的数据容器&#xff0c;可以方便地存储和处理不同类型的数据。 二、怎么使用ce…

算法通关村第十八关:白银挑战-回溯热门问题

白银挑战-回溯热门问题 回溯主要解决一些暴力枚举也搞不定的问题zh&#xff0c;例如组合、分割、子集、排列、棋盘等。 1. 组合总和问题 LeetCode39 https://leetcode.cn/problems/combination-sum/ 思路分析 如果不考虑重复&#xff0c;跟题目 LeetCode 113 类似 考虑重复…

Autowired members must be defined in valid Spring bean (@Component|@Service|…)

报错如下&#xff1a; 自动注入对象必须定义在有效的spring bean内&#xff0c;也就是说只有本身作为bean的类才能注入其他对象。 修正方法&#xff1a;在BookTest上加上Component注解

基于STM32F103 实现按键状态机

文章目录 开发板开发环境前言按键消抖按键硬件原理图软件延时实现思路 实验目的代码按键状态按键信息按键相关定义按键底层配置及状态获取 总结 开发板 正点原子STM32F103ZET6战舰 开发环境 stm32cubeMX Clion 前言 在单片机使用按键时&#xff0c;为了消除按键的抖动&…