目录
- [1. UdpServer:英汉字典](#1. UdpServer:英汉字典)
-
- [1.1 服务器功能与实现逻辑](#1.1 服务器功能与实现逻辑)
- [1.2 代码实现](#1.2 代码实现)
-
- [1.2.1 class Dict:导入字典信息与提供翻译功能](#1.2.1 class Dict:导入字典信息与提供翻译功能)
- [1.2.2 网络模块与功能模块](#1.2.2 网络模块与功能模块)
- [2. UdpServer:简单的聊天室](#2. UdpServer:简单的聊天室)
-
- [2.1 服务器功能与实现逻辑](#2.1 服务器功能与实现逻辑)
- [2.2 代码实现](#2.2 代码实现)
-
- [2.2.1 class MessageRoute:将收到的信息对聊天室内的成员进行广播](#2.2.1 class MessageRoute:将收到的信息对聊天室内的成员进行广播)
- [2.2.2 服务器端的调整与实现](#2.2.2 服务器端的调整与实现)
- [2.2.3 客户端代码的调整与实现](#2.2.3 客户端代码的调整与实现)
1. UdpServer:英汉字典
1.1 服务器功能与实现逻辑
- 服务器功能: 向服务器发送字典中存在的英文单词,服务器可以返回对应的中文翻译
- 实现逻辑:
通过文件将英汉翻译在服务器本地持久化存储,定义一个字典类,创建对象时,会将英汉翻译导入类中的unordered_map类型的成员变量中。提供成员函数可以通过传入的英文单词查询到对应的中文翻译。
1.2 代码实现
1.2.1 class Dict:导入字典信息与提供翻译功能
| 成员变量 | 作用 |
|---|---|
| unordered_map<string, string> _dict | 存储导入的翻译信息 |
| string _dict_conf_filepath | 翻译文件持久化存储的本地路径 |
| 成员函数 | 作用 |
|---|---|
| Load | 打开配置文件,导入翻译 |
| 构造 | 初始化成员变量,调用Load |
| Traslate | 查找_dict返回中文翻译 |
Load函数实现逻辑:
-
- 使用getline函数按行读取文件中的数据
-
- 通过单词翻译之间的分隔符分别读取英文部分与翻译部分
-
- 读取时,检测读取到的数据是否合法
-
- 将读取的字典信息插入到_dict中
代码实现:
cpp
//翻译格式:
//happy: 开心的
const std::string path = "./Dict.txt";
const std::string sep = ":";//分隔符
namespace dict_space
{
class Dict
{
void Load()
{
std::ifstream in(_dict_conf_file.c_str());
std::string buf;
while(std::getline(in, buf))
{
//检测不为空
if(buf.empty()) continue;
//获取英文部分
std::string word;
int pos = buf.find(sep);
word = buf.substr(0, pos);
if(word.empty()) continue;
//获取翻译部分
std::string translation;
translation = buf.substr(pos + 2, buf.size());
if(translation.empty()) continue;
LOG(INFO, "insert %s: %s", word.c_str(), translation.c_str());
//插入字典中
_dict.insert(std::make_pair(word, translation));
}
LOG(INFO, "insert success");
in.close();
}
public:
Dict()
:_dict_conf_file(path)//创建对象初始化时,此全局变量在对象之前
{
Load();
}
const std::string Translate(const std::string word, bool& ok)
{
ok = true;
if(_dict.find(word) == _dict.end())
{
ok = false;
return "未找到";
}
return _dict[word];
}
~Dict()
{}
private:
unordered_map<std::string, std::string> _dict;
std::string _dict_conf_file;
};
}
1.2.2 网络模块与功能模块
在前一章的学习中,我们已经搭建起了基础的Udp网络通信。所有服务器本质上都是输入是输出的问题,所以,我们无需重新编写网络模块,将翻译的功能部分添加进去。而是,可以通过函数对象与回调函数的方式,不让网络通信模块与业务模块强耦合,采用模块与模块调用嵌合的方式,提高代码的健壮性。
class UdpServer调整部分:
cpp
//以包装器加回调函数的方式,调用翻译模块
using func_t = std::function<std::string(std::string, bool&)>;
class UdpServer
{
public:
void Start()
{
//先收后发
_isrunning = true;
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char request[1024];
ssize_t n = recvfrom(_sockfd, request, sizeof(request) - 1, 0, (struct sockaddr*)&peer, &len);
if(n > 0)
{
request[n] = 0;
InetAddr addr(peer);
LOG(INFO, "get message from [%s:%d]: %s", addr.Ip().c_str(), addr.Port(), request);
bool ok;
std::string response = _func(request, ok);
sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&peer, len);
}
}
_isrunning = false;
}
private:
int _sockfd;
uint16_t _port;
bool _isrunning;
func_t _func;
};
UdpServer启动与调用方式:
cpp
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERROR);
}
EnableScreen();
Dict dict;
std::unique_ptr<UdpServer> svr = make_unique<UdpServer>(std::stoi(argv[1]), \
std::bind(&Dict::Translate, &dict, placeholders::_1, placeholders::_2));
svr->InitServer();
svr->Start();
return 0;
}
2. UdpServer:简单的聊天室
2.1 服务器功能与实现逻辑
- 服务器功能: 服务器会维护一个聊天室,每当有主机向服务器发送信息,服务器就会将信息发送给所有处在聊天室内的成员
- 实现逻辑:
实现一个专用的模块类,用户维护处于聊天室内的成员主机信息,并且提供向所有成员广播信息的接口。此类不直接进行信息的方法,而是将任务发布给线程池,线程池将任务分配给线程池中的线程来完成。
2.2 代码实现
2.2.1 class MessageRoute:将收到的信息对聊天室内的成员进行广播
| 成员变量 | 作用 |
|---|---|
| vector _online_user | 用于存储维护聊天室内的成员信息 |
| pthread_mutex_t _mutex | 用于保护代码中的临界区 |
| 成员函数 | 作用 |
|---|---|
| IsExits | 用于检测发送通信申请的成员是否存在聊天室内 |
| 构造、析构 | 维护互斥锁 |
| AddUser | 向聊天室内添加新成员 |
| DelUser | 删除聊天室内的指定成员 |
| RouteHelper | 向聊天室内的所有成员广播信息 |
| Route | 将广播信息的任务分配给线程池中的线程 |
代码实现:
cpp
using func_t = std::function<void(void)>;
class MessageRoute
{
private:
bool IsExits(InetAddr addr)
{
//一台主机只有一个进程进行通信
for(auto& e : _online_user)
{
if(e.Ip() == addr.Ip())
{
return true;
}
}
return false;
}
public:
MessageRoute()
{
pthread_mutex_init(&_mutex, nullptr);
}
void AddUser(InetAddr user)
{
LockGuard lockguard(&_mutex);
if(IsExits(user))
return;
_online_user.push_back(user);
}
void DelUser(InetAddr user)
{
LockGuard lockguard(&_mutex);
if(!IsExits(user))
return;
for(auto it = _online_user.begin(); it != _online_user.end(); it++)
{
if(it->Ip() == user.Ip())
{
_online_user.erase(it);
break;//迭代器失效
}
}
}
//将消息转发给所有成员
void RouteHelper(int sockfd, std::string message, InetAddr who)
{
LockGuard lockguard(&_mutex);
//IP无法显示?
std::string sendmessage = "\n[" + who.Ip() + " : " + std::to_string(who.Port()) + "] " + message + "\n";
for(auto e : _online_user)
{
sendto(sockfd, sendmessage.c_str(), sendmessage.size(), 0, (struct sockaddr*)e.Addr(), sizeof(struct sockaddr_in));
}
}
//将发送工作分配给线程池中的线程
void Route(int sockfd, std::string message, InetAddr who)
{
if(!IsExits(who))
AddUser(who);
func_t task = std::bind(&MessageRoute::RouteHelper, this, sockfd, message, who);
ThreadPool<func_t>* threads = ThreadPool<func_t>::GetInstance();
threads->Enqueue(task);
if(message == "Quit" || message == "Q")
{
DelUser(who);
}
}
~MessageRoute()
{
pthread_mutex_destroy(&_mutex);
}
private:
std::vector<InetAddr> _online_user;
pthread_mutex_t _mutex;
};
2.2.2 服务器端的调整与实现
class UdpServer的调整:
cpp
//定义与调用新的功能模块
enum
{
SOCKET_ERROR = 1,
BIND_ERROR,
USAGE_ERROR
};
static int defualt_fd = -1;
using handler_message_t = std::function<void(int, std::string, InetAddr)>;
class UdpServer
{
public:
UdpServer(uint16_t port, handler_message_t handler_message)
:_sockfd(defualt_fd), _port(port), _handler_message(handler_message)
{}
void InitServer()
{
//1. 创建socket fd
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd < 0)
{
LOG(FATAL, "socket error: %s, %d", strerror(errno), errno);
exit(SOCKET_ERROR);
}
LOG(INFO, "socket create success");
//2. 填充struct sockaddr_in
struct sockaddr_in local;
bzero(&local, sizeof(sockaddr_in));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
//3. 绑定
int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
if(n < 0)
{
LOG(FATAL, "bind error: %s %d", strerror(errno), errno);
exit(BIND_ERROR);
}
LOG(INFO, "bind success");
}
void Start()
{
//先收后发
_isrunning = true;
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char sendmessage[1024];
ssize_t n = recvfrom(_sockfd, sendmessage, sizeof(sendmessage) - 1, 0, (struct sockaddr*)&peer, &len);
if(n > 0)
{
sendmessage[n] = 0;
InetAddr addr(peer);
LOG(INFO, "get message from [%s:%d]: %s", addr.Ip().c_str(), addr.Port(), sendmessage);
_handler_message(_sockfd, sendmessage, addr);
}
}
_isrunning = false;
}
~UdpServer()
{}
private:
int _sockfd;
uint16_t _port;
bool _isrunning;
handler_message_t _handler_message;
};
UdpServer的启动与调用方式:
cpp
void Usage(std::string proc)
{
std::cout << "Usage\n\t" << proc << " local_port\n" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERROR);
}
EnableScreen();
MessageRoute route;
std::unique_ptr<UdpServer> svr = make_unique<UdpServer>(std::stoi(argv[1]), \
std::bind(&MessageRoute::Route, &route, placeholders::_1, placeholders::_2, placeholders::_3));
svr->InitServer();
svr->Start();
return 0;
}
2.2.3 客户端代码的调整与实现
客户端中,我们这里将消息的接收与发送分别使用两个bash命令行窗口,便于可以更清晰的观察收到的信息。并且将消息的接收与发送操作分离,使得客户端即使不进行消息发送也可以接收其他主机发送的消息。
运行客户端时,使用指令:./UdpClient serverip serverport 2> /dev/pts/...,将标准错误重定向到指定的bash命令行文件,从而达成收到信息的分离显示。
UdpClient的代码实现:
cpp
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " server_ip server_port\n" << std::endl;
}
int InitClient(std::string& serverip, uint16_t serverport, struct sockaddr_in* serveraddr)
{
//1. 创建socket fd
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
LOG(FATAL, "socket create error %s %d", strerror(errno), errno);
}
LOG(INFO, "socket create success");
memset(serveraddr, 0, sizeof(sockaddr_in));
serveraddr->sin_port = htons(serverport);
serveraddr->sin_family = AF_INET;
serveraddr->sin_addr.s_addr = serverip;
return sockfd;
}
void sendmessage(int sockfd, struct sockaddr_in server, std::string name)
{
while(true)
{
std::string message;
std::cout << "send message# ";
getline(std::cin, message);
fflush(stdout);
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
}
}
void recvmessage(int sockfd, string name)
{
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char buf[1024] = { 0 };
recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer, &len);
InetAddr addr(peer);
std::cerr << name << "| " << buf << endl;
}
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
struct sockaddr_in server;
uint16_t port = std::stoi(argv[2]);
std::string serverip = inet_addr(argv[1]);
int sockfd = InitClient(serverip, port, &server);
function<void(std::string)> recv = std::bind(recvmessage, sockfd, placeholders::_1);
function<void(std::string)> send = std::bind(sendmessage, sockfd, server, placeholders::_1);
Thread r(recv, "recver");
Thread s(send, "sender");
r.start();
s.start();
r.join();
s.join();
return 0;
}