当前位置: 首页 > news >正文

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进行连接&#xff0c;对socket,bind,listen,accept,connect等函数&#xff0c;我们都设想程序完美地、没有任何异常地运行&#xff0c;但显然这不现实&#xff0c;应该设置出现异常的处理机制&#x…...

时空智友企业流程化管控系统uploadStudioFile接口存在任意文件上传漏洞

免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。 1. 时空智友…...

Linux 中文件的权限说明

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

MySql数据库中数据类型

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

Godot中的信号

目录 概念 signal connect方法连接Callable 信号要求参数 查看信号 连接信号 监听信号 Button - text属性 pressed 连接源 「按钮」的信号连接 使用代码&#xff0c;将方法与信号相连接 节点的connect方法 节点直接使用emit_signal方法通过字符串的方式触发信号…...

vba学习系列(8)--指定列单元格时间按时间段计数

系列文章目录 文章目录 系列文章目录前言一、背景二、VBA总结 前言 一、背景 时间格式&#xff1a;00:00:00 时间段格式&#xff1a;00:00:00 - 01:00:00 计数N列单元格时间位于时间段内的行数 二、VBA 代码如下&#xff08;示例&#xff09;&#xff1a; Sub AssignTimeSeg…...

大型企业软件开发是什么样子的? - Web Dev Cody

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

【stm32】DMA的介绍与使用

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

哈希表的魔力

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

《YOLO 目标检测》—— YOLO v3 详细介绍

&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;还未写完&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xf…...

WNN 多模态整合 | Seurat 单细胞多组学整合流程

测试环境&#xff1a;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 硬链接 在讲过了内存文件系统后&#xff0c;我们可以知道文件分为两种&#xff1a; 打开的文件&#xff08;内存中&#xff09;未…...

网安加·百家讲坛 | 徐一丁:金融机构网络安全合规浅析

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

九、pico+Unity交互开发——触碰抓取

一、VR交互的类型 Hover&#xff08;悬停&#xff09; 定义&#xff1a;发起交互的对象停留在可交互对象的交互区域。例如&#xff0c;当手触摸到物品表面&#xff08;可交互区域&#xff09;时&#xff0c;视为触发了Hover。 Grab&#xff08;抓取&#xff09; 概念&#xff…...

老机MicroServer Gen8再玩 OCP万兆光口+IT直通

手上有一台放了很久的GEN8微型服务器&#xff0c;放了很多年&#xff0c;具体什么时候买的我居然已经记不清了 只记得开始装修的时候搬家出去就没用了&#xff0c;结果搬出去有了第1个孩子&#xff0c;孩子小的时候也没时间折腾&#xff0c;等孩子大一点的时候&#xff0c;又有…...

jmeter 从多个固定字符串中随机取一个值的方法

1、先新增用户参数&#xff0c;将固定值设置为不同的变量 2、使用下面的函数&#xff0c;调用这写变量 ${__RandomFromMultipleVars(noticeType1|noticeType2|noticeType3|noticeType4|noticeType5)} 3、每次请求就是随机取的值了...

priority_queue (优先级队列的使用和模拟实现)

使用 priority_queue 优先级队列与 stack 和 queue 一样&#xff0c;也是一个容器适配器&#xff0c;其底层通过 vector 来实现的。与 stack 和 queue 不同的是&#xff0c;它的第一个元素总是它所包含的元素中最大或最小的一个。 也就是说&#xff0c;优先级队列就是数据结…...

VisionPro 手部骨骼跟踪 Skeletal Hand Tracking 虚拟首饰

骨骼手部跟踪由XR Hands Package中的Hand Subsystem提供。使用场景中的Hand Visualizer组件&#xff0c;用户可以显示玩家手部的蒙皮网格或每个关节的几何图形&#xff0c;以及用于基于手部物理交互的物理对象。用户可以直接针对Hand Subsystem编写 C# 脚本&#xff0c;以推断骨…...

class 9: vue.js 3 组件化基础(2)父子组件间通信

目录 父子组件之间的相互通信父组件传递数据给子组件Prop为字符串类型的数组Prop为对象类型 子组件传递数据给父组件 父子组件之间的相互通信 开发过程中&#xff0c;我们通常会将一个页面拆分成多个组件&#xff0c;然后将这些组件通过组合或者嵌套的方式构建页面。组件的嵌套…...

Laravel|Lumen项目配置信息config原理

介绍 Laravel 框架的所有配置文件都保存在 config 目录中。每个选项都有说明&#xff0c;你可随时查看这些文件并熟悉都有哪些配置选项可供你使用。 使用 您可以在应用程序的任何位置使用全局 config 辅助函数轻松访问配置值。 可以使用“点”语法访问配置值&#xff0c;其中…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...