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_INET或PF_INET,SOCK_DGRAM,0);。
- AF_INET表示:IPv4 互联网域;AF_INET+SOCK_DGRAM表示: Udp;protocol通常是0。AF_INET=PF_INET。
- socket()的返回值,success,返回一个socket的文件描述符(可以读也可以写,且是全双工(有独立的发送缓冲区和接收缓冲区,可以边读边写));error,返回-1。
- close()的返回值,success,返回0;error,返回-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_family为AF_INET,表示IPv4 互联网域。
-
sin_port为无符号16位整型的端口号,0-1023为专用端口号,1024-65535为可分配端口号。
-
s_addr为无符号32位整型的IP地址,IP地址一般的表示形式是点分十进制,如:192.168.0.1,但是用字符串需要15个字节,其实4个字节(4个0-255)就行。
-
addrlen为addr的大小。
-
bind()的返回值,success,返回0;error,返回-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*,可以是任意类型),len,buf的大小。
- flags,0为阻塞等待。
- *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*,可以是任意类型),len,buf的大小。带了const是输入,。
- flags,0为阻塞等待。
- *dest_addr,输入型参数,网络通信,一般传struct sockaddr_in*并强转为struct sockaddr*,带有接收方的IP和端口号。
- addrlen,表示src_addr大小。
- sendto()的返回值,success,返回发送的字节数;error,返回-1。
- ssize_t表示有符号的整数类型。
1.5 网络数据的转化
- 在处理struct sockaddr_in的IP和端口号时,注意对网络序列的转化。
- 注意:
- 网络中的数据都是大端存储(高位字节放低地址),例如:
一个 32 位整数 0x12345678(4 字节),在大端主机中存储为 12 34 56 78(高位字节在前(低地址)),在小端主机中存储为 78 56 34 12(低位字节在前(低地址))。 -
#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的大小。
- ntop(),pton(),是线程安全的。
- 上面的函数,主机 -> 网络(大端),保证struct sockaddr_in里面的IP和端口号是大端存储;网络(大端) -> 主机,要取出来会自行判断大小端并进行转化。
- 字节序(大端 / 小端)解决的是 “多字节数据” 在内存中如何排列的问题。
char 类型在 C 语言中占 1 个字节(8 位),它本身没有 “高低位字节” 的概念 —— 单个字节就是最小的存储单位,不存在 “排列顺序” 问题(多字节读取的问题)。
2、EchoServer
2.1 大致思路
- 实现一个EchoServer,UdpClient给UdpServer发什么,UdpServer就给UdpClient回什么。
- UdpClient.cc,UdpServer.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 大致思路
- 实现一个DictServer,UdpClient给UdpServer发英文,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 大致思路
- 实现一个ChatServer,UdpServer上显示公共的聊天消息,并且一个UdpClient给UdpServer发送消息,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。