Linux__之__基于UDP的Socket编程网络通信

前言

本篇博客旨在使用Linux系统接口进行网络通信, 帮助我们更好的熟悉使用socket套接字网络通信, 学会了socket网络通信, 就能发现所谓网络, 不过都是套路而已, 话不多说, 让我们直接进入代码编写部分.

1. 事先准备

今天我们先来模拟实现一个echo demo, 也就是客户端向服务器发送信息, 服务器能接收信息, 并且进行回显, 为了代码的可读性和可调试性, 我们在其中使用日志信息, 接下来我会带领大家手撕一个日志代码, 之后运用到我们的echo demo中, 在日志中, 如果想访问临界资源, 我们需要进行加锁和解锁, 这里我也会带领大家, 基于linux系统调用进行锁的封装, 使得我们的锁使用起来更加方便.

1.1 Mutex.hpp

想要进行锁的封装, 那我们首先需要了解一下锁, 这里就不过多赘述锁的定义, 简单来说锁是原子性的, 当操作系统在时间片的作用下进行进程间轮转调度时, 会发生进程间切换,多线程同步访问共享资源时, 可能导致我们的数据不安全, 为了保护临界资源所采取的一种措施就是锁.

锁的定义的方式有两种, 一种是全局使用宏进行初始化, 不需要手动释放, 由操作系统进行释放, 一种是局部定义使用init进行初始化,我们这里使用init初始化, 第一个参数是锁, 第二个参数为锁的属性, 默认为nullptr就行了, 然后销毁就是用destory系统调用, 我们对这些进行封装, 下面创建了一个LockGuard类, 我们利用对象的特性, 出了局部作用域会自动释放的特点, 进一步简化了锁的使用.

#pragma once
#include <iostream>
#include <pthread.h>namespace LockMoudle
{class Mutex{public:Mutex(const Mutex&) = delete;const Mutex& operator=(const Mutex&) = delete;Mutex(){int n = ::pthread_mutex_init(&_lock, nullptr);(void)n;}~Mutex(){int n = ::pthread_mutex_destroy(&_lock);(void)n;}void Lock(){//加锁int n = pthread_mutex_lock(&_lock);(void)n;}//获取锁pthread_mutex_t *LockPtr(){return &_lock;}//解锁void Unlock(){int n = ::pthread_mutex_unlock(&_lock);(void)n;}private:pthread_mutex_t _lock;};class LockGuard{public:LockGuard(Mutex &mtx):_mtx(mtx){_mtx.Lock();}~LockGuard(){_mtx.Unlock();}private:Mutex &_mtx;};
}

1.2 Log.hpp

在日志类里, 如果使用文件策略, 为了防止多线程并发访问, 创建多个文件, 我们可以进行加锁, 一次只能有一个线程进行访问

首先明确日志策略, 刷新到文件缓冲区, 还是命令行缓冲区, 定义基类, 使用子类继承基类的虚方法, 实现多态, 使用内部类进行日志消息的创建, 并且调用外部类的策略方法进行打印.

在这里插入图片描述
在这里插入图片描述

#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem> //C++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"namespace LogModule
{using namespace LockMoudle;//获取当前系统时间std::string CurrentTime(){time_t time_stamp = ::time(nullptr);struct tm curr;localtime_r(&time_stamp, &curr); //时间戳, 获取可读性较强的时间信息char buffer[1024];snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",curr.tm_year + 1900,curr.tm_mon + 1,curr.tm_mday,curr.tm_hour,curr.tm_min,curr.tm_sec);return buffer;}//构成: 1. 构建日志字符串 2.刷新落盘//落盘策略(screen, file)//1.日志文件的默认路径和文件名const std::string defaultlogpath = "./log/";const std::string defaultlogname = "log.txt";//2.日志等级enum class LogLevel{DEBUG = 1,INFO,WARNING,ERROR,FATAL};//枚举类型转字符串std::string Level2String(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::INFO:return "INFO";case LogLevel::WARNING:return "WARNING";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";default:return "None";}}//3.刷新策略class LogStrategy{public:virtual ~LogStrategy() = default; //虚析构函数,多态,能够正确调用对象进行析构, 编译器自动生成virtual void SyncLog(const std::string &message) = 0;//纯虚函数,子类必须手动实现};//3.1控制台策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy(){}~ConsoleLogStrategy(){}//向控制台打印日志信息messagevoid SyncLog(const std::string &message){LockGuard lockguard(_lock);std::cout << message << std::endl;}private:Mutex _lock;};//文件级策略class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname): _logpath(logpath),_logname(logname){//确定_logpath是否存在LockGuard lockguard(_lock);if(std::filesystem::exists(_logpath)){return ;}try{//不存在进行创建std::filesystem::create_directories(_logpath);}catch(std::filesystem::filesystem_error &e){std::cerr << e.what() << "\n";}}~FileLogStrategy(){}//重写基类虚方法void SyncLog(const std::string &message){LockGuard lockguard(_lock);std::string log = _logpath + _logname;std::ofstream out(log, std::ios::app);if(!out.is_open()) {return;}out << message << "\n";out.close(); }private:std::string _logpath;std::string _logname;Mutex _lock;};//日志类: 构建日志字符串, 根据策略, 进行写入class Logger{public:Logger(){//默认采用控制台策略_strategy = std::make_shared<ConsoleLogStrategy>();}void EnableConsolelog(){_strategy = std::make_shared<ConsoleLogStrategy>();}void EnableFileLog(){_strategy = std::make_shared<FileLogStrategy>();}~Logger(){}//一条完整的信息[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)//外部类实现策略, 内部类实现信息class LogMessage{public:LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger): _currtime(CurrentTime()),_level(level),_pid(::getpid()),_filename(filename),_line(line),_logger(logger){std::stringstream ssbuffer;ssbuffer << "[" << _currtime << "] "<< "[" << Level2String(_level) << "] "<< "[" << _pid << "] "<< "[" << _filename << "] "<< "[" << _line << "] - ";_loginfo = ssbuffer.str();}template <typename T>LogMessage &operator<<(const T &info){std::stringstream ss;ss << info;_loginfo += ss.str();return *this;}~LogMessage(){if (_logger._strategy){_logger._strategy->SyncLog(_loginfo);}}private:std::string _currtime; //当前日志的时间LogLevel _level; //日志等级pid_t _pid; //进程pidstd::string _filename; //源文件int _line; //行号Logger &_logger; //策略std::string _loginfo; //日志信息};  //重载operator(), 故意的拷贝LogMessage operator()(LogLevel level, const std::string &filename, int line){return LogMessage(level, filename, line, *this);}private:std::shared_ptr<LogStrategy> _strategy;};Logger logger;#define LOG(Level) logger(Level, __FILE__, __LINE__)#define ENABLE_CONSOLE_LOG() logger.EnableConsolelog()#define ENABLE_FILE_LOG() logger.EnableFileLog()
}

2. 编写Echo demo代码

2.1 UdpServer.hpp 和 UdpServer.cc

这里使用套接字进行通信, 套接字可以简单理解为一个文件流, 创建套接字之后填写网络信息, 进行和内核的绑定, 这里我使用的是云服务器, 默认不需要绑定ip, 所以这里我只需绑定端口号, 从命令行获取.

#include "UdpServer.hpp"int main(int argc, char *argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << "localport" << std::endl;Die(USAGE_ERR);}uint16_t port = std::stoi(argv[1]);ENABLE_CONSOLE_LOG();std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);svr_uptr->InitServer();svr_uptr->Start();return 0;
}
#pragma once#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "IntAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"using namespace LogModule;const static int gsockfd = -1;
//const static std::string gdefaultip = "127.0.0.1" //表示本地主机
const static uint16_t gdefaultport = 8080;class UdpServer
{
public://命令行输入ip + 端口号进行绑定, 虚拟机无需绑定ip, 只需指定端口号进行绑定即可UdpServer(uint16_t port = gdefaultport): _sockfd(gsockfd), _addr(port), _isrunning(false){}//都是套路void InitServer(){//1.创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); //指定网络通信模式. 面向数据包, 标记为设置为0if(_sockfd < 0){LOG(LogLevel::FATAL) << "socket: " << strerror(errno);Die(SOCKET_ERR);}LOG(LogLevel::INFO) << "socket success, sockfd is" << _sockfd;//2.1填充网络信息并绑定, 网络信息都在sockaddr_in里面, 这里我们封装一下//2.2bindint n = ::bind(_sockfd, _addr.Netaddr(), _addr.NetAddrlen());if(n < 0){LOG(LogLevel::FATAL) << "bind" << strerror(errno);Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success";}//启动, cs模式,进行接受消息, 并且回显回去void Start(){_isrunning = true;while(true){char inbuffer[1024];struct sockaddr_in peer; //客户端信息, 输出型参数socklen_t len = sizeof(peer);ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len);if(n > 0){InetAddr cli(peer);inbuffer[n] = 0;std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + '#' + inbuffer;LOG(LogLevel::DEBUG) << clientinfo;std::string echo_string = "echo#";echo_string += inbuffer;::sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, CONV(&peer), sizeof(peer));}}_isrunning = false;} ~UdpServer(){if(_sockfd > gsockfd)::close(_sockfd);}
private:int _sockfd;InetAddr _addr;bool _isrunning;
};

2.2 IntAddr.hpp 和 Commm.hpp

这里对IntAddr进行了封装, IntAddr这里包含了网络信息, 网络通信IntAddr_in, 我们需要对他进行强转, C语言版多态

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"class InetAddr
{
private:void PortNet2Host(){_port = ::ntohs(_net_addr.sin_port);}void IpNet2Host(){char ipbuffer[64];const char *ip = ::inet_ntop(AF_INET,&_net_addr.sin_addr,ipbuffer, sizeof(ipbuffer));(void)ip;}
public:InetAddr(){}//如果传进来的是一个sockaddr_in, 网络转主机InetAddr(const struct sockaddr_in &addr) : _net_addr(addr){PortNet2Host();IpNet2Host();}//如果传进来的是端口号, 就转化为网络, 服务器不需要自己绑定ipInetAddr(uint16_t port) : _port(port), _ip(""){_net_addr.sin_family = AF_INET;_net_addr.sin_port = htons(_port);_net_addr.sin_addr.s_addr = INADDR_ANY;}struct sockaddr* Netaddr() {return CONV(&_net_addr); }socklen_t NetAddrlen() {return sizeof(_net_addr); }std::string Ip() {return _ip; }uint16_t Port() {return _port; }~InetAddr(){}
private:struct sockaddr_in _net_addr;std::string _ip;uint16_t _port;
};

Comman.hpp

#pragma once#include<iostream>#define Die(code) do {exit(code); } while(0)
#define CONV(v) (struct sockaddr *)(v)enum
{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR
};

2.3 Client.cc

客户端进行标准输入获取信息, 并且发送到服务器, 然后接受服务器回显的内容, 并且打印

#include "UdpClient.hpp"
#include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>//csint main(int argc, char *argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << "serverip serverport" << std::endl;Die(USAGE_ERR);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);//1.创建socketint sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "socket error" << std::endl;Die(SOCKET_ERR);}//1.1填充网络信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = ::htons(serverport);server.sin_addr.s_addr = ::inet_addr(serverip.c_str());//客户端不需要进行绑定, 端口号由系统进行分配while(true){std::cout << "Place Enter# ";std::string message;std::getline(std::cin, message);int n = ::sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));(void)n;struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];n = ::recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&temp), &len);if(n > 0){buffer[n] = 0;std::cout << buffer << std::endl;}}return 0;
}

3. 运行结果

在这里插入图片描述


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

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

相关文章

【Agent】Dify Docker 安装问题 INTERNAL SERVER ERROR

总结&#xff1a;建议大家选择稳定版本的分支&#xff0c;直接拉取 master 分支&#xff0c;可能出现一下后面更新代码导致缺失一些环境内容。 启动报错 一直停留在 INSTALL 界面 我是通过 Docker 进行安装的&#xff0c;由于项目开发者不严谨导致&#xff0c;遇到一个奇怪的…

unity开发效率提升笔记

本文将记录提升Unity开发效率的若干细节&#xff0c;持续更新 一.VSCode文件标签多行显示 1.File->Preference->Settings (快捷键Ctrl 逗号) 2.搜索workbench.editor.wrapTabs 3.勾选上这个单选开关 若依然不是多行 4.搜索workbench.editor.tabSizing,选择fi…

python每日十题(6)

列表操作函数有&#xff08;假设列表名为ls&#xff09;&#xff1a; len(ls)&#xff1a;返回列表ls的元素个数&#xff08;长度&#xff09;。min(ls)&#xff1a;返回列表ls的最小元素。max(ls)&#xff1a;返回列表ls的最大元素。list(x)&#xff1a;将x转变为列表类型。使…

【Java】TCP网络编程:从可靠传输到Socket实战

活动发起人小虚竹 想对你说&#xff1a; 这是一个以写作博客为目的的创作活动&#xff0c;旨在鼓励大学生博主们挖掘自己的创作潜能&#xff0c;展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴&#xff0c;那么&#xff0c;快来参加吧&#xff01…

使用HAI来打通DeepSeek的任督二脉

一、什么是HAI HAI是一款专注于AI与科学计算领域的云服务产品&#xff0c;旨在为开发者、企业及科研人员提供高效、易用的算力支持与全栈解决方案。主要使用场景为&#xff1a; AI作画&#xff0c;AI对话/写作、AI开发/测试。 二、开通HAI 选择CPU算力 16核32GB&#xff0c;这…

mysql——第二课

学生表 CREATE TABLE student (id int(11) NOT NULL AUTO_INCREMENT,name varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,sex varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,age int(11) DEFAULT NULL,c_id int(10) DEFAULT NULL,PRIMARY KEY (id),KEY c_id (c_id),CONSTR…

单播、广播、组播和任播

文章目录 一、单播二、广播三、组播四、任播代码示例&#xff1a; 五、各种播的比较 一、单播 单播&#xff08;Unicast&#xff09;是一种网络通信方式&#xff0c;它指的是在网络中从一个源节点到一个单一目标节点对的传输模式。单播传输时&#xff0c;数据包从发送端直接发…

1-1 MATLAB深度极限学习机

本博客来源于CSDN机器鱼&#xff0c;未同意任何人转载。 更多内容&#xff0c;欢迎点击本专栏目录&#xff0c;查看更多内容。 参考[1]魏洁.深度极限学习机的研究与应用[D].太原理工大学[2023-10-14].DOI:CNKI:CDMD:2.1016.714596. 目录 0.引言 1.ELM-AE实现 2.DE…

头歌 数据采集概述答案

问题1&#xff1a;以下哪个不是Scrapy体系架构的组成部分&#xff1f; 正确答案&#xff1a;B. 支持者(Support) 解释&#xff1a;Scrapy的主要组成部分包括&#xff1a; 爬虫(Spiders)&#xff1a;定义如何爬取网站和提取数据 引擎(Engine)&#xff1a;负责控制数据流在系统中…

【uniapp】记录tabBar不显示踩坑记录

由于很久没有使用uniapp了&#xff0c;官方文档看着又杂乱&#xff0c;底部tab导航栏一直没显示&#xff0c;苦思许久&#xff0c;没有发现原因&#xff0c;最后网上搜到帖子&#xff0c;list里的第一个数据&#xff0c;pages 的第一个 path 必须与 tabBar 的第一个 pagePath 相…

JVM 知识点梳理

JDK 、JRE、JVM JDK&#xff08; Java Development Kit &#xff09; Java开发工具包 JRE 开发命令工具&#xff08;运行java.exe、编译javac.exe、javaw.exe&#xff09; JRE&#xff08; Java Runtime Environment &#xff09;Java运行环境 JVM Java核心类库&#xff08;l…

蓝桥杯 之 第27场月赛总结

文章目录 习题1.抓猪拿国一2.蓝桥字符3.蓝桥大使4.拳头对决5.未来竞赛6.备份比赛数据 习题 比赛地址 1.抓猪拿国一 十分简单的签到题 print(sum(list(range(17))))2.蓝桥字符 常见的字符匹配的问题&#xff0c;是一个二维dp的问题&#xff0c;转化为对应的动态规划求解 力扣…

Ambari、Bigtop源码编译最新支持情况汇总

以下是目前的版本情况 支持了绝大部分的组件编译及安装 版本组件名称组件版本env 版本v1.0.5Ozone1.4.11.0.5Impala4.4.11.0.5Nightingale7.7.21.0.5Categraf0.4.11.0.5VictoriaMetrics1.109.11.0.5Cloudbeaver24.3.31.0.5Celeborn0.5.31.0.5v1.0.4Doris2.1.71.0.4v1.0.3Phoen…

仅靠prompt,Agent难以自救

Alexander的观点很明确&#xff1a;未来 AI 智能体的发展方向还得是模型本身&#xff0c;而不是工作流&#xff08;Work Flow&#xff09;。还拿目前很火的 Manus 作为案例&#xff1a;他认为像 Manus 这样基于「预先编排好的提示词与工具路径」构成的工作流智能体&#xff0c;…

【Docker系列一】Docker 简介

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Sqoop 常用命令

Sqoop 是用于在 Hadoop 和关系型数据库&#xff08;如 MySQL、Oracle 等&#xff09;之间高效传输数据的工具。以下是常用的 Sqoop 命令及示例&#xff1a; CREATE TABLE employees (id INT AUTO_INCREMENT PRIMARY KEY, -- 自增主键&#xff0c;用于唯一标识每一行name VAR…

连续型随机变量及其分布

连续型随机变量 数学公式可以看作一门精确描述事物的语言&#xff0c;比语言尤其是汉语的模糊性精确多了&#xff01;离散型数据的处理可以通过枚举和相加进行处理。而连续型数据则没有办法这样处理。我们必须要通过函数和取值区间还有微积分计算。 &#xff3b;定义1&#x…

PostgreSQL_数据使用与日数据分享

目录 前置&#xff1a; 1 使用 1.1 获取前复权因子 1.2 查询股票的纵向数据 1.3 查询股票的横向数据 2 日数据分享&#xff08;截止至&#xff1a;2025-03-21&#xff09; 总结 前置&#xff1a; 本博文是一个系列。在本人“数据库专栏”-》“PostgreSQL_”开头的博文。…

Rocky9.5基于sealos快速部署k8s集群

首先需要下载 Sealos 命令行工具&#xff0c;sealos 是一个简单的 Golang 二进制文件&#xff0c;可以安装在大多数 Linux 操作系统中。 以下是一些基本的安装要求&#xff1a; 每个集群节点应该有不同的主机名。主机名不要带下划线。 所有节点的时间需要同步。 需要在 K8s …

qt实现一个简单http服务器和客户端

一、功能简介 服务器&#xff1a; 登录功能、下载文件功能 客户端&#xff1a; 登录功能、下载文件功能、上传成绩功能 二、服务器代码 //HttpServer.h #ifndef HTTPSERVER_H #define HTTPSERVER_H#include <QMainWindow> #include <QTcpSocket> #include <QTc…