目录
[1.1 创建套接字](#1.1 创建套接字)
[1.2 绑定套接字](#1.2 绑定套接字)
[1.3 接收消息](#1.3 接收消息)
[1.4 发送消息](#1.4 发送消息)
[1.5 网络数据的转化](#1.5 网络数据的转化)
[2.1 大致思路](#2.1 大致思路)
[2.2 UdpServer.hpp](#2.2 UdpServer.hpp)
[2.3 UdpServer.cc](#2.3 UdpServer.cc)
[2.4 UdpClient.cc](#2.4 UdpClient.cc)
[2.5 示例及完整代码](#2.5 示例及完整代码)
[3.1 大致思路](#3.1 大致思路)
[3.2 InetAddr.hpp](#3.2 InetAddr.hpp)
[3.3 Dict.hpp](#3.3 Dict.hpp)
[3.4 UdpServer.hpp](#3.4 UdpServer.hpp)
[3.5 UdpServer.cc](#3.5 UdpServer.cc)
[3.6 UdpClient.cc](#3.6 UdpClient.cc)
[3.7 示例及完整代码](#3.7 示例及完整代码)
[4.1 大致思路](#4.1 大致思路)
[4.2 Route.hpp](#4.2 Route.hpp)
[4.3 UdpServer.hpp](#4.3 UdpServer.hpp)
[4.4 UdpServer.cc](#4.4 UdpServer.cc)
[4.5 UdpClient.cc](#4.5 UdpClient.cc)
[4.6 示例及完整代码](#4.6 示例及完整代码)
1、UDP编程的相关接口
1.1 创建套接字
- 创建套接字 本质上是在内核中创建一个用于网络通信 的抽象 "文件对象 ",并通过文件描述符 让用户进程操作它。
cpp
#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 + 端口 )的本质是给通信端点分配一个唯一的网络标识,让消息能在网络中 "准确投递"。
cpp
#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 接收消息
cpp
#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 发送消息
cpp
#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(低位字节在前(低地址))。
cpp
#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 是一个进程标识 ,需要自己设置 ,因为服务器的端口号需要固定。
cpp
#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)); // 初始化server
server.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
cpp
#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查看),本地环回 IP127.0.0.1,和另一个外部 IP。通过本地环回 IP,要求客户端和服务端必须在同一台机器上 (客户端用本地环回IP连接服务端,客户端自己的 IP 也是本地环回IP),实际上是本地通信 (不通过网络,在OS里转一圈交付给对方),一般用来进行网络代码的测试。
cpp
#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
cpp
#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
cpp
#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
cpp
#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)); // 初始化server
server.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
cpp
#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
cpp
#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
cpp
#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
cpp
#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)); // 初始化server
server.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
cpp
#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
cpp
#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。