【Socket】Unix环境下搭建简易本地时间获取服务

本文搭建一个Unix环境下的、局域网内的、简易的本地时间获取服务。

主要用于验证:

  1. 当TCP连接成功后,可以在两个线程中分别进行读操作、写操作动作
  2. 当客户端自行终止连接后,服务端会在写操作时收到 SIGPIPE 信号
  3. 当客户端执行shutdown写操作后,客户端会在写操作时收到 SIGPIPE 信号
  4. 当客户端执行shutdown写操作后,服务端会在读操作时得到返回值 0

服务端功能:

  1. 轮询监听Client的连接(阻塞式)
  2. 创建并缓存会话对象
  3. 开启会话对象的读操作线(阻塞式IO)、写操作线程(阻塞式IO)
  4. 当读写操作线程退出时通过回调来执行资源释放(fd,会话对象)

客户端功能:

  1. 连接成功后直接开启读操作线程(阻塞式IO)、写操作线程(阻塞式IO)
  2. 在2秒后shutdown写端
  3. 在3秒后退出工作线程

(本文对打印进行了加锁,确保输出信息看起来更清晰,否则信息会混乱交错) 

服务端源码(局域网ip、端口port 按需自行修改噢😊):

// TimeServer.cpp#include <iostream>
#include <thread>
#include <vector>
#include <map>
#include <atomic>
#include <exception>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>#include "TimeConn.hpp"std::map<int, TimeConn> conn_map;int initServer(const std::string& ip, uint16_t port) {int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (server == -1) {std::cout << "socket failed, errno: " << strerror(errno) << std::endl;_exit(0);}sockaddr_in addr{.sin_family = AF_INET,.sin_port = htons(port)};int success = inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);if (success == 0) {std::cout << "invalid ip address, errno: " << strerror(errno) << std::endl;_exit(0);} else if (success == -1) {std::cout << "inet_pton error, errno: " << strerror(errno) << std::endl;_exit(0);}success = bind(server, reinterpret_cast<const sockaddr*>(&addr), sizeof(sockaddr_in));if (success == -1) {std::cout << "bind failed, errno: " << strerror(errno) << std::endl;_exit(0);}success = listen(server, 50);if (success == -1) {std::cout << "listen failed, errno: " << strerror(errno) << std::endl;_exit(0);}return server;
}void handleConn(int conn) noexcept(false) {sockaddr client_addr;socklen_t len = sizeof(decltype(client_addr));// 读取连接建立时client的信息auto success = getpeername(conn, &client_addr, &len);if (success == 0) {if (client_addr.sa_family == AF_INET) {sockaddr_in* ipv4 = reinterpret_cast<sockaddr_in*>(&client_addr);std::cout << "client ip: " << std::hex << ipv4->sin_addr.s_addr << std::dec << " port: " << ipv4->sin_port << std::endl;}} else if (success == -1) {std::cout << "getpeername failed, errno: " << strerror(errno) << std::endl;close(conn);return;}TimeConn& timeConn = conn_map[conn];timeConn.initConnFd(conn);timeConn.startRead([&timeConn](int conn_fd){if (timeConn.canClose()) {close(conn_fd);conn_map.erase(conn_fd);}});timeConn.startWrite([&timeConn](int conn_fd){if (timeConn.canClose()) {close(conn_fd);conn_map.erase(conn_fd);}});
}int main(int argc, char* argv[]) {std::cout << "Hello, I am server" << std::endl;std::string ip{"192.168.0.110"};auto server = initServer(ip, 10080);while (true) {std::cout << "Server accepting..." << std::endl;int conn = accept(server, nullptr, nullptr);if (conn == -1) {if (errno == EAGAIN) {continue;} else {std::cout << "accept failed, errno: " << strerror(errno) << std::endl;_exit(0);}}std::cout << "new connect! conn fd: " << conn << std::endl;try {handleConn(conn);} catch (std::exception& e) {// std::cout << "handleConn exception: " << e.what() << std::endl;}}close(server);return 0;
}

服务端会话源码:

// TimeConn.hpp#ifndef __TIMECONN_HPP__
#define __TIMECONN_HPP__#include <unistd.h>
#include <atomic>
#include <thread>class TimeConn
{
public:TimeConn(int conn = -1) : mConnFd{conn}, isReading{false}, isWriting{false}{// ...};virtual ~TimeConn(){close(mConnFd);};// constexpr TimeConn &operator=(const TimeConn &);public:void initConnFd(int conn);void startRead(std::function<void(int)> callback);void startWrite(std::function<void(int)> callback);void stopRead();void stopWrite();bool canClose();private:int mConnFd;std::atomic_bool isReading;std::atomic_bool isWriting;
};#endif
// TimeConn.cpp#include <chrono>
#include <iostream>
#include <mutex>
#include <sstream>
#include <sys/socket.h>
#include <signal.h>#include "TimeConn.hpp"// constexpr TimeConn &TimeConn::operator=(const TimeConn & other) {
//     this->mConnFd = other.mConnFd;
//     return *this;
// }static std::mutex m;static void print_log(const std::stringstream& ss) {std::lock_guard<std::mutex> lock(m);std::cout << ss.str() << std::endl;
}void TimeConn::initConnFd(int conn) {this->mConnFd = conn;
}void TimeConn::startRead(std::function<void(int)> callback) {using namespace std::literals;isReading = true;std::thread([this, callback]{std::stringstream ss;ss << "conn fd: " << this->mConnFd << " start read";print_log(ss);while (this->isReading) {char buffer[512];ssize_t res = recv(this->mConnFd, &buffer, sizeof(buffer), 0);if (res == 0) {std::stringstream ss1;ss1 << "no data or remote end";print_log(ss1);break;} else if (res == -1) {// errorstd::stringstream ss2;ss2 << "conn fd:" << this->mConnFd <<  " recv failed: " << strerror(errno);print_log(ss2);break;} else {std::stringstream ss3;ss3 << "recv success, count: " << res << " data: " << buffer;print_log(ss3);}}this->isReading = false;callback(this->mConnFd);// 注意!// 在经过callback后,若map进行了erase操作,则该TimeConn obj内存被清除,this->mConnFd值是不确定的,大概率是0,但也可能已被其它值占用std::stringstream ss4;ss4 << "conn fd " << this->mConnFd << " Reading finish";print_log(ss4);}).detach();
}void TimeConn::startWrite(std::function<void(int)> callback) {using namespace std::literals;isWriting = true;std::thread([this, callback]{std::stringstream ss;ss << "conn fd: " << this->mConnFd << " start write";print_log(ss);// send 时若该连接已关闭,则会产生SIGPIPE信号,程序默认执行动作是“退出进程”// 解决方案一 使用signal忽略SIGPIPE// signal(SIGPIPE, SIG_IGN);while (this->isWriting) {const auto now = std::chrono::system_clock::now();const std::time_t t_c = std::chrono::system_clock::to_time_t(now);const auto* t = std::ctime(&t_c);ssize_t res = -1;// 发送数据// send 时若该连接已关闭,则会产生SIGPIPE信号,程序默认执行动作是“退出进程”// 解决方案二(操作系统受限) 若操作系统支持,可以加上flag MSG_NOSIGNALres = send(this->mConnFd, t, strlen(t) + sizeof('\0'), MSG_DONTROUTE | MSG_NOSIGNAL);if (res == -1) {// errorstd::stringstream ss1;ss1 << "conn fd:" << this->mConnFd <<  " send failed: " << strerror(errno);print_log(ss1);break;} else {std::stringstream ss2;ss2 << "send success, count: " << res << " data: " << t;print_log(ss2);}// std::this_thread::sleep_for(1s);}this->isWriting = false;callback(this->mConnFd);// 注意!// 在经过callback后,若map进行了erase操作,则该TimeConn obj内存被清除,this->mConnFd值是不确定的,大概率是0,但也可能已被其它值占用std::stringstream ss3;ss3 << "conn fd " << this->mConnFd << " Writing finish";print_log(ss3);}).detach();
}void TimeConn::stopRead() {isReading = false;
}void TimeConn::stopWrite() {isWriting = false;
}bool TimeConn::canClose() {// 鉴于该示例启动线程的时机与退出线程的时机比较简单,所以无需加锁return !isReading && !isWriting;
}

本文中使用的recv、send函数都是用阻塞式IO,所以相应的返回值处理都是按照阻塞式时的错误来进行处理的。若采用非阻塞式IO,则处理方式并不是如此的。

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

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

相关文章

kettle+report designer导出带样式的excel包含多个sheet页

场景介绍&#xff1a; 运用pentaho report designer报表设计器&#xff0c;查询数据库字典表生成带有样式的excel&#xff0c;通过kettle pentaho报表输出组件导出形成数据字典&#xff0c;最终形成的数据字典样式如下图&#xff1a; 案例适用范围&#xff1a; pentaho repor…

1. Prism系列之数据绑定

Prism系列之数据绑定 文章目录 Prism系列之数据绑定一、安装Prism二、实现数据绑定三、更换数据源 一、安装Prism 创建一个WPF工程&#xff0c;创建名为 PrismNewSample 的WPF项目。 使用管理解决方案的Nuget包 在上面或许我们有个疑问&#xff1f; 为啥安装prism会跟Pri…

java springboot+jsoup写一段爬虫脚本 将指定地址的 图片链接 文本 超链接地址存入自己的属性类对象中

首先 还是最基本的 要在 pom.xml 引入依赖 <dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.14.1</version> </dependency>然后 我们可以在项目中创建一个属性类 我这里就叫 WebContent了…

STM32使用SIM900A、SIM800C、SIM800A完成短信发送、连接onenet上传数据、拨打电话_完整教程

一、前言 本篇文章介绍SIM800C 、SIM800A、SIM900A 等等系列的模块的常用AT指令,讲解模块的使用方法,演示短信发送、拨打电话、网络连接,与服务器通信等常用案例。 如果只是用到发送短信、拨打电话、连接网络通信、这些模块的AT指令是兼容的。 文章最后贴了完整的STM32代码…

isBlank和isEmpty的区别

在Java 11中&#xff0c;​isEmpty()​和 ​isBlank()​方法具有不同的行为和用途。 ​​isEmpty()​方法&#xff1a; ​​isEmpty()​方法是String类已经存在的方法&#xff0c;它用于检查一个字符串是否为空。如果字符串长度为0&#xff0c;则返回true&#xff1b;否则返回…

关于put_response和get_response的总结

Response总结&#xff0c;共三种情况 &#xff08;1&#xff09;每一笔都等response -> 直接get_response 注意put_response的前提是当前的seq的body没有直接结束&#xff0c;这个body一定是最底层发req的那个seq的body &#xff08;2&#xff09;连续发送数据&#xff0…

龙芯loongarch64安装numpy报错“No module named ‘numpy.core._multiarray_umath‘”

前言 在之前编译安装Python3.8的文章中说明了,龙芯仓库的很多包都有问题,安装之后很多无法使用,比如安装numpy后,就会出现“No module named numpy.core._multiarray_umath” 问题复现 配置pip源 vim /etc/pip.conf 复制下面的内容并保存 [global] timeout = 60 index-url…

Java小案例-RocketMQ的11种消息类型,你知道几种?(普通消息和批量消息)

前言 这篇给大家讲普通消息和批量消息&#xff0c;主要配合代码进行讲解&#xff0c;关于RocketMQ的基础知识已经在上篇给大家讲过需要回顾的点击下面这个链接去看 RocketMQ基础知识 普通消息 普通消息其实就很简单&#xff0c;是Apache RocketMQ中最基础的消息形式&#x…

电力行业的革新者:配网故障定位系统引领行业进步

随着科技的不断发展&#xff0c;电力行业也在不断地进行改革和创新。在这个过程中&#xff0c;恒峰智慧科技设计的配网故障定位系统作为一种先进的技术手段&#xff0c;正逐步引领行业的进步。本文将详细介绍配网行波型故障预警与定位系统的工作原理、功能特点以及在电力行业中…

easyrecovery14破解个人版数据恢复软件下载

easyrecovery14是easyrecovery系列软件的新版本&#xff0c;也是目前行业领先的数据恢复软件&#xff0c;具备更快捷、更高效、更便捷三大特色&#xff0c;能够帮助用户轻松恢复电脑丢失的数据。目前软件支持恢复不同存储介质数据&#xff0c;包括硬盘、光盘、U盘/移动硬盘、数…

236. 二叉树的最近公共祖先 --力扣 --JAVA

题目 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是它自…

C++笔记汇总(随时更新)

你好&#xff0c;这里是争做图书馆扫地僧的小白。 个人主页&#xff1a;争做图书馆扫地僧的小白_-CSDN博客 目标&#xff1a;希望通过学习技术&#xff0c;期待着改变世界。 目录 前言 一、C语言向C语言过度的知识点 二、C语言的相关知识 总结 前言 2023.12.13 之前撰写的笔…

前端开发领域的方向

1.脚手架工具&#xff1a;开发和维护一个通用的脚手架工具&#xff0c;可以帮助团队快速初始化项目结构、配置构建工具、集成常用的开发依赖等。 2.组件库&#xff1a;开发和维护一个内部的组件库&#xff0c;包含常用的UI组件、业务组件等&#xff0c;提供给团队成员复用&…

力扣刷题笔记——进制高低位储存数据

1920. 基于排列构建数组 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个 从 0 开始的排列 nums&#xff08;下标也从 0 开始&#xff09;。请你构建一个 同样长度 的数组 ans &#xff0c;其中&#xff0c;对于每个 i&#xff08;0 < i < nums.length&…

ProcessOn在线绘制部分项目流程图

目录 一、ProcessOn 1.1 简介 1.2 官方网站 二、Axure自定义元件库 2.1 新建元件库 2.2 自定义元件 2.3 添加元件库 三、HIS系统门诊流程图 四、HIS系统住院流程图 五、HIS系统药品采购入库流程图 六、OA会议流程图 一、ProcessOn 1.1 简介 ProcessOn是一款在线的流…

后台业务管理系统原型模板,Axure后台组件库(整套后台管理页面)

后台业务系统需要产品经理超强的逻辑思维能力和业务理解能力&#xff0c;整理了一批后台原型组件及完整的用 Axure 8 制作的后台系统页面&#xff0c;方便产品经理们快速上手制作后台原型。 包括交互元件、首页、商品、订单、库存、用户、促销、运营、内容、统计、财务、设置、…

备忘录在手机哪里 这个方法轻松让轻松找到

作为一个常常忙于各种事务的人&#xff0c;我经常需要记录一些重要的事情&#xff0c;以免忘记。以前我总是使用纸质备忘录&#xff0c;但是随着科技的发展&#xff0c;我现在开始使用手机备忘录。然而&#xff0c;有时候我会找不到备忘录在哪里&#xff0c;让我感到非常困扰。…

1、初识 llvm源码编译 及virtualbox和ubuntu环境搭建

很久没更新了&#xff0c;最近准备研究逆向和加固&#xff0c;于是跟着看雪hanbing老师学习彻底搞懂ollvm&#xff0c;终于把所有流程跑通了&#xff0c;中间遇到了太多的坑&#xff0c;所以必须记录一下&#xff0c;能避免自己和帮助他人最好。 环境搭建太重要了&#xff0c;…

Github、Gitee优秀的开源项目分享

先赞后看&#xff0c;养成习惯&#xff01;&#xff01;&#xff01;❤️ ❤️ ❤️ 资源收集不易&#xff0c;如果喜欢可以关注我哦&#xff01; ​如果本篇内容对你有所启发&#xff0c;欢迎访问我的个人博客了解更多内容&#xff1a;链接地址 ​ Java 项目 javacore - Java …

差分数组详解,一维二维差分

文章目录 差分数组引言差分数组的定义二维差分数组二维差分数组代码实现 总结OJ练习 差分数组 引言 如果给你一个包含500个元素的数组&#xff0c;让你把从第一个元素到第100个元素的值都加上1&#xff0c;你会毫不犹豫的说枚举&#xff01;那么如果给你一个包含5000万个元素的…