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

35.Socket网络编程(UDP)(下)

UdpSocket编程V2(字典)

Linux-remote: linux远程仓库https://gitee.com/its-quite-six/linux-remote/tree/master/25_9_15/2.Dictionary

细节点1

        先创建实例dict,构造服务端类udpserver方法用lambda表达式,捕获dict实例,调用其成员函数,就实现了类之间的耦合。

细节点2

        对于字符串的分割,用substr更好,用迭代器构造的话还要自行判断。substr第二个参数不传默认截取到结尾。

代码:

Dict.hpp

#pragma once
#include <iostream>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"using namespace LogModule;
const std::string defaultdict = "./dict.txt";
const std::string separator = ": ";class Dict
{
public:Dict(const std::string &dictpath = defaultdict):_dict_path(dictpath){}void Load(){// 只读方式打开文件std::ifstream ifs(defaultdict);if (!ifs.is_open()){LOG(LogLevel::FATAL) << "open " << defaultdict << " failure!";exit(1);}// data的内容: apple: 苹果std::string data;while (std::getline(ifs, data)){size_t pos = data.find(separator);// 没找到分割符if (pos == std::string::npos){LOG(LogLevel::WARNING) << "加载失败,内容是:" << data;continue;}std::string english = data.substr(0, pos);std::string chinese = data.substr(pos + separator.size());if (english.empty() || chinese.empty()){LOG(LogLevel::WARNING) << "加载失败,缺失中文/英文,内容是:" << data;continue;}LOG(LogLevel::INFO) << "加载成功,单词为:" << data;_dict.insert(std::make_pair(english, chinese));}ifs.close();}std::string Translate(const std::string &english){auto it = _dict.find(english);if (it == _dict.end()){LOG(LogLevel::INFO) << "没有找到单词,英文为:" << english;return "NONE";}LOG(LogLevel::INFO) << "找到了对应的单词, " << english << separator << it->second;return it->second;}~Dict(){}private:std::string _dict_path; //路径+文件名std::unordered_map<std::string, std::string> _dict;
};

UdpSocket编程V3(聊天室)

分析场景1

聊天室的功能:客户端发送消息给主机,主机将消息转发给所有的在线用户

模型:服务端线程创建了sokcet,接受来自客户端的信息。

客户端不止一个(客户端信息不止一条),如何管理这些客户端信息呢?

        先描述在组织,为了便于实现port和IP等转化,以及stuct sockaddr_in的封装,封装一个InetAddr的类用来描述客户端信息;定义一个Route管理类,用vector组织管理客户端信息InetAddr;

易错1:

const变量只能调用const成员函数(调用普通成员函数属于权限放大)。

易错2:

        InetAddr的构造函数要把_peer清0后使用,并且_peer的sin_family字段要设置成AF_INET,不然Route用来转发时,客户端收不到。

分析场景2

udp是支持全双工的,多线程读写。

  • client处理时,读线程和写线程分离,让消息读写是异步的,使得不用等写完才能读。
  • 读端打印到std::cerr(fd=2),将2号重定向到另一个打开的终端上,这样就实现了读写输出到不同地方,便于查看。

易错点1:

注意:信息是指发消息的那个的 信息+数据。

消息转发,给所有人都发一条,包括自己。

ChatV1
Linux-remote: linux远程仓库https://gitee.com/its-quite-six/linux-remote/tree/master/25_9_15/3.Chat_V1

工作流程:

  1. 客户端给服务端发送数据
  2. 服务端接收数据
  3. 消息处理,回调
  4. 回调本质工作:消息转发给每一个在线的客户端
  5. 转发给在线客户端

结构:

  • 服务端主线程执行接收客户端数据,进行消息转发的工作
  • 客户端三个线程:
    主线程控制整个调用逻辑;
    发送线程负责发送数据给服务端;
    接收线程负责接受数据,写入到2号文件描述符对应文件。

ChatV2

Linux-remote: linux远程仓库https://gitee.com/its-quite-six/linux-remote/tree/master/25_9_15/4.Chat_V2

        与ChatV1唯一区别,ChatV2的服务端主线程只负责接收客户端数据。消息转发的工作交给线程池,多线程来做消息转发工作。

服务端:主线程负责通信,线程池负责处理消息转发的任务。

inet_ntoa(线程不安全)

之前用的inet_ntoa返回的char*是函数内部申请的静态存储区

多次调用inet_ntoa,结果会被覆盖,因此,inet_ntoa是线程不安全的,不建议用。

ip转化函数(inet_ntop , inet_pton 线程安全)

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

  • af:协议类型,AF_INET
  • src:网络序列的IP数字
  • dst:输出型参数buffer,存放转换后的IP地址,字符串
  • size:buffer容量
  • 返回值:成功返回dst指针;失败返回空指针,错误码被设置

int inet_pton(int af, const char *src, void *dst);

  • af:协议类型,AF_INET
  • src:本地ip地址,字符串
  • dst:网络ip地址,32位无符号整数
  • 返回值:成功返回1;af正确但ip地址无效返回0;af不正确返回-1,错误码被设置

细节点1:

task_t为std::function<void()>类型,通过bind参数绑定可以实现(lambda同理)

细节点2:

        线程池是基于生产者消费者模型写的,其中消费者处理任务是多进程并发执行的,此时,_online_users是临界资源,且有增加和删除,因此消息转发任务需要加锁。

InetAddr.hpp实现

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"using namespace LogModule;class InetAddr
{
public:InetAddr(const struct sockaddr_in peer): _peer(peer){// 网络转主机_port = ntohs(peer.sin_port);char buff[64];inet_ntop(AF_INET, &_peer.sin_addr.s_addr, buff, sizeof(buff));_ip = buff;}InetAddr(const uint16_t port, const std::string &ip): _port(port), _ip(ip){// 主机转网络memset(&_peer, 0, sizeof(_peer));_peer.sin_family = AF_INET;_peer.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &_peer.sin_addr.s_addr);}bool operator==(const InetAddr &inetaddr) const{return (_port == inetaddr._port) && (_ip == inetaddr._ip);}// 返回本地序列的端口号uint16_t Port() const { return _port; }// 返回本地序列的点分十进制ipstd::string Ip() const { return _ip; }// 返回网络地址对象指针const struct sockaddr_in *Peer() const { return &_peer; }// 返回ip+端口号拼接的字符串std::string AddrString() const { return _ip + ":" + std::to_string(_port); }~InetAddr(){}private:uint16_t _port;           // 主机序列portstd::string _ip;          // 主机ip,点分10进制struct sockaddr_in _peer; // 网络序列peer
};

Route.hpp实现

#pragma once
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "Mutex.hpp"using namespace LogModule;
using namespace MutexModule;class Route
{
private:bool Exist(const InetAddr &inetaddr){for (auto &user : _online_users){if (user == inetaddr)return true;}return false;}// 添加在线用户void AddUser(const InetAddr &inetaddr){_online_users.emplace_back(inetaddr);LOG(LogLevel::INFO) << "添加用户成功, " << inetaddr.AddrString();}// 删除在线用户void DeleteUser(const InetAddr &inetaddr){for (auto it = _online_users.begin(); it != _online_users.end(); it++){if (*it == inetaddr){_online_users.erase(it);break;}}LOG(LogLevel::INFO) << "删除用户成功, " << inetaddr.AddrString();}public:Route(){}// 消息转发给所有在线用户,以服务端的名义sockfdvoid MessageRoute(int sockfd, const std::string &data, const InetAddr &inetaddr){// 消息转发是多线程并行的,_online_users是临界资源,访问要加锁MutexGuard mutexguard(_mutex);// 1.判断该用户是否在线,如果不在线,就要添加if (!Exist(inetaddr))AddUser(inetaddr);// 格式:(用户ip:port:数据)std::string message = inetaddr.AddrString() + "# " + data;// 2.消息转发,给所有人都发一条for (auto &user : _online_users){ssize_t n = sendto(sockfd, message.c_str(), message.size(), 0,(const struct sockaddr *)(user.Peer()), sizeof(struct sockaddr_in));(void)n;}// 3.如果data内容为QUIT,那么就删除对应用户if (data == "QUIT")DeleteUser(inetaddr);}~Route(){}private:std::vector<InetAddr> _online_users;Mutex _mutex;
};

拓展

验证UDP - windows 作为client访问 Linux(验证成功)

防火墙处添加外网可访问的端口号。


文章转载自:

http://BbnSsYrI.mjbjq.cn
http://ne3wRUVf.mjbjq.cn
http://xee8LaPW.mjbjq.cn
http://XhJaYnUq.mjbjq.cn
http://x9XPTiDM.mjbjq.cn
http://t7Bc8rGK.mjbjq.cn
http://72RhBgZy.mjbjq.cn
http://JycuPp6G.mjbjq.cn
http://2pOm9aBM.mjbjq.cn
http://mC4vl3pR.mjbjq.cn
http://V98X3izs.mjbjq.cn
http://FG3nF1j9.mjbjq.cn
http://EdpgPtGD.mjbjq.cn
http://k9sTAeYd.mjbjq.cn
http://cj7OPDZW.mjbjq.cn
http://XpNwddmk.mjbjq.cn
http://OX3HoVug.mjbjq.cn
http://rAjMxKG9.mjbjq.cn
http://nOqgVKsC.mjbjq.cn
http://l4iUk9YE.mjbjq.cn
http://UdhyxE0g.mjbjq.cn
http://1ibClqPo.mjbjq.cn
http://Hwxq4rxh.mjbjq.cn
http://EqZmPgyn.mjbjq.cn
http://qD9KKIkK.mjbjq.cn
http://f8XSBN8k.mjbjq.cn
http://dKWpS5G5.mjbjq.cn
http://6ZpzBPzd.mjbjq.cn
http://LTGe0QQj.mjbjq.cn
http://LcET63nO.mjbjq.cn
http://www.dtcms.com/a/388195.html

相关文章:

  • 【前沿技术Trip Three】正则表达式
  • 多平台数据交换解耦方案选型
  • ​​[硬件电路-239]:从电阻器的高频等效模型,看高频信号的敏感性,电路的性能受到频率的影响较大
  • Java 中的 23 种设计模式详解
  • 《2025年AI产业发展十大趋势报告》六十二
  • 【字节跳动】LLM大模型算法面试题:大模型 LLM的架构介绍?
  • 【C++】类成员访问控制
  • 彩笔运维勇闯机器学习--梯度下降法
  • 正点原子zynq_FPGA学习笔记-vivado安装
  • 基于yolov8/yolo11的视觉识别算法使用和详解
  • 2025年数据科学与大数据技术和统计学有什么区别?
  • STM32H743-ARM例程2-GPIO点亮LED
  • 每天五分钟深度学习:深层神经网络的前向传播算法和反向传播算法
  • 【LeetCode】41. 缺失的第一个正数
  • Linux系统指令之 —— ip route route
  • 嵌入式硬件笔记:三种滤波电路的对比
  • webrtc弱网-InterArrivalDelta类源码分析与算法原理
  • 第6章:计算机内存实战
  • 模型压缩与量化实战:将BERT模型缩小4倍并加速推理
  • RS485 与 CAN 通讯:选哪个更合适?
  • 腾讯微保社招笔试
  • centos系统安装mysql8
  • Go语言垃圾回收器深入解析
  • 大模型的领域知识注入的四种路径
  • 寻找高速传输新选择:当传统方案不再满足现代企业需求
  • (CV方向)视频理解前沿:基于TimeSformer的时空注意力模型实战
  • hot100--简单题(3)
  • STM32开发(TIM定时器:通用定时器 - PWM)
  • 从原始数据到高效模型:基础特征工程的系统指南
  • 大数据场景下时序数据库选型指南,Apache IoTDB的领先技术和实践