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

  • (꒪ꇴ꒪ ),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

// 创建套接字socketint 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))函数用于将套接字与本地地址和端口绑定,以便在特定地址和端口上监听连接请求。

    // 绑定地址结构体bindint 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连接。它连接到指定的远程地址和端口。

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


       ⑤  listen():设置监听端口

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

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


        ⑥ accept():接受TCP连接

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

	// 等待连接acceptstruct 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操作   空间资源-1char 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操作    数据资源-1char 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操作   空间资源-1char 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操作    数据资源-1char 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)创建套接字socketint 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)绑定地址结构体bindint b = bind(ser_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));if(b == -1){perror("bind");return -1;}printf("绑定成功\n");// (4)开启监听listenint l = listen(ser_socket, 3);if (l == -1){perror("listen");return -1;}printf("监听成功\n");//ser_socket由 待链接套接字 变成 监听套接字while(1){// (5)等待连接acceptstruct 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/shutdownclose(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操作   空间资源-1char 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操作    数据资源-1char 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)创建套接字socketint 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)建立连接connectint 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/shutdownclose(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.rhkb.cn/news/122907.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【回眸】牛客网刷刷刷!(八)——中断专题

目录 前言 1、在CortexM内核中&#xff0c;当系统响应一个中断时 2、用与非门和或非门可以实现其他基本门电路。进而实现任何逻辑电路 3、cpu interface提供了功能包含 4、以Cortex-M3内核为例&#xff0c;如果某个中断在得到响应之前&#xff0c;其请求信号以若干的脉冲的…

超越时间与人力的软件开发智慧:《人月神话》

目录 1、写在前面2、沟通&#xff01;沟通&#xff01;沟通&#xff01;3、“银弹论”4、“人月神话”不能成立的原因5、影响力6、图书推荐 1、写在前面 《人月神话》是由计算机科学家弗雷德里克布鲁克斯所著的一本经典著作&#xff0c;首次出版于1975年。这本书以一个个小故事…

SSL证书系列--又拍云Let’s Encrypt免费DV SSL证书使用教程

原文网址&#xff1a;SSL证书系列--又拍云Let’s Encrypt免费DV SSL证书使用教程_IT利刃出鞘的博客-CSDN博客 简介 本文介绍如何使用又拍云部署Let’s Encrypt免费DV SSL证书。 一、了解Let’s Encrypt 了解和关注SSL证书的朋友&#xff0c;似乎没有理由不知道 Let’s Encr…

苹果与芯片巨头Arm达成20年新合作协议,将继续采用芯片技术

9月6日消息&#xff0c;据外媒报道&#xff0c;芯片设计巨头Arm宣布在当地时间周二提交给美国证券交易委员会&#xff08;SEC&#xff09;的最新IPO文件中&#xff0c;透露与苹果达成了一项长达20年的新合作协议&#xff0c;加深了双方之间的合作关系。 报道称&#xff0c;虽然…

进入低功耗和唤醒

休眠模式 进入休眠模式 如果使用 WFI 指令进入睡眠模式&#xff0c;则嵌套向量中断控制器 (NVIC) 确认的任意外设中断都会 将器件从睡眠模式唤醒。 如果使用 WFE 指令进入睡眠模式&#xff0c;MCU 将在有事件发生时立即退出睡眠模式。唤醒事件可 通过以下方式产生&#xff…

jvs-智能bi(自助式数据分析)9.1更新内容

​jvs-智能bi更新功能 1.报表增加权限功能&#xff08;服务、模板、数据集、数据源可进行后台权限分配&#xff09; 每个报表可以独立设置权限&#xff0c;通过自定义分配&#xff0c;给不同的人员分配不同的权限。 2.报表新增执行模式 可选择首次报表加载数据为最新数据和历…

搜索二维矩阵 II

题目链接 搜索二维矩阵 II 题目描述 注意点 矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 解答思路 最初想到使用深度优先遍历剪枝实现&#xff0c;但是运行后超出时间限制了可以直接遍历整个矩阵查找&#xff0c;虽然不超时…

升级iOS 17出现白苹果、不断重启等系统问题怎么办?

iOS 17发布后了&#xff0c;很多果粉都迫不及待的将iphone/ipad升级到最新iOS17系统&#xff0c;体验新系统功能。 但部分果粉因硬件、软件的各种情况&#xff0c;导致升级系统后出现故障&#xff0c;比如白苹果、不断重启、卡在系统升级界面等等问题。 如果遇到了这些系统问题…

python 创建 Telnet 客户端

目录 前言 1. Telnet 客户端框架 2. Telnet 代码分解 2.1 基于 TK 创建会话窗口 2.1.1 创建 Text 文本控件 2.1.2 创建 Frame 容器 2.1.2.1 基于 Frame 容器创建主机地址输入框 2.1.2.1.1 主机地址输入框绑定焦点事件 2.1.2.2 创建 Telnet 连接按钮控件 2.1.2.3 创建…

小程序如何上传微信聊天记录的文件

wx.chooseMessageFile({count: 10,type: image,success (res) {// tempFilePath可以作为img标签的src属性显示图片const tempFilePaths res.tempFiles} })参数说明 回调函数说明

【区块链 | IPFS】浅谈 | IPFS数据存储原理

IPFS在数据存储方面采用的是分散式的文件存储,区别于HTTP协议的位置寻址,IPFS是基于内容寻址,当文件上传到IPFS节点存储时,节点会对文件进行Merkle DAG(默克尔有向无环图)的格式组织分块存储,在存储完毕后,文件将以Merkle DAG的根哈希数来表示该文件,用户可以从IPFS构…

TSUMU58CDT9-1显示器芯片方案

TSUMU58CDT9-1是用于LCD显示器的整体解决方案图形处理IC&#xff0c;面板分辨率高达WUXGA。它配置了高速集成三adc /PLL、集成DVI/HDMI接收器、高质量显示处理引擎、集成微控制器和支持LVDS面板接口格式的输出显示接口。TSUMU58CDT9-1支持一个灵活的可配置数字输入接口&#xf…

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

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

Python Flask Web开发三:数据表的字段增加和删除

前言 在实际的开发中&#xff0c;数据表中的字段的增加和删除是很正常的操作&#xff0c;在运营的不断提需求下&#xff0c;这个修改的频率是很高的&#xff0c;那么在flask中如何进行字段的增加和删除呢&#xff1f;下面我来给大家讲讲 一、创建迁移脚本 使用数据库迁移工具…

C++内存管理(2)new、delete详解

目录 new operator&#xff08;new操作&#xff09; new类对象时加不加括号的差别 new工作任务 delete工作任务 new和delete 堆区空间操作&#xff08;对比malloc和free&#xff09; new和delete操作基本类型的空间 new和delete操作基本类型的数组 new和delete操作类的…

Qt开发_调用OpenCV(3.4.7)设计完成人脸检测系统

一、前言 近年来,人脸识别技术得到了广泛的应用,它可以在各种场景中实现自动化的人脸检测和识别,例如安防监控、人脸解锁、人脸支付等。 该项目的目标是设计一个简单易用但功能强大的人脸检测系统,可以实时从摄像头采集视频,并对视频中的人脸进行准确的检测和框选。通过…

0012Java程序设计-springboot基于微信小程序的校园智慧帮系统的设计与实现

摘要目录相关技术2.1 MySQL数据库2.2 SpringBoot框架2.3 uniapp框架2.4 B/S架构 系统设计系统实现开发环境 摘要 随着移动互联网高速发展&#xff0c;手机、移动智能终端设备在生活中有着越来越重要的地位。在高校推崇以人为本的今天&#xff0c;也逐渐重视“移动互联网”技术…

​7.1 项目1 学生通讯录管理:文本文件增删改查(C++版本)(自顶向下设计+断点调试) (A)​

C自学精简教程 目录(必读) 作业目标&#xff1a; 这个作业中&#xff0c;你需要综合运用之前文章中的知识&#xff0c;来解决一个相对完整的应用程序。 作业描述&#xff1a; 1 在这个作业中你需要在文本文件中存储学生通讯录的信息&#xff0c;并在程序启动的时候加载这些…

【Mycat1.6】缓存不生效问题处理

背景 系统做读写分离&#xff0c;有大量读需求&#xff0c;基本没有实时获取数据业务需要&#xff0c;所以可以启用缓存来减缓数据库压力&#xff0c;传统使用mybatis的缓存需要大量侵入式声明&#xff0c;所以结合需求使用Mycat中间件来满足 数据库结构 mysql-master&#…

渗透测试漏洞原理之---【业务安全】

文章目录 1、业务安全概述1.1业务安全现状1.1.1、业务逻辑漏洞1.1.2、黑客攻击目标 2、业务安全测试2.1、业务安全测试流程2.1.1、测试准备2.1.2、业务调研2.1.3、业务建模2.1.4、业务流程梳理2.1.5、业务风险点识别2.1.6 开展测试2.1.7 撰写报告 3、业务安全经典场景3.1、业务…