乐山乐人网站建设公司建站优化信息推广

张小明 2026/1/3 6:50:21
乐山乐人网站建设公司,建站优化信息推广,网站域名分几种,网站运营面试问题前言:最近在复习 Linux 网络编程,重点梳理了 UDP 协议的实现细节。虽然 UDP 是无连接、不可靠的协议,但其简单高效的特性在很多场景下(如实时音视频、DNS)依然是首选。从最简单的 Echo Server 出发,逐步重构为支持业务解耦的字典服务器,最后实现一个支持多线程的全双工聊…前言:最近在复习 Linux 网络编程,重点梳理了 UDP 协议的实现细节。虽然 UDP 是无连接、不可靠的协议,但其简单高效的特性在很多场景下(如实时音视频、DNS)依然是首选。从最简单的 Echo Server 出发,逐步重构为支持业务解耦的字典服务器,最后实现一个支持多线程的全双工聊天室,并探讨其中涉及的地址转换陷阱。一、UDP 编程的核心套路与 TCP 不同,UDP 不需要维护连接状态(没有三次握手),因此其系统调用更加精简。核心 API 概览socket(): 创建套接字,参数需指定SOCK_DGRAM。bind(): 服务器必须显式绑定 IP 和 Port;客户端通常不需要显式绑定,由 OS 在首次发送数据时自动分配随机端口 。recvfrom(): 核心读取函数。因为没有连接,必须通过参数(src_addr)获取发送方的地址信息(IP+Port),否则无法回信 。sendto(): 核心发送函数。必须指定目标地址(dest_addr)。为什么服务器推荐绑定INADDR_ANY?在云服务器或多网卡机器上,直接绑定公网 IP 可能会失败(因为公网 IP 通常是在云厂商的 NAT 网关上)。 最佳实践是绑定INADDR_ANY(0.0.0.0)。含义:服务器愿意接收本机所有网卡(任意 IP)转发到该端口的数据 。优势:通用性强,无需关心具体 IP 变动。二、不同版本的UDP服务器V1 版本:Echo Server(最简模型)这是 UDP 的Hello World。逻辑非常简单:收到什么,就发回什么。关键点在于使用recvfrom获取客户端的sockaddr,利用这个地址直接sendto返回数据。不足在于网络逻辑和业务逻辑耦合在一起,无法扩展。服务端由以下几个文件组成:UdpServer.hpp:#pragma once #include iostream #include string #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include strings.h #include "Logger.hpp" static const int gdefaultsockfd = -1; class UdpServer { public: UdpServer(uint16_t port) : _sockfd(gdefaultsockfd), _port(port), _isrunning(false) { } void Init() { // 1.创建套接字 _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 把网络在文件系统中展开 if (_sockfd 0) { LOG(LogLevel::FATAL) "create socket error"; exit(1); } LOG(LogLevel::INFO) "create socket success: " _sockfd; // 2.bind套接字 // 2.1 填充IP和Port struct sockaddr_in local; bzero(local, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); // 统一大端 // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // inet_addr 1. 字符串转整数ip 2. 整数ip是网络序列的 local.sin_addr.s_addr = htonl(INADDR_ANY); // 任意IPbind 不明确具体IP,只要发给对应的主机,对应的port,全都能收到 // 2.2 和socketfd进行bind int n = bind(_sockfd, (const sockaddr *)local, sizeof(local)); if (n 0) { LOG(LogLevel::FATAL) "bind socket error"; exit(2); } LOG(LogLevel::INFO) "bind socket success : " _sockfd; } void Start() { _isrunning = true; while (_isrunning) { // 读取数据 char buffer[1024]; buffer[0] = 0; // O(1) 清空数据 struct sockaddr_in peer; socklen_t len = sizeof(peer); ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)peer, len); // flag == 0 , 阻塞模式 if (n 0) { uint16_t clientport = ntohs(peer.sin_port); std::string clientip = inet_ntoa(peer.sin_addr); buffer[n] = 0; LOG(LogLevel::DEBUG) "[" clientip " : " clientport "]#" buffer; std::string echo_string = "server echo# "; echo_string += buffer; sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)peer, len); } } _isrunning = false; } void Stop() { _isrunning = false; } ~UdpServer() { } private: int _sockfd; uint16_t _port; // std::string _ip; // 不建议绑定固定ip bool _isrunning; };UdpServer.cc:#include "UdpServer.hpp" #include memory void Usage(std::string proc) { std::cerr "Usage :" proc "loaclport" std::endl; } // ./udp_server serverport int main(int argc, char *argv[]) { if (argc != 2) { Usage(argv[0]); exit(0); } uint16_t localport = std::stoi(argv[1]); EnableConsoleLogStrategy(); std::unique_ptrUdpServer ptr = std::make_uniqueUdpServer(localport); ptr-Init(); ptr-Start(); return 0; }客户端文件为:UdpClient.cc#include iostream #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include strings.h #include string void Usage(std::string proc) { std::cerr "Usage :" proc "serverip serverport" std::endl; } // ./udp_client serverip serverport int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); exit(0); } int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd 0) { std::cout "create socket error" std::endl; return 0; } std::string serverip = argv[1]; uint16_t serverport = std::stoi(argv[2]); struct sockaddr_in server; bzero(server, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(serverport); server.sin_addr.s_addr = inet_addr(serverip.c_str()); socklen_t len = sizeof(server); while (true) { std::cout "Please Enter@ "; std::string line; std::getline(std::cin, line); sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr *)server, len); char buffer[1024]; struct sockaddr_in temp; socklen_t len = sizeof(temp); ssize_t m = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)temp, len); if(m 0) { buffer[m] = 0; std::cout buffer std::endl; } } return 0; }V2 版本:Dict Server(业务解耦)为了让服务器能处理不同业务(如英译汉、查数据),我们需要将网络收发与业务处理分离。设计模式:策略模式 / 回调函数。实现:定义一个callback_t类型(利用 C++11std::function):using callback_t = std::functionstd::string(const std::string word, const std::string whoip, uint16_t whoport);服务器类UdpServer持有一个func_t成员。在Recv之后,调用回调函数处理业务,获取结果后再Send。服务端文件为:DictServer.hpp:#pragma once #include iostream #include string #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include strings.h #include functional #include "Logger.hpp" using callback_t = std::functionstd::string(const std::string word, const std::string whoip, uint16_t whoport); static const int gdefaultsockfd = -1; class DictServer { public: DictServer(uint16_t port, callback_t func) : _sockfd(gdefaultsockfd), _port(port), _cb(func), _isrunning(false) { } void Init() { // 1.创建套接字 _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 把网络在文件系统中展开 if (_sockfd 0) { LOG(LogLevel::FATAL) "create socket error"; exit(1); } LOG(LogLevel::INFO) "create socket success: " _sockfd; // 2.bind套接字 // 2.1 填充IP和Port struct sockaddr_in local; bzero(local, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); // 统一大端 // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // inet_addr 1. 字符串转整数ip 2. 整数ip是网络序列的 local.sin_addr.s_addr = htonl(INADDR_ANY); // 任意IPbind 不明确具体IP,只要发给对应的主机,对应的port,全都能收到 // 2.2 和socketfd进行bind int n = bind(_sockfd, (const sockaddr *)local, sizeof(local)); if (n 0) { LOG(LogLevel::FATAL) "bind socket error"; exit(2); } LOG(LogLevel::INFO) "bind socket success : " _sockfd; } void Start() { _isrunning = true; while (_isrunning) { // 读取数据 char buffer[1024]; buffer[0] = 0; // O(1) 清空数据 struct sockaddr_in peer; socklen_t len = sizeof(peer); ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)peer, len); // flag == 0 , 阻塞模式 if (n 0) { uint16_t clientport = ntohs(peer.sin_port); std::string clientip = inet_ntoa(peer.sin_addr); buffer[n] = 0; // buffer[n] = 0; // LOG(LogLevel::DEBUG) "[" clientip " : " clientport "]#" buffer; // std::string echo_string = "server echo# "; // echo_string += buffer; std::string word = buffer; LOG(LogLevel::DEBUG) "用户查找: " word; std::string result = _cb(word, clientip, clientport); sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)peer, len); } } _isrunning = false; } void Stop() { _isrunning = false; } ~DictServer() { } private: int _sockfd; uint16_t _port; // std::string _ip; // 不建议绑定固定ip callback_t _cb; bool _isrunning; };Dictionary.hpp:#pragma once #include iostream #include unordered_map #include string #include filesystem #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include "Logger.hpp" static const std::string sep = ": "; class Dictionary { private: void LoadConf() { std::ifstream in(_path); if (!in.is_open()) { LOG(LogLevel::ERROR) "open file error" _path; return; } std::string line; while (std::getline(in, line)) { LOG(LogLevel::DEBUG) "load dict message: " line; auto pos = line.find(sep); if (pos == std::string::npos) { LOG(LogLevel::WARNING) "format error: " line; continue; } std::string word = line.substr(0, pos); std::string value = line.substr(pos + sep.size()); if (word.empty() || value.empty()) { LOG(LogLevel::WARNING) "format error, word or value is empty"; } _dict.insert({word, value}); } in.close(); } public: Dictionary(const std::string path) : _path(path) { LOG(LogLevel::INFO) "construct Dictionary obj"; LoadConf(); } std::string Translate(const std::string word, const std::string whoip, uint16_t whoport) { (void)whoip,(void)whoport; auto iter = _dict.find(word); if(iter == _dict.end()) { return "unknown"; } return iter-first + " - " + iter-second; } ~Dictionary() { } private: std::string _path; std::unordered_mapstd::string, std::string _dict; };DictServer.cc#include "DictServer.hpp" #include "Dictionary.hpp" #include memory void Usage(std::string proc) { std::cerr "Usage :" proc " loaclport" std::endl; } // ./udp_server serverport int main(int argc, char *argv[]) { if (argc != 2) { Usage(argv[0]); exit(0); } EnableConsoleLogStrategy(); Dictionary dict("./dict.txt"); uint16_t localport = std::stoi(argv[1]); std::unique_ptrDictServer ptr = std::make_uniqueDictServer(localport, [dict](const std::string word, const std::string whoip, uint16_t whoport) - std::string { return dict.Translate(word, whoip, whoport); }); ptr-Init(); ptr-Start(); return 0; }客户端文件为:DictClient.cc:#include iostream #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include strings.h #include string void Usage(std::string proc) { std::cerr "Usage :" proc "serverip serverport" std::endl; } // ./udp_client serverip serverport int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); exit(0); } int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd 0) { std::cout "create socket error" std::endl; return 0; } std::string serverip = argv[1]; uint16_t serverport = std::stoi(argv[2]); struct sockaddr_in server; bzero(server, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(serverport); server.sin_addr.s_addr = inet_addr(serverip.c_str()); socklen_t len = sizeof(server); // client 不需要显示bind自己的ip和端口 // client会在os的帮助下,随机bind端口号,防止端口冲突 while (true) { std::cout "Please Enter@ "; std::string line; std::getline(std::cin, line); sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr *)server, len); char buffer[1024]; buffer[0] = 0; struct sockaddr_in temp; socklen_t len = sizeof(temp); ssize_t m = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)temp, len); if(m 0) { buffer[m] = 0; std::cout buffer std::endl; } } return 0; }效果:UdpServer变成了通用的网络底层,具体的查字典逻辑(加载dict.txt到unordered_map)封装在外部模块中 。V3 版本:多人聊天室(状态管理与多线程)这是最复杂的场景,实现了类似群聊的功能。服务端设计:消息路由,服务端不再是简单的请求-响应模型,而是变成了消息分发中心。用户管理:维护一个在线用户列表std::vectorInetAddr。核心逻辑:接收消息。检查发送者是否在用户列表中。如果不在,视为新用户上线,加入列表 。广播:遍历用户列表,将消息sendto给除了发送者之外的所有人(或所有人)。处理指令(如 QUIT):从列表中移除用户 。服务端由以下几个文件组成:ChatServer.hpp:#pragma once #include iostream #include string #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include strings.h #include functional #include "Logger.hpp" #include "InetAddr.hpp" using callback_t = std::functionvoid(int socket, const std::string message, InetAddr inetaddr); // 不要设为引用 static const int gdefaultsockfd = -1; class ChatServer { public: ChatServer(uint16_t port, callback_t func) : _sockfd(gdefaultsockfd), _port(port), _cb(func), _isrunning(false) { } void Init() { // 1.创建套接字 _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 把网络在文件系统中展开 if (_sockfd 0) { LOG(LogLevel::FATAL) "create socket error"; exit(1); } LOG(LogLevel::INFO) "create socket success: " _sockfd; InetAddr local(_port); int n = bind(_sockfd, local.Addr(), local.Length()); if (n 0) { LOG(LogLevel::FATAL) "bind socket error"; exit(2); } LOG(LogLevel::INFO) "bind socket success : " _sockfd; } void Start() { _isrunning = true; while (_isrunning) { // 读取数据 char buffer[1024]; buffer[0] = 0; // O(1) 清空数据 struct sockaddr_in peer; socklen_t len = sizeof(peer); ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)peer, len); // flag == 0 , 阻塞模式 if (n 0) { buffer[n] = 0; InetAddr clinetaddr(peer); LOG(LogLevel::DEBUG) "get a client info# " clinetaddr.Ip() " - " clinetaddr.Port() ": " buffer; std::string message = buffer; // 回调 _cb(_sockfd, message, clinetaddr); } } _isrunning = false; } void Stop() { _isrunning = false; } ~ChatServer() { } private: int _sockfd; uint16_t _port; // std::string _ip; // 不建议绑定固定ip callback_t _cb; bool _isrunning; };Route.hpp:#pragma once #include iostream #include string #include vector #include "InetAddr.hpp" class Route { private: bool IsExists(const InetAddr addr) { for (auto e : _online_user) { if (e == addr) return true; } return false; } void AddUser(const InetAddr addr) { if (!IsExists(addr)) _online_user.push_back(addr); } void SendMessageToAll(int sockfd, std::string message, InetAddr addr) { for (auto e : _online_user) { LOG(LogLevel::DEBUG) "route [" message "] to: " e.ToString(); std::string info = addr.ToString(); info += "# "; info += message; sendto(sockfd, message.c_str(), message.size(), 0, e.Addr(), e.Length()); } } void DeleteUser(const std::string message, const InetAddr addr) { if (message == "QUIT") { auto iter = _online_user.begin(); for (; iter _online_user.end(); iter++) { if (*iter == addr) { _online_user.erase(iter); } break; } } } public: Route() { } void RouteMessageToAll(int sockfd, std::string message, InetAddr addr) { AddUser(addr); SendMessageToAll(sockfd, message,addr); DeleteUser(message, addr); } ~Route() { } private: // 临界资源 // 1. 锁 std::vectorInetAddr _online_user; // 2. 锁 + 拷贝 // std::vectorInetAddr _send_lint; };ChatServer.cc:#include "ChatServer.hpp" #include "ThreadPool.hpp" #include "Route.hpp" #include memory void Usage(std::string proc) { std::cerr "Usage :" proc " loaclport" std::endl; } using task_t = std::functionvoid(); // ./udp_server serverport int main(int argc, char *argv[]) { if (argc != 2) { Usage(argv[0]); exit(0); } EnableConsoleLogStrategy(); uint16_t localport = std::stoi(argv[1]); // 1.消息转发功能 std::unique_ptrRoute r = std::make_uniqueRoute(); // 2.线程池对象 auto tp = ThreadPooltask_t::GetInstance(); // 3.服务器对象 std::unique_ptrChatServer ptr = std::make_uniqueChatServer(localport, [r, tp](int sockfd, std::string message, InetAddr addr) { task_t task = std::bind(Route::RouteMessageToAll, r.get(), sockfd, message, addr); tp-Enqueue(task); }); ptr-Init(); ptr-Start(); return 0; }V3代码中为了使IP、端口号看起来更具体,定义了InetAddr类:#pragma once // 描述client socket信息的类 #include iostream #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include strings.h #include functional #include string #include "Logger.hpp" class InetAddr { private: void Net2Host() { _port = ntohs(_addr.sin_port); _ip = inet_ntoa(_addr.sin_addr); } void Host2Net() { bzero(_addr, sizeof(_addr)); _addr.sin_family = AF_INET; _addr.sin_port = htons(_port); _addr.sin_addr.s_addr = inet_addr(_ip.c_str()); } public: InetAddr(const struct sockaddr_in client) : _addr(client) { Net2Host(); } InetAddr(uint16_t port,const std::string ip = "0.0.0.0") : _port(port), _ip(ip) { Host2Net(); } uint16_t Port() { return _port; } std::string Ip() { return _ip; } struct sockaddr *Addr() { return (sockaddr *)_addr; } socklen_t Length() { socklen_t len = sizeof(_addr); return len; } std::string ToString() { return _ip + std::to_string(_port); } bool operator==(const InetAddr addr) { return _ip == addr._ip _port == addr._port; } ~InetAddr() { } private: struct sockaddr_in _addr; // 网络风格地址 std::string _ip; // 主机风格地址 uint16_t _port; };以及单例线程池代码:Thread.hpp:#ifndef __THREAD_HPP__ #define __THREAD_HPP__ #include iostream #include vector #include pthread.h #include unistd.h #include functional #include sys/syscall.h #include "Logger.hpp" #define get_lwp_id() syscall(SYS_gettid); using func_t = std::functionvoid(const std::string name); const std::string threadnamedefault = "None - name"; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; class Thread { private: /* data */ pthread_t _tid; pid_t lwpid; std::string _name; func_t _func; bool _isrunning; public: Thread(func_t func, const std::string name = threadnamedefault) : _name(name), _func(func), _isrunning(false) { LOG(LogLevel::INFO) _name " create thread success"; } ~Thread() { // LOG(LogLevel::INFO) _name "thread destory"; } static void *start_routine(void *args) // 取消this指针 { Thread *self = static_castThread *(args); self-_isrunning = true; self-lwpid = get_lwp_id(); self-_func(self-_name); pthread_exit((void *)0); } void Start() { int n = pthread_create(_tid, nullptr, start_routine, this); if (n == 0) { LOG(LogLevel::INFO) _name " run thread success"; } } void Join() { if (!_isrunning) return; int n = pthread_join(_tid, nullptr); if (n == 0) { LOG(LogLevel::INFO) _name " pthread join success"; } } void Stop() { if(!_isrunning) return; _isrunning = false; int n = pthread_cancel(_tid); } }; #endifThreadPool.hpp:#pragma once #include iostream #include memory #include queue #include vector #include "Thread.hpp" #include "Mutex.hpp" #include "Cond.hpp" const static int threadnum_default = 3; // for debug // 单例线程池 template typename T class ThreadPool { private: bool QueueIsEmpty() { return _q.empty(); } void Routine(const std::string name) { // LOG(LogLevel::INFO) name " hello world"; while (true) // 死循环 { // 把任务从线程获取到线程私有 临界区 - 私有栈 T t; { LockGuard lockguard(_lock); while (QueueIsEmpty() _isrunning) { _thread_wait_num++; _cond.Wait(_lock); _thread_wait_num--; } // T t = _q.front(); // 处理任务不需要再临界区,只需把取任务保护好就行 if (!_isrunning QueueIsEmpty()) // 退出情况设计 { LOG(LogLevel::INFO) "线程池退出 任务队列为空, " name "退出"; break; } t = _q.front(); _q.pop(); // t(); } t(); // 处理任务 } } ThreadPool(int threadnum = threadnum_default) : _threadnum(threadnum), _thread_wait_num(0), _isrunning(false) // 对象先创建(存在),再初始化 { for (int i = 0; i threadnum; i++) { // 方法1 // auto f = std::bind(hello, this); // 方法2 std::string name = "thread-" + std::to_string(i + 1); _threads.emplace_back(Thread([this](const std::string name) { this-Routine(name); }, name)); // Thread t([this]() { // }, name); // _threads.push_back(std::move(t)); } LOG(LogLevel::INFO) " threadpool cerate success"; } // 复制拷⻉禁⽤ ThreadPoolT operator=(const ThreadPoolT ) = delete; ThreadPool(const ThreadPoolT ) = delete; public: void Start() { if (_isrunning) return; _isrunning = true; for (auto t : _threads) { t.Start(); } LOG(LogLevel::INFO) " threadpool start success"; } // 退出逻辑设计 // 1.如果被唤醒 队列没有任务 = 让线程退出 // 2.如果被唤醒 队列有任务 = 线程不能立即退出 而应该让线程把任务处理完,再退出 // 3.线程本身没有休眠 我们应该让他把能处理的任务全处理完成,再退出 void Stop() { // 这种做法太简单粗暴 // if (!_isrunning) // return; // _isrunning = false; // for (auto t : _threads) // { // t.Stop(); // } // LOG(LogLevel::INFO) " threadpool stop success"; if (!_isrunning) return; _isrunning = false; if (_thread_wait_num 0) _cond.NotifyAll(); } void Wait() { for (auto t : _threads) { t.Join(); } LOG(LogLevel::INFO) " threadpool wait success"; } void Enqueue(const T in) { if (!_isrunning) return; { LockGuard lockguard(_lock); _q.push(in); if (_thread_wait_num 0) _cond.NotifyOne(); } } // 获取单例 // 让用户以类的方式访问构造单例,不需要自己构造 static ThreadPoolT *GetInstance() { if(!_instance) { LockGuard LockGuard(_singleton_lock); if (!_instance) { LOG(LogLevel::DEBUG) "线程池首次被使用,创建并初始化"; _instance = new ThreadPoolT(); _instance-Start(); } // else // { // LOG(LogLevel::DEBUG) "线程池单例已经存在,直接获取"; // } } return _instance; } ~ThreadPool() { LOG(LogLevel::INFO) " threadpool destory success"; } private: // 任务队列 std::queueT _q; // 整体使用的临界资源 // 多个线程 std::vectorThread _threads; int _threadnum; int _thread_wait_num; // 锁 Mutex _lock; // 条件变量 Cond _cond; bool _isrunning; // 防止线程池重复启动 // 单例中静态指针 // 懒汉模式设计 static ThreadPoolT *_instance; static Mutex _singleton_lock; }; template class T ThreadPoolT *ThreadPoolT::_instance = nullptr; template class T Mutex ThreadPoolT::_singleton_lock;线程池中的Cond、Mutex都是对库函数的封装,不难理解。而UDP 协议本身是全双工的(可以同时读写)。在聊天室场景下,客户端不能阻塞在cin等待输入,否则无法及时接收别人发来的消息。于是采用多线程来解决该问题。主线程:负责从标准输入读取数据,调用sendto。接收线程:负责死循环调用recvfrom,将收到的消息打印到屏幕 。于是客户端文件为:ChatClient.cc:#include iostream #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include strings.h #include string #include thread void Usage(std::string proc) { std::cerr "Usage :" proc "serverip serverport" std::endl; } int sockfd = -1; uint16_t serverport; std::string serverip; void InitClient(const std::string serverip, uint16_t serverport) { sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd 0) { std::cout "create socket error" std::endl; exit(2); } } void recver() { while (true) { char buffer[1024]; buffer[0] = 0; struct sockaddr_in temp; socklen_t len = sizeof(temp); ssize_t m = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)temp, len); if (m 0) { buffer[m] = 0; std::cout buffer std::endl; } } } void sender() { struct sockaddr_in server; bzero(server, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(serverport); server.sin_addr.s_addr = inet_addr(serverip.c_str()); socklen_t len = sizeof(server); while (true) { std::cout "Please Enter@ "; std::string line; std::getline(std::cin, line); sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr *)server, len); } } // ./udp_client serverip serverport int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); exit(0); } serverip = argv[1]; serverport = std::stoi(argv[2]); InitClient(serverip, serverport); std::thread tsend(sender); std::thread trecv(recver); trecv.join(); tsend.join(); return 0; }上述的聊天逻辑使用图片来进行如下总结:三、地址转换与线程安全在网络编程中,我们经常需要将sockaddr_in中的 IP 地址(32位整数)转换为点分十进制字符串(如 192.168.1.1)。而使用in_addr转字符串函数inet_ntoa需要注意一些情况。接口原型:char *inet_ntoa(struct in_addr in);该函数会将转换结果存放在内部的一个静态缓冲区 (static buffer)中,并返回该缓冲区的指针 。会出现如下问题:覆盖问题:如果在一行代码中连续调用两次inet_ntoa,第二次的结果会覆盖第一次。// 错误示例 printf("ptr1: %s, ptr2: %s\n", inet_ntoa(addr1), inet_ntoa(addr2)); // 输出结果可能是一样的,都是 addr2 的值线程不安全:在多线程环境下,如果两个线程同时调用该函数,缓冲区内容会被竞争覆盖,导致乱码或逻辑错误 。解决方案推荐使用inet_ntop: 这是一个现代的、线程安全的函数。它要求调用者提供缓冲区,从而避免了内部静态缓冲区。函数原型:const char *inet_ntop(int af, const void *restrict src, char dst[restrict .size], socklen_t size);使用示例:char ip_buf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, addr.sin_addr, ip_buf, sizeof(ip_buf));
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

网站生成app教育类型网站

外贸网站建设公司选哪家?[百年网络科技]是您的不二之选在当今全球化的商业环境中,外贸网站已成为企业拓展国际市场的重要工具。一个专业、高效的外贸网站不仅能够展示企业的产品和服务,还能够吸引潜在客户,提高企业的知名度和竞争…

张小明 2025/12/26 8:25:33 网站建设

天津网站建设专家我的家乡网页制作代码

我终于查到了分,顺利拿下了软考高级系统架构设计师的证书。我老婆在小区门口寿司摆摊的,做了有7年时间了,现在每天城管都来,非常的痛苦,不知道还能摆摊多久。每天收摊回家累得腰酸背痛,还要在这个流量时代被…

张小明 2025/12/26 8:21:29 网站建设

如何做淘宝客的网站ai做网站 如何切图

Web开发技术综合指南 1. FastTemplate类详解 FastTemplate是一个在PHP开发中非常实用的模板类,它能帮助开发者更高效地处理模板文件。以下是对其详细的介绍: - 类的定义与属性 : class FastTemplate {var $FILELIST = array(); // 存储文件句柄数组var $DYNAMIC = ar…

张小明 2025/12/26 8:15:24 网站建设

网站网址怎么做二维码代引流推广公司

Linux系统用户、组管理及文本流处理实用指南 1. 用户与组管理基础 在Linux系统中,用户和组管理是系统管理员的重要工作。所有用户账户信息存储在 /etc/passwd 文件中,每行定义一个用户,包含以下字段: - 用户名 - 密码 - 用户ID(UID) - 组ID(GID) - 注释 - 主…

张小明 2025/12/26 8:13:23 网站建设

软件开发文档通用要求seo营销推广公司

模板方法模式(Template Method) 模板方法模式将算法流程与算法具体实现相分离的结构. 首先, 定义一个抽象类, 抽象类中有一个不可重写的final方法, 这个方法中封装核心的算法流程, 但这个流程中只有方法调用, 没有具体实现. 具体的方法实现由子类完成, 一些方法是抽象方法, 子类…

张小明 2025/12/26 8:05:18 网站建设