一个简单的基于C/S模型的TCP通信实例

news2024/11/27 0:37:45

1 TCP协议

1.1 概念

TCP是一种面向连接的、可靠的协议,有点像打电话,双方拿起电话互通身份之后就建立了连接,然后说话就行了,这边说的话那边保证听得到,并且是按说话的顺序听到的,说完话挂机断开连接。也就是说TCP传输的双方需要首先建立连接,之后由TCP协议保证数据收发的可靠性,丢失的数据包自动重发,上层应用程序收到的总是可靠的数据流,通讯之后关闭连接。

1.2 TCP数据包格式

在这里插入图片描述

1.3 TCP3次握手过程

在这里插入图片描述

1.3.1 客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的段1

  • 客户端发出段1,SYN位表示连接请求。序号是1000,这个序号在网络通讯中用作临时的地址,每发一个数据字节,这个序号要加1,这样在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况
  • 另外,规定SYN位和FIN位也要占一个序号,这次虽然没发数据,但是由于发了SYN位,因此下次再发送应该用序号1001。
  • mss表示最大段尺寸,如果一个段太大,封装成帧后超过了链路层的最大帧长度,就必须在IP层分片,为了避免这种情况,客户端声明自己的最大段尺寸,建议服务器端发来的段不要超过这个长度。

1.3.2 服务器端回应客户端,是三次握手中的第2个报文段,同时带ACK标志和SYN标志。它表示对刚才客户端SYN的回应;同时又发送SYN给客户端,询问客户端是否准备好进行数据通讯。

  • 服务器发出段2,也带有SYN位,同时置ACK位表示确认,确认序号是1001,表示“我接收到序号1000及其以前所有的段,请你下次发送序号为1001的段”,也就是应答了客户端的连接请求,同时也给客户端发出一个连接请求,同时声明最大尺寸为1024。

1.3.3 客户必须再次回应服务器端一个ACK报文,这是报文段3。

  • 客户端发出段3,对服务器的连接请求进行应答,确认序号是8001。在这个过程中,客户端和服务器分别给对方发了连接请求,也应答了对方的连接请求,其中服务器的请求和应答在一个段中发出,因此一共有三个段用于建立连接,称为“三方握手(three-way-handshake)”。在建立连接的同时,双方协商了一些信息,例如双方发送序号的初始值、最大段尺寸等。

在TCP通讯中,如果一方收到另一方发来的段,读出其中的目的端口号,发现本机并没有任何进程使用这个端口,就会应答一个包含RST位的段给另一方。例如,服务器并没有任何进程使用8080端口,我们却用telnet客户端去连接它,服务器收到客户端发来的SYN段就会应答一个RST段,客户端的telnet程序收到RST段后报告错误Connection refused:

1.4 数据传输

在这里插入图片描述

  • 客户端发出段4,包含从序号1001开始的20个字节数据。
  • 服务器发出段5,确认序号为1021,对序号为1001-1020的数据表示确认收到,同时请求发送序号1021开始的数据,服务器在应答的同时也向客户端发送从序号8001开始的10个字节数据,这称为piggyback。
  • 客户端发出段6,对服务器发来的序号为8001-8010的数据表示确认收到,请求发送序号8011开始的数据。

在数据传输过程中,ACK和确认序号是非常重要的,应用程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区中,发出数据包给对方之后,只有收到对方应答的ACK段才知道该数据包确实发到了对方,可以从发送缓冲区中释放掉了,如果因为网络故障丢失了数据包或者丢失了对方发回的ACK段,经过等待超时后TCP协议自动将发送缓冲区中的数据包重发。

1.5 TCP4次挥手

在这里插入图片描述

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

  • 客户端发出段7,FIN位表示关闭连接的请求。

  • 服务器发出段8,应答客户端的关闭连接请求。

  • 服务器发出段9,其中也包含FIN位,向客户端发送关闭连接请求。

  • 客户端发出段10,应答服务器的关闭连接请求。

建立连接的过程是三方握手,而关闭连接通常需要4个段,服务器的应答和关闭连接请求通常不合并在一个段中,因为有连接半关闭的情况,这种情况下客户端关闭连接之后就不能再发送数据给服务器了,但是服务器还可以发送数据给客户端,直到服务器也关闭连接为止。

2 案例

2.1 SERVER端

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

#define SERV_PORT 9527
void sys_err(const char *str){
	perror(str);
	exit(1);
}
int main(int argc,char *argv[]){
	// 定义监听套接字、通信套接字
	int lfd = 0, cfd = 0;
	int ret,i;
	char buf[BUFSIZ],client_IP[1024];
	// 定义服务器端、客户端地址结构
	struct sockaddr_in serv_addr,clit_addr;
	
	// 客户端地址结构长度
	socklen_t clit_addr_len;
	
	// 初始化服务器端地址结构
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(SERV_PORT);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	// 监听套接字初始化以及创建失败处理
	lfd = socket(AF_INET,SOCK_STREAM,0);
	if(lfd == -1) {
		sys_err("socket error");
	}
	
	// 将套接字绑定IP+端口
	bind(lfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr));
	
	// 设置lfd套接字用于监听以及可以连接的客户端套接字数量
	listen(lfd,128);
	
	// 客户端地址结构长度初始化
	clit_addr_len = sizeof(clit_addr);
	
	// 本服务器端目前只可同时对一个客户端进行通信
	while(1){
	// 设置服务端套接字为被动状态,返回一个新的套接字用于通信以及创建失败处理
		cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);
		if(cfd == -1){
		sys_err("accept error");
		}
		// 打印客户端ip以及端口
		printf("client ip: %s port:%d\n",	inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),ntohs(clit_addr.sin_port));
		while(1) {
				// 接收客户端传来的数据
				ret = read(cfd,buf,sizeof(buf));
				if(ret == 0) {
					printf("the link is disconneted!\n");
					break;
				}
				// 将客户端数据传入标准输出流中
				write(STDOUT_FILENO,buf,ret);
				// 将传来的数据变大写并传回到客户端
				for(i = 0; i < ret; i++)
					buf[i] = toupper(buf[i]);
				write(cfd,buf,ret);
			}
	}
	
	// close(lfd);
	// close(cfd);

	return 0;
}
	


2.2 CLIENT端

客户端得知服务器端ip以及端口即可通信

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

#define SERV_PORT 9527

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}

int main(int argc, char *argv[])
{
    int cfd;
    int conter = 10;
    char buf[BUFSIZ];
    //服务器地址结构
    struct sockaddr_in serv_addr;         
		
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
 	// 转换ip地址由点分制到二进制
    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
    // 初始化用于通信的套接字
    cfd = socket(AF_INET, SOCK_STREAM, 0);
    if (cfd == -1)
        sys_err("socket error");
	// 将客户端套接字与服务器端套接字连接
    int ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    if (ret != 0)
        sys_err("connect err");
	
    while (--conter) {
    	 // 将数据通过套接字传输给服务器端
        write(cfd, "hello\n", 6);
        // 从套接字读取数据
        ret = read(cfd, buf, sizeof(buf));
        // 将数据输出至标准输出
        write(STDOUT_FILENO, buf, ret);
        
        sleep(1);
    }
	// 关闭客户端套接字
    close(cfd);

	return 0;
}

2.3 实现效果

  • 运行服务器端,服务器先阻塞等待客户端请求
  • 运行客户端,客户端发送连接请求
  • 连接成功,服务器端输出客户端ip以及端口
  • 服务器获取客户端传来的数据并将其打印到标准输出以及对其进行大写转换
  • 服务器将处理好的数据发送给客户端
  • 客户端获取数据并打印至标准输出
  • 客户端关闭套接字

客户端:

在这里插入图片描述

服务器端:
在这里插入图片描述

3 通信时序与代码对应图

在这里插入图片描述

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

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

相关文章

2023 华为 Datacom-HCIE 真题题库 08--含解析

单项选择 1.[试题编号&#xff1a;190385] &#xff08;单选题&#xff09;以下关于BGP/MPLSIPVPN路由交互的描述&#xff0c;错误的是哪一项? A、PE与CE之间交互的是IPv4路由信息 B、出口PE可以通过BGP、IGP或静态路由的方式向远端CE发送IPv4路由 C、入口PE将从CE接收到的I…

数组(1)

文章目录 目录1. 一维数组的创建和初始化1.1 一维数组的创建1.2 一维数组的初始化 2. 一维数组的使用3. 一维数组在内存中的存储4. 二维数组的创建和初始化4.1 二维数组的创建4.2 二维数组的初始化 5. 二维数组的使用6. 二维数组在内存中的存储7. 数组越界8. 数组作为函数参数 …

chatgpt赋能python:Python自动填表单:提高工作效率的好帮手

Python 自动填表单&#xff1a;提高工作效率的好帮手 在现代社会中&#xff0c;表单已成为我们日常工作中不可或缺的一部分。填表单虽然看似简单&#xff0c;但是时间一长&#xff0c;不仅会影响工作效率&#xff0c;还会带来心理负担。幸运的是&#xff0c;Python 自动填表单…

Linux-0.11 boot目录bootsect.s详解

Linux-0.11 boot目录bootsect.s详解 模块简介 bootsect.s是磁盘启动的引导程序&#xff0c;其概括起来就是代码的搬运工&#xff0c;将代码搬到合适的位置。下图是对搬运过程的概括&#xff0c;可以有个印象&#xff0c;后面将详细讲解。 bootsect.s主要做了如下的三件事: 搬…

doris---Rollup

Rollup 3.5.1基本概念 通过建表语句创建出来的表称为 Base 表&#xff08;Base Table,基表&#xff09; 在 Base 表之上&#xff0c;我们可以创建任意多个 ROLLUP 表。这些 ROLLUP 的数据是基于 Base 表产生的&#xff0c;并且在物理上是独立存储的。 Rollup表的好处&#xff…

C#调用FreeSpire.PDF获取PDF文档中使用的字体

除了图片之外&#xff0c;电子文件中使用的字体都必须要在本机中安装才能正常查看文字&#xff08;word缺少字体的话会自动使用相似或默认字体&#xff09;&#xff0c;要想知道电子文件中使用的字体&#xff0c;可以将电子文件转换为PDF文件&#xff08;如果是打印成PDF的话&a…

chatgpt赋能python:Python收集数据在SEO中的重要性

Python 收集数据在 SEO 中的重要性 随着互联网的发展&#xff0c;搜索引擎对于用户获取信息的重要性日益增加。SEO&#xff08;搜索引擎优化&#xff09;一直是每个网站必须考虑的问题。Python 收集数据在 SEO 中可以发挥重要的作用&#xff0c;帮助网站提高排名。下面我们来详…

Solidity拓展:数学运算过程中数据长度溢出的问题

在数学运算过程中假如超过了长度则值会变成该类型的最小值&#xff0c;如果小于了该长度则变成最大值 数据上溢 uint8 numA 255; numA;uint8的定义域为[0,255]&#xff0c;现在numA已经到顶了&#xff0c;numA会使num变成0(由于256已经超过定义域&#xff0c;它会越过256&…

结构体 --- C语言

目录 1.结构体的声明 2.结构体变量的定义和初始化 3.结构体成员访问 4.结构体传参 1.结构体的声明 结构是一些值的集合&#xff0c;这些称为成员变量&#xff0c;结构的每个成员可以是不同类型的变量。 而数组是一组类型相同的元素的集合。 生活中的描述 人&#xff1a;名…

测试C#分词工具jieba.NET(续1:提取关键词及并行分词)

jieba.NET支持通过两种算法提取文本关键词&#xff1a;TF-IDF算法和TextRank算法&#xff0c;关于这两种算法的介绍详见参考文献10-11&#xff0c;在jieba.NET中对应的类为TfidfExtractor和TextRankExtractor&#xff0c;这两个分词都都支持调用ExtractTags和ExtractTagsWithWe…

文件上传至公有云Nos及对接CDN

项目开发中&#xff0c;需要将图片文件上传至网易公有云的Nos,并且结合CDN做加速服务&#xff0c;记录一下开发过程。流程图&#xff1a; 1. 文件上传到公有云Nos 网易对象存储服务&#xff08;Netease Object Storage&#xff0c;简称NOS&#xff09;是网易数帆提供的高可用…

pycharm环境下打开Django内置的数据库Sqlite出错问题解决

问题描述 在数据库库文件中写入一条记录后&#xff0c;在pycharm的terminal终端下执行查看表的命令出错 执行语句为&#xff1a; 连接数据库报错 python manage.py dbshell CommandError: You appear not to have the sqlite3 program installed or on your path. Error:…

JavaScript之DOM基础

1. 初识DOM DOM: 文档对象模型 是W3C组织推荐的处理可扩展标记语言(HTML或者XML)的标准编程接口。 w3c已经定义了一系列的DOM接口&#xff0c;通过这些DOM接口可以改变网页的内容&#xff0c;结构和样式。 DOM树&#xff1a;文档&#xff1a;一个页面就是一个文档, DOM中使用d…

Android逆向猿人学2022年app比赛第四题grpc与Protobuf使用(步步验证)

教程 前言一、起步二、抓包三、分析四、验证五、HOOK借鉴 前言 前面2-3题和第一题解题思路基本上一样的&#xff0c;这里就不出教程了&#xff0c;这篇文章比较繁琐&#xff0c;基本上描述了我做这题的思路&#xff0c;有很多走不通的地方也有对应的方法&#xff0c;所以会比较…

linux网络初探

linux网络 1.1查看本机ip IP地址 IP地址网络地址主机地址&#xff0c;网络地址&#xff08;网络号&#xff09;相同的主机为本地网络中的主机&#xff0c;可以直接相互通信&#xff0c;而网络地址不同的主机为远程网络中的主机&#xff0c;相互通信必须通过本地网关&#xf…

Flutter Windows开发环境搭建教程与学习资料推荐

Windows应用软件开发有很多框架可以选择&#xff0c;例如比较流行的Electron、Qt、CEF、WPF、WinForm、MFC、DuiLib、SOUI等等。Flutter是近几年流行的全平台应用开发框架&#xff0c;可以进行Android、IOS、Web、MacOS、Windows、Linux等平台的应用软件开发。 一、Flutter介绍…

启动U盘制作工具Rufus 4.0.2035

Rufus是是一款小巧实用免费开源的帮助格式化和创建可启动USB闪存驱动器的工具&#xff0c;如USB钥匙/软盘、记忆棒等&#xff0c;可快速制作linux系统或者win启动u盘&#xff0c;可快速的将ISO镜像文件制作成可引导启动的USB启动盘&#xff0c;支持ISO镜像、GPT和UEFI&#xff…

设置参考文献编号与文中插入引用的具体步骤

目录 一、前言 二、操作步骤 &#xff08;一&#xff09;参考文献设置编号 &#xff08;二&#xff09;文章中引用参考文献方式 一、前言 本教程使用的软件是WPS 二、操作步骤 &#xff08;一&#xff09;参考文献设置编号 1.把引用文献的这个编号全部删掉 2.右键点击段…

学习笔记——vue中使用el-dropdown组件报错

今天在工作中&#xff0c;发现使用el-select做的下拉框&#xff0c;下拉菜单展开后&#xff0c;鼠标点击下拉框之外的区域时&#xff0c;下拉菜单没有收起。然后&#xff0c;我打开控制台&#xff0c;发现了这个错误。 Uncaught TypeError: Cannot read properties of null (re…

《Linux0.11源码解读》理解(四) head之重新设置IDT/GDT

上节提到&#xff0c;现在cs:ip指向0地址&#xff0c;此处存储着作为操作系统核心代码的system模块&#xff0c;是由head.s和 main.c以及后面所有源代码文件编译链接而成。head.s(以下简称head)紧挨着main.c&#xff0c;我们先执行head。 重新设置内核栈 _pg_dir: _startup_3…