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

socket编程UDP

目录

UDP网络编程

V1版本-Echo Server

V2版本-Dict  Server

V3版本-Chat Server

地址转换函数

实现


UDP网络编程

V1版本-Echo Server

我们利用上篇基础接口,写一个简单的网络通信代码。

框架:客户端向服务端发送消息,服务端处理信息,同时给客户端回显消息。

服务端UdpServer.hpp:

注意:将服务端IP绑定设置成INADDR_ANY(是一个特殊常量(值为 0.0.0.0))的理由是,它可以设置将监听主机上的所有网络接口,也就是说无论主机有多少个网卡或动态 IP,将来客户端只需要拿着主机某一个IP和确定端口就可以进行网络通信。

UdpServer.cc:

UdpClient.cc:

代码:

UdpClient.cc:

// ./UdpClient server_ip server_port
int main(int agrc, char *agrv[])
{if (agrc != 3){std::cerr << "Usage: " << agrv[0] << "ip port" << std::endl;return 1;}std::string server_ip = agrv[1];uint16_t server_port = std::stoi(agrv[2]);// 1.创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket error!" << std::endl;return 2;}// 不需要绑定// 填写服务器信息struct sockaddr_in server;memset(&server, 0, sizeof(server)); // 清0server.sin_addr.s_addr = inet_addr(server_ip.c_str());server.sin_family = AF_INET;server.sin_port = htons(server_port);while (true){std::string input;std::cout<<"Please enter:";std::getline(std::cin, input);// 发送sendto(sockfd, input.c_str(), input.size(),0, (struct sockaddr *)&server, sizeof(server));// 收char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1,0, (struct sockaddr *)&peer, &len);//打印if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}}return 0;
}

UdpServer.hpp:

using func_t = std::function<std::string(const std::string &)>;
const int sockdefault = -1;
// 网络通信类
class UdpServer
{
public:UdpServer(uint16_t port, func_t func): _sockfd(sockdefault), _port(port), _func(func), _isrunning(false){}void Init(){// 1.创建套接字 本质是网络文件 返回文件描述符_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 创建失败{LOG(LogLevel::FATAL) << "socket error!";exit(1);}LOG(LogLevel::INFO) << "socket create success! socketfd:" << _sockfd;// 2填充sockaddr_in 结构体struct sockaddr_in local;bzero(&local, sizeof(local)); // 清0// 表示使用 IPv4 协议 进行网络通信local.sin_family = AF_INET;// 将来需要将自己的端口号和ip发送至网络// 需要将端口号和ip转成网络格式,再发送至网络local.sin_port = htons(_port); // 填充端口号// ip需要先转成4字节,再变网络格式// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 填充iplocal.sin_addr.s_addr =  INADDR_ANY;// 3.绑定socket信息,ip和端口号// IP和端口必须是众所周知且不能轻易改变的int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind error!";exit(2);}LOG(LogLevel::INFO) << "bind success,socket:" << _sockfd;}void Start(){_isrunning = true;while (_isrunning){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// 1.收消息,会收到哪个ip,哪个进程发送的消息ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1,0, (struct sockaddr *)&peer, &len);if (s > 0) // 返回实际接收的字节数{// 将网络端口序列转化出来int peer_port = ntohs(peer.sin_port);// 将4字节的网络格式ip转化出来std::string peer_ip = inet_ntoa(peer.sin_addr);buffer[s] = 0;std::string result=_func(buffer);// 打印LOG(LogLevel::DEBUG) << "[" << peer_ip << ":"<< peer_port << "]# " << buffer;// 2.给对应的客户端发消息sendto(_sockfd, result.c_str(), result.size(),0, (struct sockaddr *)&peer, len);}}}
private:int _sockfd;     // 套接字fduint16_t _port;  // 端口号bool _isrunning;func_t _func;
};

UdpServer.cc:

// 仅仅是用来进行测试的
std::string defaulthandler(const std::string &message)
{std::string hello = "hello, ";hello += message;return hello;
}
//./UdpServer port
int main(int agrc, char *agrv[])
{if (agrc != 2){std::cerr << "Usage: " << agrv[0] << "port" << std::endl;return 1;}// std::string ip = agrv[1];uint16_t port = std::stoi(agrv[1]);Enable_Console_Log_Strategy();std::unique_ptr<UdpServer> us = std::make_unique<UdpServer>(port,defaulthandler);us->Init();us->Start();return 0;
}

V2版本-Dict  Server

实现一个翻译功能,当客户端发送一个单词给服务端,服务端翻译,将结果返回给服务端。

核心功能:

服务端:需要实现翻译,我们可以这样,一个字典文档存放着单词和汉字的映射数据,一个字典类(里面有着哈希表),首先需要将字典文档中的映射信息加载到字典类的哈希表中,然后将字典类中的翻译接口传给服务端,将来服务端收到客户端的消息,就执行这个回调函数(翻译接口)。

字典类(Dict):

翻译功能:

代码:

Dict.hpp:

const std::string defalutdict = "./dictionary.txt";
const std::string sep = ": ";
class Dict
{
public:Dict(const std::string &path = defalutdict): _dict_path(path){}bool LoadDict(){// 以读方式打开std::ifstream in(_dict_path);if (!in.is_open()){LOG(LogLevel::DEBUG) << "打开字典:" << _dict_path << "错误";return false;}std::string line;while (std::getline(in, line)){// apple: 苹果auto pos = line.find(sep);if (pos == std::string::npos){LOG(LogLevel::ERROR) << "解析:" << line << "失败";continue;}std::string english = line.substr(0, pos);std::string chinese = line.substr(pos + sep.size());if (english.empty() || chinese.empty()){LOG(LogLevel::ERROR) << "没有有效内容:" << line;continue;}// 正常数据_dict.insert(std::make_pair(english, chinese));LOG(LogLevel::INFO) << "加载:" << line;}in.close();return true;}std::string Translate(const std::string &word, InetAddr &cli){auto iter = _dict.find(word);if (iter == _dict.end()){LOG(LogLevel::INFO) << "进入翻译功能:" << cli.Ip() << ":"<< cli.Port() << ":#" << word << "->" << "None";return "None";}LOG(LogLevel::INFO) << "进入翻译功能:" << cli.Ip() << ":"<< cli.Port() << ":#" << word << "->" << iter->second;return iter->second;}private:std::string _dict_path;                             // 路径+文件名std::unordered_map<std::string, std::string> _dict; // 映射
};

其他代码比较冗余,不过多列举。

V3版本-Chat Server

我们要实现一个简单的聊天室。

客户端多线程,一个发,一个收,当客户端没发消息时,它能收到其他客户端的消息,发消息时,服务端收到消息,做路由服务,将路由服务插入线程池队列,由多个线程消费路由服务。

我们先讲讲地址转换函数。

地址转换函数

字符串转in_addr的函数:

in_addr转字符串的函数:

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接⼝是 void *addrptr。

关于inet_ntoa:

inet_ntoa这个函数返回了⼀个char*,很显然是这个函数⾃⼰在内部为我们申请了⼀块内存来保存ip的 结果.那么是否需要调⽤者⼿动释放呢?

inet_ntoa函数,是把这个返回结果放到了静态存储区.这个时候不需要我们⼿动进⾏释放。

inet_ntoa把结果放到⾃⼰内部的⼀个静态存储区,这样第⼆次调⽤时的结果会覆盖掉上⼀次的结果

如果有多个线程调⽤inet_ntoa,是否会出现异常情况呢?

在APUE中,明确提出inet_ntoa不是线程安全的函数,但是在centos7上测试,并没有出现问题,可能内部的实现加了互斥锁。在多线程环境下,推荐使⽤inet_ntop,这个函数由调⽤者提供⼀个缓冲区保存结果,可以规避线程 安全问题;

实现

我们可以将Ip和端口主机和网络之间的转化,提前写一个类,可以避免代码冗余情况。

InetAddr类:将ip和端口进行网络和主机之间的转化。

 

UdpServer.cc,UdpServer.hpp(服务端):

UdpClient.cc(客户端):

Route.hpp(路由服务):

代码:

Route.hpp:

class Route
{
private:bool IsExist(InetAddr &peer){for (auto &user : _online_user){if (user == peer)return true;}return false;}void AddUser(InetAddr &peer){LOG(LogLevel::INFO) << "新添加一个新用户" << peer.StringAddr();_online_user.push_back(peer);}void DeleteUser(InetAddr &peer){for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++){if (*iter == peer){LOG(LogLevel::INFO) << "删除一个在线用户:" << peer.StringAddr() << "成功";_online_user.erase(iter);break;}}}public:void MessageRoute(int sockfd, std::string &message, InetAddr &peer){LockGuard lock(_mutex);if (!IsExist(peer)){AddUser(peer);}std::string send_message = peer.StringAddr() + "#" + message;// 给每个用户都发送一下信息for (auto &user : _online_user){sendto(sockfd, send_message.c_str(), send_message.size(), 0,(struct sockaddr *)&user.NetAddr(), sizeof(user.NetAddr()));}//退出if (message == "quit"){LOG(LogLevel::INFO) << "删除一个在线用户" << peer.StringAddr();DeleteUser(peer);}}private:Mutex _mutex;std::vector<InetAddr> _online_user; // 在线用户
};

InetAddr.hpp:

class InetAddr
{
public:// 两个构造函数InetAddr(struct sockaddr_in &addr): _addr(addr){// 网络转主机序列_port = ntohs(_addr.sin_port);// _ip = inet_ntoa(_addr.sin_addr);char ipbuffer[64];inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));_ip = ipbuffer;}InetAddr(const std::string &ip, uint16_t port): _ip(ip), _port(port){// 主机转网络序列memset(&_addr, 0, sizeof(_addr)); // 清0_addr.sin_family = AF_INET;_addr.sin_port = htons(_port);inet_pton(AF_INET, ip.c_str(), &_addr.sin_addr);}uint16_t Port() { return _port; }std::string Ip() { return _ip; }bool operator==(InetAddr &add){return add._ip == _ip && add._port == _port;}struct sockaddr_in &NetAddr(){return _addr;}std::string StringAddr(){return _ip + ":" + std::to_string(_port);}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};

UdpServer.hpp:

using func_t = std::function<void(int sockfd, const std::string &, InetAddr &)>;
const int sockdefault = -1;
// 网络通信类
class UdpServer
{
public:UdpServer(uint16_t port, func_t func): _sockfd(sockdefault), _port(port), _func(func), _isrunning(false){}void Init(){// 1.创建套接字 本质是网络文件 返回文件描述符_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 创建失败{LOG(LogLevel::FATAL) << "socket error!";exit(1);}LOG(LogLevel::INFO) << "socket create success! socketfd:" << _sockfd;//可以这样uint16_t port = _port;InetAddr local("0", port);int n = bind(_sockfd, (struct sockaddr *)(&(local.NetAddr())),sizeof(local.NetAddr()));if (n < 0){LOG(LogLevel::FATAL) << "bind error!";exit(2);}LOG(LogLevel::INFO) << "bind success,socket:" << _sockfd;}void Start(){_isrunning = true;while (_isrunning){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// 1.收消息,会收到哪个ip,哪个进程发送的消息ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1,0, (struct sockaddr *)&peer, &len);if (s > 0) // 返回实际接收的字节数{// 网络转主机序列InetAddr client(peer);buffer[s] = 0;_func(_sockfd, buffer, client); // 回调函数}}}
private:int _sockfd;    // 套接字fduint16_t _port; // 端口号bool _isrunning;func_t _func;
};

UdpServer.cc:

using task_t = std::function<void()>;//./UdpServer port
int main(int agrc, char *agrv[])
{if (agrc != 2){std::cerr << "Usage: " << agrv[0] << "port" << std::endl;return 1;}uint16_t port = std::stoi(agrv[1]);// 路由服务Route r;// 多线程处理auto tp = ThreadPool<task_t>::GetInstance();Enable_Console_Log_Strategy();std::unique_ptr<UdpServer> us = std::make_unique<UdpServer>(port,[&tp,&r](int sockfd,const std::string& message,InetAddr& peer){task_t t =std::bind(&Route::MessageRoute,&r,sockfd,message,peer);tp->Enqueue(t); });us->Init();us->Start();return 0;
}
void Recv()
{while (true){// 收char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1,0, (struct sockaddr *)&peer, &len);// 打印if (m > 0){buffer[m] = 0;std::cerr << buffer << std::endl;//2}}
}
void Send()
{struct sockaddr_in server;memset(&server, 0, sizeof(server)); // 清0server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());//发const std::string online = "inline";sendto(sockfd, online.c_str(), online.size(), 0,(struct sockaddr *)&server, sizeof(server));while (true){std::string input;std::cout << "Please enter:";//1std::getline(std::cin, input);//0// 发送sendto(sockfd, input.c_str(), input.size(),0, (struct sockaddr *)&server, sizeof(server));if (input == "quit"){pthread_cancel(id);break;}}
}
// ./UdpClient server_ip server_port
int main(int agrc, char *agrv[])
{if (agrc != 3){std::cerr << "Usage: " << agrv[0] << "ip port" << std::endl;return 1;}server_ip = agrv[1];server_port = std::stoi(agrv[2]);// 1.创建套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket error!" << std::endl;return 2;}// 发送和接受消息多线程Thread recver(Recv);Thread sender(Send);recver.Start();sender.Start();// id = recver.Id();recver.Join();sender.Join();return 0;
}

好了,我们下期见。

http://www.lryc.cn/news/622530.html

相关文章:

  • 深度解析和鲸社区热门项目:电商双 11 美妆数据分析的细节与价值
  • AI Agents 2025年十大战略科技趋势
  • 【Java学习】锁、线程死锁、线程安全2
  • 【原理】C# 字段、属性对比及其底层实现
  • 使用npm/pnpm自身安装指定版本的pnpm
  • pnpm(Performant npm)的安装
  • Docker之安装部署——(1)配置国内docker镜像源
  • 【多模态大模型】--BLIP3
  • 疯狂星期四文案网第40天运营日记
  • PyTorch的安装-CPU版本或者GPU安装有什么区别吗
  • Comfyui进入python虚拟环境
  • 《P1194 买礼物》
  • 综合案例:Python 函数知识整合 — 学生成绩管理系统
  • 【秋招笔试】2025.08.15饿了么秋招机考-第三题
  • 无脑整合springboot2.7+nacos2.2.3+dubbo3.2.9实现远程调用及配置中心
  • hex文件结构速查
  • PyQt6实例_50个流通领域重要生产资料市场价格查看工具
  • OpenCV---getStructuringElement 结构元素获取
  • 铨林接纸机学习记录1
  • 嵌入式开发学习———Linux环境下网络编程学习(二)
  • STC8单片机驱动I2C屏幕:实现时间、日期与温湿度显示
  • AutoSar AP平台功能组并行运行原理
  • 码上爬第七题【协程+对抗格式化检测+数组移位】
  • 【Canvas与玻璃光】铝圈蓝底玻璃光按钮
  • 吉他和弦学习:从音程基石到流畅弹奏
  • 优先级反转问题
  • 在使用 scp 传输大文件时,为避免因连接超时导致传输中断
  • 领域防腐层(ACL)在遗留系统改造中的落地
  • python中的reduce函数
  • MSYS2+CMake配置C/C++开发环境