16天自制CppServer-day02
day02-设置错误与异常处理机制
上一天我们写了一个客户端与服务器通过socket进行连接,对socket
,bind
,listen
,accept
,connect
等函数,我们都设想程序完美地、没有任何异常地运行,但显然这不现实,应该设置出现异常的处理机制,方便我们定位到bug。
我们前面说过sockfd一般从3开始,若是sockfd为-1,则很明显出现异常。
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1) {
print("socket create error");
exit(-1);
}
为了处理一个错误,需要至少占用五行代码,这使编程十分繁琐,程序也不好看,异常处理所占篇幅比程序本身都多。
为了方便编码以及代码的可读性,可以封装一个错误处理函数:
void errif(bool condition, const char *errmsg){if(condition){perror(errmsg);exit(EXIT_FAILURE);}
}
第一个参数是是否发生错误,如果为真,则表示有错误发生,会调用<stdio.h>头文件中的perror,这个函数会打印出errno的实际意义,还会打印出我们传入的字符串,也就是第函数第二个参数,让我们很方便定位到程序出现错误的地方。然后使用<stdlib.h>中的exit函数让程序退出并返回一个预定义常量EXIT_FAILURE。
上面需要通过五行代码来处理可以改写为如下形式:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
errif(sockfd == -1, "socket create error");
对于所有的socket
,bind
,listen
,accept
,connect
等函数,我们都使用这种方式处理错误:
errif(bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket bind error");
errif(listen(sockfd, SOMAXCONN) == -1, "socket listen error");
int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);
errif(clnt_sockfd == -1, "socket accept error");
errif(connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket connect error");
到现在最简单的错误处理函数已经封装好了,但这仅仅用于本教程的开发,在真实的服务器开发中,错误绝不是一个如此简单的话题。
当我们建立一个socket连接后,就可以使用<unistd.h>头文件中read和write来进行网络接口的数据读写操作了。
这两个函数用于TCP连接。如果是UDP,需要使用sendto和recvfrom,这些函数的详细用法可以参考游双《Linux高性能服务器编程》第五章第八节。
接下来的教程用注释的形式写在代码中,先来看服务器代码:
while (true) {char buf[1024]; //定义缓冲区memset(&buf, 0, sizeof(buf)); //清空缓冲区ssize_t read_bytes = read(clnt_sockfd, buf, sizeof(buf)); //从客户端socket读到缓冲区,返回已读数据大小if(read_bytes > 0){printf("message from client fd %d: %s\n", clnt_sockfd, buf); write(clnt_sockfd, buf, sizeof(buf)); //将相同的数据写回到客户端} else if(read_bytes == 0){ //read返回0,表示EOFprintf("client fd %d disconnected\n", clnt_sockfd);close(clnt_sockfd);break;} else if(read_bytes == -1){ //read返回-1,表示发生错误,按照上文方法进行错误处理close(clnt_sockfd);errif(true, "socket read error");}
}
客户端代码逻辑是一样的:
while(true){char buf[1024]; //定义缓冲区memset(&buf, 0,sizeof(buf)); //清空缓冲区scanf("%s", buf); //从键盘输入要传到服务器的数据ssize_t write_bytes = write(sockfd, buf, sizeof(buf)); //发送缓冲区中的数据到服务器socket,返回已发送数据大小if(write_bytes == -1){ //write返回-1,表示发生错误printf("socket already disconnected, can't write any more!\n");break;}memset(&buf, 0,sizeof(buf)); //清空缓冲区 ssize_t read_bytes = read(sockfd, buf, sizeof(buf)); //从服务器socket读到缓冲区,返回已读数据大小if(read_bytes > 0){printf("message from server: %s\n", buf);}else if(read_bytes == 0){ //read返回0,表示EOF,通常是服务器断开链接,等会儿进行测试printf("server socket disconnected!\n");break;}else if(read_bytes == -1){ //read返回-1,表示发生错误,按照上文方法进行错误处理close(sockfd);errif(true, "socket read error");}
}
一个小细节,Linux系统的文件描述符理论上是有限的,在使用完一个fd之后,需要使用头文件
<unistd.h>
中的close
函数关闭。更多内核相关知识可以参考Robert Love《Linux内核设计与实现》的第三版。
由于是一个while(true)循环,客户端可以一直输入,服务器也会一直echo我们的消息。由于scanf函数的特性,输入的语句遇到空格时,会当成多行进行处理,我们可以试试。
接下来在客户端使用control+c终止程序,可以看到服务器也退出了程序并显示:
client fd 4 disconnected
再次运行两个程序,这次我们使用control+c终止掉服务器,再试图从客户端发送信息,可以看到客户端输出:
server socket disconnected!
至此,我们已经完整地开发了一个echo服务器,并且有最基本的错误处理!
但现在,我们的服务器只能处理一个客户端,我们可以试试两个客户端同时连接服务器,看程序将会如何运行。在day03的教程里,我们将会讲解Linux系统高并发的基石–epoll,并编程实现一个可以支持无数客户端同时连接的echo服务器!
服务端源码:
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include "util.h"int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);errif(sockfd == -1, "socket create error");struct sockaddr_in serv_addr;memset&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");serv_addr.sin_port = htons(8888);errif(bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket bind error");errif(listen(sockfd, SOMAXCONN) == -1, "socket listen error");struct sockaddr_in clnt_addr;socklen_t clnt_addr_len = sizeof(clnt_addr);memset(&clnt_addr, 0, sizeof(clnt_addr));int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);errif(clnt_sockfd == -1, "socket accept error");printf("new client fd %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));while (true) {char buf[1024];memset(&buf, 0, sizeof(buf));ssize_t read_bytes = read(clnt_sockfd, buf, sizeof(buf));if(read_bytes > 0){printf("message from client fd %d: %s\n", clnt_sockfd, buf);write(clnt_sockfd, buf, sizeof(buf));} else if(read_bytes == 0){printf("client fd %d disconnected\n", clnt_sockfd);close(clnt_sockfd);break;} else if(read_bytes == -1){close(clnt_sockfd);errif(true, "socket read error");}}close(sockfd);return 0;
}
客户端源码:
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include "util.h"int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);errif(sockfd == -1, "socket create error");struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");serv_addr.sin_port = htons(8888);errif(connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket connect error");while(true){char buf[1024];memset(&buf, 0, sizeof(buf));scanf("%s", buf);ssize_t write_bytes = write(sockfd, buf, sizeof(buf));if(write_bytes == -1){printf("socket already disconnected, can't write any more!\n");break;}memset(&buf, 0, sizeof(buf));ssize_t read_bytes = read(sockfd, buf, sizeof(buf));if(read_bytes > 0){printf("message from server: %s\n", buf);}else if(read_bytes == 0){printf("server socket disconnected!\n");break;}else if(read_bytes == -1){close(sockfd);errif(true, "socket read error");}}close(sockfd);return 0;
}
异常处理函数:
#include "util.h"
#include <stdio.h>
#include <stdlib.h>void errif(bool condition, const char *errmsg){if(condition){perror(errmsg);exit(EXIT_FAILURE);}
}
异常处理头文件
#ifndef UTIL_H
#define UTIL_Hvoid errif(bool, const char*);#endif
Makefile :
server:
g++ util.cpp client.cpp -o client && \
g++ util.cpp server.cpp -o server
clean:
rm server && rm client
相关文章:
16天自制CppServer-day02
day02-设置错误与异常处理机制 上一天我们写了一个客户端与服务器通过socket进行连接,对socket,bind,listen,accept,connect等函数,我们都设想程序完美地、没有任何异常地运行,但显然这不现实,应该设置出现异常的处理机制&#x…...

时空智友企业流程化管控系统uploadStudioFile接口存在任意文件上传漏洞
免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。 1. 时空智友…...

Linux 中文件的权限说明
目录 一:文件权限类型二:默认权限管理1. 查看当前用户的umask值2. 修改当前用户的umask值3. 根据umask计算默认权限 三:普通权限管理1. 三种普通权限说明1.1 对于非目录文件来说1.2 对于目录文件来说 2. 查看某个文件的权限信息2.1 使用 ls -…...

MySql数据库中数据类型
本篇将介绍在 MySql 中的所有数据类型,其中主要分为四类:数值类型、文本和二进制类型、时间日期、String 类型。如下(图片来源:MySQL数据库): 目录如下: 目录 数值类型 1. 整数类型 2. …...

Godot中的信号
目录 概念 signal connect方法连接Callable 信号要求参数 查看信号 连接信号 监听信号 Button - text属性 pressed 连接源 「按钮」的信号连接 使用代码,将方法与信号相连接 节点的connect方法 节点直接使用emit_signal方法通过字符串的方式触发信号…...
vba学习系列(8)--指定列单元格时间按时间段计数
系列文章目录 文章目录 系列文章目录前言一、背景二、VBA总结 前言 一、背景 时间格式:00:00:00 时间段格式:00:00:00 - 01:00:00 计数N列单元格时间位于时间段内的行数 二、VBA 代码如下(示例): Sub AssignTimeSeg…...

大型企业软件开发是什么样子的? - Web Dev Cody
引用自大型企业软件开发是什么样子的? - Web Dev Cody_哔哩哔哩_bilibili 一般来说 学技术的时候 我们会关注 开发语言特性 ,各种高级语法糖,底层技术 但是很少有关注到企业里面的开发流程,本着以终为始(以就业为导向…...

【stm32】DMA的介绍与使用
DMA的介绍与使用 1、DMA简介2、存储器映像3、DMA框图4、DMA基本结构5、DMA请求6、数据宽度与对齐7、数据转运DMA(存储器到存储器的数据转运)程序编写: 8、ADC连续扫描模式DMA循环转运DMA配置:程序编写: 1、DMA简介 DM…...

哈希表的魔力
哈希表与字典 普遍存在一种误解,认为“哈希表”和“字典”这两个术语可以互换。这种观念从根本上是不准确的,至少在计算机科学领域是如此。 字典是将键映射到值的数据结构的一般概念。而哈希表是字典的具体实现。 本质上,字典扮演着一个总体…...

《YOLO 目标检测》—— YOLO v3 详细介绍
!!!!!!!!!!!!!还未写完!!!!!!!…...

WNN 多模态整合 | Seurat 单细胞多组学整合流程
测试环境:CentOS7.9, R4.3.2, Seurat 4.4.0, SeuratObject 4.1.4 2024.10.23 # WNN library(ggplot2) library(dplyr) library(patchwork)1. 导入数据 (1). load counts of RNA and protein dyn.load(/home/wangjl/.local/lib/libhdf5_hl.so.100) library(hdf5r)…...

【Linux】磁盘文件系统(inode)、软硬链接
文章目录 1. 认识磁盘1.1 磁盘的物理结构1.2 磁盘的逻辑结构 2. 引入文件系统2.1 EXT系列文件系统的分区结构2.2 inode 3. 软硬链接3.1 软链接3.2 硬链接 在讲过了内存文件系统后,我们可以知道文件分为两种: 打开的文件(内存中)未…...

网安加·百家讲坛 | 徐一丁:金融机构网络安全合规浅析
作者简介:徐一丁,北京小西牛等保软件有限公司解决方案部总监,网络安全高级顾问。2000年开始从事网络安全工作,主要领域为网络安全法规标准研究、金融行业安全咨询与解决方案设计、信息科技风险管理评估等。对国家网络安全法规标准…...

九、pico+Unity交互开发——触碰抓取
一、VR交互的类型 Hover(悬停) 定义:发起交互的对象停留在可交互对象的交互区域。例如,当手触摸到物品表面(可交互区域)时,视为触发了Hover。 Grab(抓取) 概念ÿ…...

老机MicroServer Gen8再玩 OCP万兆光口+IT直通
手上有一台放了很久的GEN8微型服务器,放了很多年,具体什么时候买的我居然已经记不清了 只记得开始装修的时候搬家出去就没用了,结果搬出去有了第1个孩子,孩子小的时候也没时间折腾,等孩子大一点的时候,又有…...

jmeter 从多个固定字符串中随机取一个值的方法
1、先新增用户参数,将固定值设置为不同的变量 2、使用下面的函数,调用这写变量 ${__RandomFromMultipleVars(noticeType1|noticeType2|noticeType3|noticeType4|noticeType5)} 3、每次请求就是随机取的值了...

priority_queue (优先级队列的使用和模拟实现)
使用 priority_queue 优先级队列与 stack 和 queue 一样,也是一个容器适配器,其底层通过 vector 来实现的。与 stack 和 queue 不同的是,它的第一个元素总是它所包含的元素中最大或最小的一个。 也就是说,优先级队列就是数据结…...
VisionPro 手部骨骼跟踪 Skeletal Hand Tracking 虚拟首饰
骨骼手部跟踪由XR Hands Package中的Hand Subsystem提供。使用场景中的Hand Visualizer组件,用户可以显示玩家手部的蒙皮网格或每个关节的几何图形,以及用于基于手部物理交互的物理对象。用户可以直接针对Hand Subsystem编写 C# 脚本,以推断骨…...

class 9: vue.js 3 组件化基础(2)父子组件间通信
目录 父子组件之间的相互通信父组件传递数据给子组件Prop为字符串类型的数组Prop为对象类型 子组件传递数据给父组件 父子组件之间的相互通信 开发过程中,我们通常会将一个页面拆分成多个组件,然后将这些组件通过组合或者嵌套的方式构建页面。组件的嵌套…...
Laravel|Lumen项目配置信息config原理
介绍 Laravel 框架的所有配置文件都保存在 config 目录中。每个选项都有说明,你可随时查看这些文件并熟悉都有哪些配置选项可供你使用。 使用 您可以在应用程序的任何位置使用全局 config 辅助函数轻松访问配置值。 可以使用“点”语法访问配置值,其中…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...