网络编程3

管道的性质

  1. 读缓冲区为空,read阻塞
  2. 写缓冲区为空,write阻塞
  3. 一端先close,另一端继续read,read不阻塞,立刻返回0
  4. 一端先close,另一端继续write,write会触发SIGPIPE信号,进程异常终止

socket和管道性质基本一致

socket的性质几乎和管道是一样的,唯一的区别是socket是全双工的

写端先close,读端继续recv:recv不阻塞,返回0。

读端先close,写端后send:读端会接接收到第一次的send,随后关闭。

使用gdb

使用命令行:gdb --args  <./xxxx>  <ip>   <port>

会开启gdb并且执行命令。

close

关闭文件描述符。具体类似于关闭管道。

基于tcp socket的即时聊天

实现的大致方式为:

 若使用:

while(1){read(stdin);……recv(通信socket);
}

会导致串联式问题

需要改成并联式解决问题

使用select IO 多路复用。(也可以使用多线程解决,但是IO多路复用比多线程好的多)

select IO多路复用

同时等待多个资源就绪

  1. 设计一个数据结构 fd_set 用来接收监听集合
  2. FD_ZERO(&set) 初始化数据结构,同时将关注的资源加入 fd_set ——FD_SET()
  3. 操作系统内核做轮询,进程在等待 select()
  4. 有任何一个资源就绪了,轮询结束,返回一个就绪集合
  5. 进程根据就绪集合去做IO操作 FD_ISSET()

代码实现:

使用bzero()初始化buf,需要加入头文件#include <strings.h>

sever.c

#include <my_header.h>/* Usage:  */
int main(int argc, char *argv[]){                                  ARGS_CHECK(argc,3);int sockfd = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(atoi(argv[2]));addr.sin_addr.s_addr = inet_addr(argv[1]);int ret = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));ERROR_CHECK(ret,-1,"bind");listen(sockfd,50);int netfd = accept(sockfd,NULL,NULL);char buf[4096];fd_set readset;//读集合while(1){FD_ZERO(&readset);//初始化FD_SET(STDIN_FILENO,&readset);//把stdin加入监听FD_SET(netfd,&readset);//把通信socket加入监听select(netfd+1,&readset,NULL,NULL,NULL);//内核轮询,进程等待//有至少一个就绪//此时readset变成就绪队列if(FD_ISSET(STDIN_FILENO,&readset)){bzero(buf,sizeof(buf));ssize_t sret = read(STDIN_FILENO,buf,sizeof(buf));if(sret == 0){send(sockfd,"serve closed\n",12,0);printf("I will close connect\n");break;}send(netfd,buf,strlen(buf),0);}  if(FD_ISSET(netfd,&readset)){//对面来消息了或者对面断开了bzero(buf,sizeof(buf));ssize_t sret = recv(netfd,buf,sizeof(buf),0);if(sret == 0){printf("close connect\n");break;}printf("buf = %s\n",buf);}}close(sockfd);close(netfd);return 0;
}

client.c

#include <my_header.h>/* Usage:  */
int main(int argc, char *argv[]){                                  ARGS_CHECK(argc,3);int sockfd = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(atoi(argv[2]));addr.sin_addr.s_addr = inet_addr(argv[1]);int ret = connect(sockfd,(struct sockaddr *)&addr,sizeof(addr));ERROR_CHECK(ret,-1,"connect");char buf[4096];fd_set readset;//读集合while(1){FD_ZERO(&readset);//初始化FD_SET(STDIN_FILENO,&readset);//把stdin加入监听FD_SET(sockfd,&readset);//把通信socket加入监听select(sockfd+1,&readset,NULL,NULL,NULL);//内核轮询,进程等待//有至少一个就绪//此时readset变成就绪队列if(FD_ISSET(STDIN_FILENO,&readset)){bzero(buf,sizeof(buf));ssize_t sret = read(STDIN_FILENO,buf,sizeof(buf));if(sret == 0){send(sockfd,"client closed\n",13,0);printf("I will close connect\n");break;}send(sockfd,buf,strlen(buf),0);}if(FD_ISSET(sockfd,&readset)){bzero(buf,sizeof(buf));ssize_t sret = recv(sockfd,buf,sizeof(buf),0);if(sret ==0){printf("close connect\n");break;}printf("buf =  %s\n",buf);}}close(sockfd);return 0;
}

服务端与客户端的大致代码一样,只需要做少量修改。可以复制一份在做更改即可。

vim文本更改技巧 :<n>,<m>s/<src>/<dec> 将第n行到第m行的src更改为dec

退出时,我们可以使用更加温和的方式,用ctrl + D实现退出。

Linux上 ctrl + D 是让recv就绪,并且返回值为0。

服务端主动退出

当退出后,短时间再次连接,主动退出的以访会进入TIME_WAIT状态,此状态下,bind函数会报错。socket库这样设计的原因是为了避免相同的四元组连接在短时间内断开又重连。实际上该设计过分严格,仅针对于bind,而我们在实现时,客户端并不使用bind,每次都是不同的ip与port,只有服务端使用了bind。

修改socket的属性

setsockopt(sockfd,level,optname,*optval,optlen)

这是一个函数,可以修改很多种类属性,不同种类的属性是不一样的

使用<man 7 socket>查询手册

在bind时无视TIME_WITE参数选项:(flag = 0/1,flag = 1允许重复使用本地址) 

optval 是参数的地址,optlen是参数的长度,用来支持多种类型。 

TIME_WITE状态依旧存在,只是在bind时无视掉了。

setsockopt()使用在服务端socket之后,bind之前

    int flag = 1;setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));

 

断线重连

应用层的断线重连,A退出了,B不要立刻终止,而是等待A的重新连接。

accept的本质是一个读全连接队列的操作,accept的阻塞就绪可以由select监听。select除了监听数据,还可以用来监听发有没有新的连接到来。

几种情况

A的状态

  • 无连接状态(单身状态):等待B连接。此时,select监听sockfd。
  • 通信状态(恋爱状态):等待stdin、B发送数据。此时select监听stdin 和 netfd。

解除监听集合和就绪集合之间的耦合

把两个无关的东西扯在一起称为耦合,现在需要解除耦合

    while(1){memcpy(&readset,&monitorset,sizeof(fd_set));select(nfds,&readset,NULL,NULL,NULL);}

若服务器A与B通信时,此时C与服务器A,请求建立连接,是可以成功的,服务器A将C放入全连接队列但是不进行通信。这是因为建立连接是由操作系统内核完成的。等到客户端B终止了,就可以从全连接队列取出C进行通信。

当客户端断开连接

客户端B断开连接,netfd就绪,recv的返回值是0。

此时需要:

  • 重新监听sockfd
  • 取消监听netfd和stdin
  • 修改netfd == -1

代码实现

select()第一个参数不知道写什么可以写1024。

serve.c

#include <my_header.h>/* Usage:  */
int main(int argc, char *argv[]){                                  ARGS_CHECK(argc,3);int sockfd = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(atoi(argv[2]));addr.sin_addr.s_addr = inet_addr(argv[1]);int flag = 1;setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));int ret = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));ERROR_CHECK(ret,-1,"bind");listen(sockfd,50);char buf[4096];fd_set readyset;//就绪集合fd_set monitorset;//监听集合FD_ZERO(&monitorset);FD_SET(sockfd,&monitorset);int netfd = -1;while(1){//用监听集合覆盖就绪集合memcpy(&readyset,&monitorset,sizeof(fd_set));select(1024,&readyset,NULL,NULL,NULL);//sockfd就绪,此时accept不会zuseif(FD_ISSET(sockfd,&readyset)){netfd = accept(sockfd,NULL,NULL);printf("connect\n");//移除对sockfd的监听FD_CLR(sockfd,&monitorset);//增加对stdin以及netfd的监听FD_SET(STDIN_FILENO,&monitorset);FD_SET(netfd,&monitorset);}//stdin状态if(netfd != -1 && FD_ISSET(STDIN_FILENO,&readyset)){bzero(buf,sizeof(buf));read(STDIN_FILENO,buf,sizeof(buf));send(netfd,buf,strlen(buf),0);}//netfd就绪状态if(netfd != -1 && FD_ISSET(netfd,&readyset)){bzero(buf,sizeof(buf));ssize_t sret = recv(netfd,buf,sizeof(buf),0);if(sret == 0){FD_CLR(netfd,&monitorset);FD_CLR(STDIN_FILENO,&monitorset);FD_SET(sockfd,&monitorset);close(netfd);netfd = -1;continue;}printf("%s\n",buf);}}close(sockfd);return 0;
}

client.c

#include <my_header.h>/* Usage:  */
int main(int argc, char *argv[]){                                  ARGS_CHECK(argc,3);int sockfd = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(atoi(argv[2]));addr.sin_addr.s_addr = inet_addr(argv[1]);int ret = connect(sockfd,(struct sockaddr *)&addr,sizeof(addr));ERROR_CHECK(ret,-1,"connect");char buf[4096];fd_set readset;//读集合while(1){FD_ZERO(&readset);//初始化FD_SET(STDIN_FILENO,&readset);//把stdin加入监听FD_SET(sockfd,&readset);//把通信socket加入监听select(sockfd+1,&readset,NULL,NULL,NULL);//内核轮询,进程等待//有至少一个就绪//此时readset变成就绪队列if(FD_ISSET(STDIN_FILENO,&readset)){bzero(buf,sizeof(buf));ssize_t sret = read(STDIN_FILENO,buf,sizeof(buf));if(sret == 0){send(sockfd,"client closed\n",13,0);printf("I will close connect\n");break;}send(sockfd,buf,strlen(buf),0);}if(FD_ISSET(sockfd,&readset)){bzero(buf,sizeof(buf));ssize_t sret = recv(sockfd,buf,sizeof(buf),0);if(sret ==0){printf("close connect\n");break;}printf("buf =  %s\n",buf);}}close(sockfd);return 0;
}

 

群聊

模型

客户端实现同上的断线重连

服务端的几种监听状态:

  • 没有客户端,监听sockfd
  • 每连入一个客户端,监听对应的netfd,原来的sockfd依然保持监听
  • 客户端断开连接时,要取消监听相关的netfd

由于netfd将会由很多个,我们需要设计数据结构去管理它。

设计一个数组管理netfd

    int netfd[1024];//用netfd数组中的元素来存放所有的netfdint curidx = 0;//下一次进入netfd数组的元素的下标

写代码时,先考虑数据结构怎么处理、存储,接着就知道如何去进行增删改查了。 

        if(FD_ISSET(sockfd,&readyset)){netfd[curidx] = accept(sockfd,NULL,NULL);printf("curidx = %d, netfd = %d\n",curidx,netfd[curidx]);FD_SET(netfd[curidx],&monitorset);++curidx;}

在监听到sockfd连接时,使用下一个数组元素accept。

代码实现

sever.c

#include <my_header.h>/* Usage:  */
int main(int argc, char *argv[]){                                  ARGS_CHECK(argc,3);int sockfd = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(atoi(argv[2]));addr.sin_addr.s_addr = inet_addr(argv[1]);int flag = 1;setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));int ret = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));ERROR_CHECK(ret,-1,"bind");listen(sockfd,50);char buf[4096];fd_set readyset;//就绪集合fd_set monitorset;//监听集合//一直监听sockfdFD_ZERO(&monitorset);FD_SET(sockfd,&monitorset);int netfd[1024];//用netfd数组中的元素来存放所有的netfdint curidx = 0;//下一次进入netfd数组的元素的下标for(int i=0;i<1024;i++){netfd[i]=-1;}while(1){memcpy(&readyset,&monitorset,sizeof(fd_set));select(1024,&readyset,NULL,NULL,NULL);if(FD_ISSET(sockfd,&readyset)){netfd[curidx] = accept(sockfd,NULL,NULL);printf("curidx = %d, netfd = %d\n",curidx,netfd[curidx]);FD_SET(netfd[curidx],&monitorset);++curidx;}//已连接的客户端发消息for(int i=0;i<curidx;i++){//i号客户端来消息if(netfd[i]!=-1&&FD_ISSET(netfd[i],&readyset)){bzero(buf,sizeof(buf));ssize_t sret = recv(netfd[i],buf,sizeof(buf),0);if(sret == 0){printf("client disconnected i = %d, netfd = %d\n",i,netfd[i]);FD_CLR(netfd[i],&monitorset);close(netfd[i]);netfd[i] = -1;continue;}for(int j =0 ;j<curidx;j++){if(j!=i&&netfd[i] != -1){send(netfd[j],buf,strlen(buf),0);}}}}}close(sockfd);return 0;
}

 当然也可以使用删除,将退出netfd元素后所有元素前移。

产生错误处理方式

  1. 使用gdb复现bug   <gdb  --args   ./xxxxx>
  2. 查看错误提示
  3. bt 从上往下找到自己写的代码,分析修改

扩展

服务端超时踢人

如果客户端十秒没有通信,服务器将他踢掉。

timeout是一个传入传出参数,如果是固定的超时时间,把设置timeout放在循环里面。

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

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

相关文章

influxdb时序数据库

以下概念及操作均来自influxdb2 官方文档 InfluxDB2 is the platform purpose-built to collect, store, process and visualize time series data. Time series data is a sequence of data points indexed in time order. Data points typically consist of successive meas…

洛谷 P3372 【模板】线段树 1

【题目链接】 洛谷 P3372 【模板】线段树 1 【题目考点】 1. 线段树 2. 树状数组 【解题思路】 本题要求维护区间和&#xff0c;实现区间修改、区间查询。 可以使用树状数组或线段树完成该问题&#xff0c;本文仅介绍使用线段树的解法。 解法1&#xff1a;线段树 线段树…

软件更新 | TSMaster 202504 版本已上线!三大功能让车载测试更智能

车载测试的智能化时代正在加速到来&#xff01;TSMaster 202504 版本正式发布&#xff0c;本次更新聚焦以太网通信与数据高效处理&#xff0c;带来三大核心功能升级—以太网报文信息过滤、XCP on Ethernet支持、按时间范围离线回放&#xff0c;助力工程师更精准、更灵活地完成测…

java-单列集合list与set。

集合定位&#xff1a;存储数据的容器 与数组的区别&#xff1a; 数组只能存储同种数据类型数据&#xff0c;集合可以存储不同类型的数据。 数组的长度一旦创建长度不可变&#xff0c;集合的长度是可变的 数组的操作单一&#xff0c;集合的操作比较丰富&#xff08;增删改查&…

ai之pdf解析工具 PPStructure 还是PaddleOCR

目录 重点是四 先用 PPStructure 版面分析,分成不同的块儿,再选用 PaddleOCR、或PPStructure基础路径OCR模型配置OCR模型配置GPU配置硬件配置性能配置一、框架选型对比分析1. **PaddleOCR核心能力**2. **PP-Structure核心能力**3. **选型结论**二、错误根因分析与修复方案1. …

Android计算机网络学习总结

TCP vs UDP 核心区别​​ ​题目​&#xff1a;TCP为什么称为可靠传输协议&#xff1f;UDP在哪些场景下比TCP更具优势&#xff1f; ​得分要点​&#xff1a; ​可靠性机制​ 三握四挥建立可靠连接确认应答&#xff08;ACK&#xff09; 超时重传滑动窗口流量控制拥塞控制&…

深入解析Java组合模式:构建灵活树形结构的艺术

引言&#xff1a;当对象需要树形组织时 在日常开发中&#xff0c;我们经常需要处理具有层次结构的对象集合。比如&#xff1a; 文件系统中的文件夹与文件GUI界面中的容器与控件企业组织架构中的部门与员工 这类场景中的对象呈现明显的整体-部分层次结构&#xff0c;如何优雅…

mobaxterm通过ssh登录docker无图形界面

1. 流程 下面是使用Mobaxterm通过SSH登录Docker无图形界面的步骤&#xff1a; 步骤 操作 1 在本地安装Mobaxterm 2 配置Mobaxterm连接SSH 3 启动Docker容器 4 在Mobaxterm中通过SSH连接到Docker容器 2. 操作步骤 步骤1&#xff1a;安装Mobaxterm 首先&#xff…

【赵渝强老师】HBase的体系架构

HBase是大表&#xff08;BigTable&#xff09;思想的一个具体实现。它是一个列式存储的NoSQL数据库&#xff0c;适合执行数据的分析和处理。简单来说&#xff0c;就是适合执行查询操作。从体系架构的角度看&#xff0c;HBase是一种主从架构&#xff0c;包含&#xff1a;HBase H…

linux 新增驱动宏config.in配置

‌1. 添加配置宏步骤‌ ‌1.1 修改 Kconfig&#xff08;推荐方式&#xff09;‌ ‌定位 Kconfig 文件‌ 内核各子目录&#xff08;如 drivers/char/&#xff09;通常包含 Kconfig 文件&#xff0c;用于定义模块配置选项7。‌添加宏定义‌ 示例&#xff1a;在 drivers/char/Kc…

关于git的使用

下载git 可以去git的官网下载https://git-scm.com/downloads 也可以去找第三方的资源下载&#xff0c;下载后是一个exe应用程序&#xff0c;直接点开一直下一步就可以安装了 右键任意位置显示这两个就代表成功&#xff0c;第一个是git官方的图形化界面&#xff0c;第二个是用…

WPF【11_8】WPF实战-重构与美化(UI 与视图模型的联动,实现INotifyPropertyChanged)

11-13 【重构】INotifyPropertyChanged 与 ObservableCollection 现在我们来完成新建客户的功能。 当用户点击“客户添加”按钮以后系统会清空当前所选定的客户&#xff0c;客户的详细信息以及客户的预约记录会从 UI 中被清除。然后我们就可以在输入框中输入新的客户信息了&am…

ArkUI:鸿蒙应用响应式与组件化开发指南(一)

文章目录 引言1.ArkUI核心能力概览1.1状态驱动视图1.2组件化&#xff1a;构建可复用UI 2.状态管理&#xff1a;从单一组件到全局共享2.1 状态装饰器2.2 状态传递模式对比 引言 鸿蒙生态正催生应用开发的新范式。作为面向全场景的分布式操作系统&#xff0c;鸿蒙的北向应用开发…

List优雅分组

一、前言 最近小永哥发现&#xff0c;在开发过程中&#xff0c;经常会遇到需要对list进行分组&#xff0c;就是假如有一个RecordTest对象集合&#xff0c;RecordTest对象都有一个type的属性&#xff0c;需要将这个集合按type属性进行分组&#xff0c;转换为一个以type为key&…

AI与.NET技术实操系列(八):使用Catalyst进行自然语言处理

引言 自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;是人工智能领域中最具活力和潜力的分支之一。从智能客服到机器翻译&#xff0c;再到语音识别&#xff0c;NLP技术正以其强大的功能改变着我们的生活方式和工作模式。 Catalyst的推出极大降低了NLP…

MySQL 8.0 OCP 1Z0-908 题目解析(13)

题目49 Choose the best answer. t is a non - empty InnoDB table. Examine these statements, which are executed in one session: BEGIN; SELECT * FROM t FOR UPDATE;Which is true? ○ A) mysqlcheck --analyze --all - databases will execute normally on all ta…

Docker 一键部署倒计时页面:Easy Countdown全设备通用

Easy Countdown 介绍 Easy countdown是一个易于设置的倒计时页面。可以设置为倒计时或计时器。可用于个人生活、工作管理、教育、活动策划等多个领域。 &#x1f6a2; 项目地址 Github&#xff1a;https://github.com/Yooooomi/easy-countdown &#x1f680;Easy Countdown …

Python训练打卡Day35

模型可视化与推理 知识点回顾&#xff1a; 三种不同的模型可视化方法&#xff1a;推荐torchinfo打印summary权重分布可视化进度条功能&#xff1a;手动和自动写法&#xff0c;让打印结果更加美观推理的写法&#xff1a;评估模式 模型结构可视化 理解一个深度学习网络最重要的2点…

四、生活常识

一、效应定律 效应 1、沉没成本效应 投入的越多&#xff0c;退出的难度就越大&#xff0c;因为不甘心自己之前的所有付出都付之东流。 2、破窗效应 干净的环境下&#xff0c;没有人会第一个丢垃圾&#xff0c;但是当环境变得糟糕&#xff0c;人们就开始无所妒忌的丢垃圾。…

机器学习圣经PRML作者Bishop20年后新作中文版出版!

机器学习圣经PRML作者Bishop20年后新书《深度学习&#xff1a;基础与概念》出版。作者克里斯托弗M. 毕晓普&#xff08;Christopher M. Bishop&#xff09;微软公司技术研究员、微软研究 院 科学智 能 中 心&#xff08;Microsoft Research AI4Science&#xff09;负责人。剑桥…