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

Linux的Socket编程之UDP

目录

1、UDP编程的相关接口

1.1 创建套接字

1.2 绑定套接字

1.3 接收消息

1.4 发送消息

1.5 网络数据的转化

2、EchoServer

2.1 大致思路

2.2 UdpServer.hpp

2.3 UdpServer.cc

2.4 UdpClient.cc

2.5 示例及完整代码

3、DictServer

3.1 大致思路

3.2 InetAddr.hpp

3.3 Dict.hpp

3.4 UdpServer.hpp

3.5 UdpServer.cc

3.6 UdpClient.cc

3.7 示例及完整代码

4、ChatServer

4.1 大致思路

4.2 Route.hpp

4.3 UdpServer.hpp

4.4 UdpServer.cc

4.5 UdpClient.cc

4.6 示例及完整代码


1、UDP编程的相关接口

1.1 创建套接字

  • 创建套接字本质上是在内核中创建一个用于网络通信的抽象 “文件对象”,并通过文件描述符让用户进程操作它。
#include <sys/types.h>
#include <sys/socket.h>
// 创建套接字
int socket(int domain, int type, int protocol);#include <unistd.h>
// 关闭套接字
int close(int fd);
  • socket(AF_INETPF_INET,SOCK_DGRAM,0);
  • AF_INET表示IPv4 互联网域;AF_INET+SOCK_DGRAM表示: Udpprotocol通常是0AF_INET=PF_INET
  • socket()返回值success,返回一个socket的文件描述符(可以读也可以写,且是全双工(有独立的发送缓冲区和接收缓冲区,可以边读边写));error,返回-1
  • close()返回值success,返回0error,返回-1

1.2 绑定套接字

  • 绑定套接字IP + 端口)的本质是给通信端点分配一个唯一的网络标识,让消息能在网络中 “准确投递”。
#include <sys/types.h>
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
  • sockfd套接字文件描述符

  • 网络通信addr一般传struct sockaddr_in*强转struct sockaddr*类似于多态

  • struct sockaddr_in定义在netinet/in.h中。

  • sin_familyAF_INET,表示IPv4 互联网域。

  • sin_port无符号16位整型端口号0-1023专用端口号1024-65535可分配端口号

  • s_addr无符号32位整型IP地址,IP地址一般的表示形式是点分十进制,如:192.168.0.1,但是用字符串需要15个字节,其实4个字节(4个0-255)就行。

  • addrlenaddr大小

  • bind()返回值success,返回0error,返回-1

1.3 接收消息

#include <sys/types.h>
#include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
  • sockfd套接字文件描述符
  • *buf输出型参数,输出信息(void*,可以是任意类型),lenbuf大小
  • flags0阻塞等待
  • *src_addr输出型参数,网络通信,一般传struct sockaddr_in*强转struct sockaddr*带有发送方IP端口号
  • *addrlen,既是输入型参数,表示src_addr大小;也是输出型参数,表示实际使用的地址结构大小。
  • recvfrom()返回值success,返回接收到的字节数error,返回-1
  • ssize_t表示有符号的整数类型

1.4 发送消息

#include <sys/types.h>
#include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
  • sockfd套接字文件描述符
  • *buf输出型参数,输入信息(void*,可以是任意类型),lenbuf大小。带了const是输入,。
  • flags0阻塞等待
  • *dest_addr输入型参数,网络通信,一般传struct sockaddr_in*强转struct sockaddr*带有接收方IP端口号
  • addrlen,表示src_addr大小
  • sendto()返回值success,返回发送的字节数error,返回-1
  • ssize_t表示有符号的整数类型

1.5 网络数据的转化

  • 在处理struct sockaddr_inIP端口号时,注意对网络序列的转化
  • 注意:
  1. 网络中的数据都是大端存储(高位字节放低地址),例如:
    一个 32 位整数 0x12345678(4 字节),在大端主机中存储为 12 34 56 78(高位字节在前(低地址)),在小端主机中存储为 78 56 34 12(低位字节在前(低地址))。
  2. #include <arpa/inet.h>// 端口号 主机 -> 网络(大端),因为是host -> net s表示short 16位
    uint16_t htons(uint16_t netshort);// IP 主机 -> 网络(大端),因为是process -> net
    int inet_pton(int af, const char *src, void *dst);int af,指定地址族(Address Family)。为AF_INET:表示处理 IPv4 地址(32 位)const char *src,输入型参数,为点分十进制的IP地址的字符串。void *dst,输出型参数,4个字节的 IP 地址(大端)。传递&struct sockaddr_in.sin_addr。// 端口号 网络(大端) -> 主机,因为是net -> host s表示short 16位
    uint16_t ntohs(uint16_t netshort);// IP 网络(大端) -> 主机,因为是net -> process
    const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);int af,指定地址族(Address Family)。为AF_INET:表示处理 IPv4 地址(32 位)const void *src,输入型参数,指向二进制形式的 IP 地址(网络字节序,大端)。传递&struct sockaddr_in.sin_addr。char *dst,输出型参数,存储转换后的点分十进制的字符串形式的 IP 地址。size为dst的大小。
  3. ntop(),pton(),是线程安全的。
  4. 上面的函数,主机 -> 网络(大端),保证struct sockaddr_in里面的IP端口号大端存储;网络(大端) -> 主机,要取出来会自行判断大小端并进行转化
  5. 字节序(大端 / 小端)解决的是 “多字节数据” 在内存中如何排列的问题。
    char 类型在 C 语言中占 1 个字节(8 位),它本身没有 “高低位字节” 的概念 —— 单个字节就是最小的存储单位,不存在 “排列顺序” 问题(多字节读取的问题)。

2、EchoServer

2.1 大致思路

  • 实现一个EchoServerUdpClientUdpServer发什么UdpServer就给UdpClient回什么
  • UdpClient.ccUdpServer.cc+UdpServer.hpp(因为UdpServer会复杂一点,分开写会清晰一点)。

2.2 UdpServer.hpp

  • 服务端套接字不建议绑定特定的IP,因为可能一个服务器上有多个IP需要收到来自多个IP的消息,所以设置为INADDR_ANY(其实就是0),,可接收该机器上的任意IP
  • 服务器的端口号port是一个进程标识,需要自己设置,因为服务器的端口号需要固定
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Log.hpp"using namespace LogModule;class UdpServer
{
public:UdpServer(uint16_t port):_port(port),_running(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 success, sockfd: " << _sockfd;// 2. 给sockfd,绑定IP和端口号struct sockaddr_in server;memset(&server,0,sizeof(server)); // 初始化serverserver.sin_family = AF_INET;server.sin_port = htons(_port); // struct sockaddr_in里面必须是大端存储server.sin_addr.s_addr = INADDR_ANY; // INADDR,是0,无所谓大小端int n = bind(_sockfd,(struct sockaddr*)&server,sizeof(server));if(n < 0){LOG(LogLevel::FATAL) << "bind error!";exit(2);}LOG(LogLevel::INFO) << "bind success, sockfd: "<< _sockfd;}void Start(){_running = true;while(_running) // 一直运行{// 1. 收消息char buf[128];struct sockaddr_in addr;memset(&addr,0,sizeof(addr));socklen_t len = sizeof(addr);ssize_t s = recvfrom(_sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&addr,&len);if(s > 0){buf[s] = 0;// 2. 发消息std::string echo_string = "server echo@ ";echo_string += buf;sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&addr,sizeof(addr));}}}~UdpServer(){int n = close(_sockfd);if(n < 0)LOG(LogLevel::INFO) << "close socket "<< _sockfd <<" error!";LOG(LogLevel::INFO) << "close socket "<< _sockfd <<" success";}
private:int _sockfd;uint16_t _port;bool _running;
};

2.3 UdpServer.cc

#include "UdpServer.hpp"
#include <memory>// 利用命令行参数
// ./udpserver port
int main(int argc,char* argv[])
{if(argc != 2){std::cerr << "Usage: "<< argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> udp_server = std::make_unique<UdpServer>(port);udp_server->Init();udp_server->Start();return 0;
}

2.4 UdpClient.cc

  • 客户端套接字IP端口号,OS会自动绑定。OS知道IP,会采用随机端口号,避免端口冲突。如:开 2 个终端运行./udpclient,连同一个服务器,不会因为端口冲突报错:每个客户端的端口都是 OS 随机分配的,互不重复。
  • 客户端知道服务器IP端口号,因为客户端和服务端是同一家公司写的。
  • 通常机器上的IP有(通过ip addr查看),本地环回IP 127.0.0.1,和另一个外部IP。通过本地环回IP,要求客户端和服务端必须在同一台机器上(客户端用本地环回IP连接服务端,客户端自己的 IP 也是本地环回IP),实际上是本地通信(不通过网络,在OS里转一圈交付给对方),一般用来进行网络代码的测试
#include "Log.hpp"
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace LogModule;// 利用命令行参数
// ./udpclient server_ip server_port
int main(int argc,char* argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}uint16_t server_port = std::stoi(argv[2]);std::string server_ip = argv[1];// 1. 创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(LogLevel::FATAL) << "socket error!";exit(1);}LOG(LogLevel::INFO) << "socket success : " << sockfd;// 2. 给sockfd,绑定IP和端口号,OS自动绑定struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);inet_pton(AF_INET,server_ip.c_str(),&server.sin_addr);while (true){// 1. 发消息std::string input;std::cout << "Please Enter# ";std::getline(std::cin, input); // 以\n为结束符sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));// 2. 收消息char buf[128];struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));socklen_t len = sizeof(addr);int s = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&addr,&len);if(s > 0){buf[s] = 0;std::cout << buf << std::endl;}}return 0;
}

2.5 示例及完整代码

  • 示例:
  • 完整代码:EchoServer。

3、DictServer

3.1 大致思路

  • 实现一个DictServerUdpClientUdpServer发英文UdpServer就给UdpClient回对应的中文
  • 现在将网络数据的转化,封装成一个类(InetAddr);将翻译的函数,封装成一个类(Dict),服务端进行调用。分成多个模块,便于维护和扩展。

3.2 InetAddr.hpp

#pragma once#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>class InetAddr
{
public:InetAddr(struct sockaddr_in& addr):_addr(addr){// 网络 -> 主机char buf[32];inet_ntop(AF_INET,&_addr.sin_addr,buf,sizeof(buf)-1);_ip = buf;_port = ntohs(_addr.sin_port);}InetAddr(std::string& ip,uint16_t port):_ip(ip),_port(port){// 主机 -> 网络inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr);_addr.sin_port = htons(_port);}std::string Ip() const{return _ip;}uint16_t Port() const{return _port;}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};

3.3 Dict.hpp

#pragma once#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;static const std::string default_dict = "./dictionary.txt";
static const std::string sep = ": ";class Dict
{
public:Dict(const std::string& path = default_dict): _path(path){}bool LoadDict(){std::ifstream in(_path);if (!in.is_open()){LOG(LogLevel::ERROR) << "open " << _path << " error!";return false;}std::string line;while (std::getline(in, line)){int pos = line.find(sep);if (pos == std::string::npos){LOG(LogLevel::WARNING) << "解析: " << line << " 失败";continue;}std::string english = line.substr(0, pos);std::string chinese = line.substr(pos + sep.size());if (english.empty() || chinese.empty()){LOG(LogLevel::WARNING) << "解析: " << line << " 失败";continue;}_dict.emplace(english, chinese);}in.close();return true;}std::string Translate(const std::string& word, const InetAddr& client){auto it = _dict.find(word);if(it == _dict.end()){LOG(LogLevel::DEBUG) << "进入翻译模块,[" << client.Ip() << " : " << client.Port() << "]# " << word << " -> None";return "None";}LOG(LogLevel::DEBUG) << "进入翻译模块,[" << client.Ip() << " : " << client.Port() << "]# " << word << " -> "<< it->second;return it->second;}private:std::string _path; // 路径+文件名std::unordered_map<std::string, std::string> _dict;
};

3.4 UdpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;using task_t = std::function<std::string(const std::string& word,const InetAddr&)>;class UdpServer
{
public:UdpServer(uint16_t port,task_t func):_port(port),_running(false),_func(func){}void Init(){// 1. 创建套接字_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){LOG(LogLevel::FATAL) << "socket error!";exit(1);}LOG(LogLevel::INFO) << "socket success, sockfd: " << _sockfd;// 2. 给sockfd,绑定IP和端口号struct sockaddr_in server;memset(&server,0,sizeof(server)); // 初始化serverserver.sin_family = AF_INET;server.sin_port = htons(_port); // struct sockaddr_in里面必须是大端存储server.sin_addr.s_addr = INADDR_ANY; // INADDR,是0,无所谓大小端int n = bind(_sockfd,(struct sockaddr*)&server,sizeof(server));if(n < 0){LOG(LogLevel::FATAL) << "bind error!";exit(2);}LOG(LogLevel::INFO) << "bind success, sockfd: "<< _sockfd;}void Start(){_running = true;while(_running) // 一直运行{// 1. 收消息char buf[128];struct sockaddr_in addr;memset(&addr,0,sizeof(addr));socklen_t len = sizeof(addr);ssize_t s = recvfrom(_sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&addr,&len);if(s > 0){buf[s] = 0;// 2. 发消息InetAddr client(addr);std::string result = _func(buf,client); // 把客户端的消息传过去sendto(_sockfd,result.c_str(),result.size(),0,(struct sockaddr*)&addr,sizeof(addr));}}}~UdpServer(){int n = close(_sockfd);if(n < 0)LOG(LogLevel::INFO) << "close socket "<< _sockfd <<" error!";LOG(LogLevel::INFO) << "close socket "<< _sockfd <<" success";}
private:int _sockfd;uint16_t _port;bool _running;task_t _func;
};

3.5 UdpServer.cc

#include <memory>
#include "UdpServer.hpp"
#include "Dict.hpp"// 利用命令行参数
// ./udpserver port
int main(int argc,char* argv[])
{if(argc != 2){std::cerr << "Usage: "<< argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);Dict dict;dict.LoadDict();std::unique_ptr<UdpServer> udp_server = std::make_unique<UdpServer>(port,[&dict](const std::string& buf,const InetAddr& client)->std::string{return dict.Translate(buf,client);});udp_server->Init();udp_server->Start();return 0;
}

3.6 UdpClient.cc

#include "Log.hpp"
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace LogModule;// 利用命令行参数
// ./udpclient server_ip server_port
int main(int argc,char* argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);// 1. 创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(LogLevel::FATAL) << "socket error!";exit(1);}LOG(LogLevel::INFO) << "socket success : " << sockfd;// 2. 给sockfd,绑定IP和端口号,OS自动绑定struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);inet_pton(AF_INET,server_ip.c_str(),&server.sin_addr);while (true){// 1. 发消息std::string input;std::cout << "Please Enter# ";std::getline(std::cin, input); // 以\n为结束符sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));// 2. 收消息char buf[128];struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));socklen_t len = sizeof(addr);int s = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&addr,&len);if(s > 0){buf[s] = 0;std::cout << buf << std::endl;}}return 0;
}

3.7 示例及完整代码

  • 实例:
  • 完整代码:DictServer。

4、ChatServer

4.1 大致思路

  • 实现一个ChatServerUdpServer显示公共的聊天消息,并且一个UdpClientUdpServer发送消息UdpServer会给所有的在线的UdpClient发送该消息
  • 我们实现简单一点,客户端使用两个终端会话,一个用来发消息;一个用来显示公共的消息(将标准错误重定向到该终端(先确认会话编号,再 ./udpclient server_ip server_port 2>/dev/pts/会话编号),将收到的公共消息打印到标准错误,就打印到了该终端上了)。
  • 引入线程池(基于前文Linux的线程池-CSDN博客):

4.2 Route.hpp

#pragma once#include <unordered_set>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;
using namespace MutexModule;class Route
{
private:struct Hash{size_t operator()(const InetAddr& client) const{struct sockaddr_in addr = client.Addr();uint32_t ip = addr.sin_addr.s_addr;uint16_t port = addr.sin_port;size_t hash1 = std::hash<uint32_t>()(ip);size_t hash2 = std::hash<uint16_t>()(port);return hash1 ^ (hash2 << 1);}};void AddClient(const InetAddr& client){if(_clients.find(client) == _clients.end()){LOG(LogLevel::INFO) << "新增一个在线用户:" << client.StringAddr();_clients.emplace(client);}else{LOG(LogLevel::WARNING) << "该用户:" << client.StringAddr() << " 已在线";}}void PopClient(const InetAddr& client){if(_clients.find(client) == _clients.end()){LOG(LogLevel::WARNING) << "该用户:" << client.StringAddr() << " 不存在";}else{LOG(LogLevel::INFO) << "删除用户:" << client.StringAddr() << " 成功";_clients.erase(client);}}public:void SendMessage(int sockfd,const std::string& message,const InetAddr& client){LockGuard lockguard(_mutex); // 处理任务,可能会冲突,所以加锁if(_clients.find(client) == _clients.end()) // 首次发消息,认为是登录AddClient(client);if(message == "QUIT")PopClient(client);std::string send_message = client.StringAddr() + "# " + message; // 127.0.0.1:8080# 你好for(const auto& cli: _clients)sendto(sockfd,send_message.c_str(),send_message.size(),0,(const struct sockaddr*)&(cli.Addr()),sizeof(cli.Addr()));}private:std::unordered_set<InetAddr,Hash> _clients;Mutex _mutex;
};

4.3 UdpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;using func_t = std::function<void(int,const std::string& word,const InetAddr&)>;class UdpServer
{
public:UdpServer(uint16_t port,func_t func):_port(port),_running(false),_func(func){}void Init(){// 1. 创建套接字_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){LOG(LogLevel::FATAL) << "socket error!";exit(1);}LOG(LogLevel::INFO) << "socket success, sockfd: " << _sockfd;// 2. 给sockfd,绑定IP和端口号struct sockaddr_in server;memset(&server,0,sizeof(server)); // 初始化serverserver.sin_family = AF_INET;server.sin_port = htons(_port); // struct sockaddr_in里面必须是大端存储server.sin_addr.s_addr = INADDR_ANY; // INADDR,是0,无所谓大小端int n = bind(_sockfd,(struct sockaddr*)&server,sizeof(server));if(n < 0){LOG(LogLevel::FATAL) << "bind error!";exit(2);}LOG(LogLevel::INFO) << "bind success, sockfd: "<< _sockfd;}void Start(){_running = true;while(_running) // 一直运行{// 1. 收消息char buf[128];struct sockaddr_in addr;memset(&addr,0,sizeof(addr));socklen_t len = sizeof(addr);ssize_t s = recvfrom(_sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&addr,&len);if(s > 0){buf[s] = 0;// 2. 推送任务InetAddr client(addr);_func(_sockfd,buf,client);}}}~UdpServer(){int n = close(_sockfd);if(n < 0)LOG(LogLevel::INFO) << "close socket "<< _sockfd <<" error!";LOG(LogLevel::INFO) << "close socket "<< _sockfd <<" success";}
private:int _sockfd;uint16_t _port;bool _running;func_t _func;
};

4.4 UdpServer.cc

#include <memory>
#include <functional>
#include "UdpServer.hpp"
#include "ThreadPool.hpp"
#include "Route.hpp"using namespace ThreadPoolModule;using task_t = std::function<void()>;// 利用命令行参数
// ./udpserver port
int main(int argc,char* argv[])
{if(argc != 2){std::cerr << "Usage: "<< argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);Route route;auto thread_pool = ThreadPool<task_t>::GetInstance();std::unique_ptr<UdpServer> udp_server = std::make_unique<UdpServer>(port,[&route,&thread_pool](int sockfd,const std::string& message,const InetAddr& client){auto task = std::bind(&Route::SendMessage,&route,sockfd,message,client); // &route表示第一个参数this指针thread_pool->Enqueue(task);});udp_server->Init();udp_server->Start();return 0;
}

4.5 UdpClient.cc

#include "Log.hpp"
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <thread>
#include <unistd.h>using namespace LogModule;std::string server_ip;
uint16_t server_port;
int sockfd;bool running = true;void Send()
{struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);while (running){// 1. 发消息std::string input;std::cout << "Please Enter# ";std::getline(std::cin, input); // 以\n为结束符sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));// 发送的QUIT,要让所有在线用户知道if (input == "QUIT"){running = false;break;}}
}void Receive()
{while (running){// 2. 收消息char buf[128];struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));socklen_t len = sizeof(addr);int s = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&addr, &len);if (s > 0){buf[s] = 0;std::cerr << buf << std::endl;}else if (s == -1){if (errno != EAGAIN && errno != EWOULDBLOCK){LOG(LogLevel::ERROR) << "recvfrom error!";}// 超时后不退出循环,继续检查running(此时有机会退出)}}
}// 利用命令行参数
// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}server_ip = argv[1];server_port = std::stoi(argv[2]);// 1. 创建套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(LogLevel::FATAL) << "socket error!";exit(1);}LOG(LogLevel::INFO) << "socket success : " << sockfd;// 2. 给sockfd,绑定IP和端口号,OS自动绑定// 关键修正:设置接收超时(例如 3 秒 0 微秒),避免recvfrom无限阻塞struct timeval timeout;timeout.tv_sec = 3;  // 秒timeout.tv_usec = 0; // 微秒setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));std::thread t1(Send);std::thread t2(Receive);t1.join();t2.join();close(sockfd);return 0;
}

4.6 示例及完整代码

  • 确认客户端的会话编号:
  • 实例:
  • 完整代码:ChatServer。

http://www.dtcms.com/a/437947.html

相关文章:

  • 环境没有tomcat怎么演示自己做的网站动漫设计专业就业方向
  • 180课时吃透Go语言游戏后端开发7:Go语言中的函数
  • Python核心架构深度解析:从解释器原理到GIL机制全面剖析
  • 数据结构_哈夫曼编码(Huffman)完整指南:从原理到实现,附考研真题详解
  • 怎样做网站吸引客户网站开发专业就业前系军
  • 四川建站模板网站公司有哪些做任务网站
  • 藏语自然语言处理入门 - 5 文本归类
  • Stanford CS336 assignment1 | Transformer Language Model Architecture
  • 告别人工出题!PromptCoT 2.0 让大模型自己造训练难题,7B 模型仅用合成数据碾压人工数据集效果!
  • Prompt Programming - 用文字重构AI智能体系
  • 基于提示学习的多模态情感分析系统:从MULT到PromptModel的华丽升级
  • Node.js 图形渲染库对比:node-canvas 与 @napi-rs/canvas
  • 【LangChain】P10 LangChain 提示词模板深度解析(一):Prompt Template
  • C# TCP 服务端开发笔记(TcpListener/TcpClient)
  • 180课时吃透Go语言游戏后端开发6:Go语言的循环语句
  • wordpress+vps建站关键词语有哪些
  • 网站建设基本标准野花高清中文免费观看视频
  • hadoop-hdfs
  • VB6.0找不到该引用word,excel“Microsoft Excel 16.0 Object Library”解决方法
  • 读者-写者问题实现真正的写优先
  • 北京人力资源网站县区网站集约化建设
  • 从零开始,用WPS和DeepSeek打造数字人科普视频
  • netgear r6220 路由器,刷openwrt后,系统备份还原
  • 特价流量网站什么情况自己建设网站
  • 昂瑞微IPO前瞻:技术破局高端射频模组,国产替代第二波浪潮下的硬科技突围
  • 开源 全平台 哔哩哔哩缓存视频合并 Github地址:https://github.com/molihuan/hlbmerge_flutter
  • EPOLLONESHOT事件类型:多线程I/O中的“一次触发“机制
  • Github卡顿问题解决方案
  • 智慧园区数字孪生建设方案(WORD)
  • GitHub 热榜项目 - 日榜(2025-10-03)