1.翻译服务器
1.dict类
之前servent调用上层的函数,这个dict就是将这个函数替换成一个类中的成员函数
cpp
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"
#include "InetAddr.hpp"
const std::string defaultdict = "./dictionary.txt";
const std::string sep = ": ";
using namespace LogModule;
class Dict
{
public:
Dict(const std::string &path = defaultdict) : _dict_path(path)
{
}
bool LoadDict()
{
//打开一个有关字典数据的文件
std::ifstream in(_dict_path);
if (!in.is_open())
{
LOG(LogLevel::DEBUG) << "打开字典: " << _dict_path << " 错误";
return false;
}
std::string line;
//得到文件中的每一行,也就是将中文和英文都录入一个hash表中
while (std::getline(in, line))
{
// 每一行的数据例子"apple: 苹果"
auto pos = line.find(sep);
if (pos == std::string::npos)
{
LOG(LogLevel::WARNING) << "解析: " << line << " 失败";
continue;
}
//切割成english和chinese两部分之后放入hash表
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.insert(std::make_pair(english, chinese));
LOG(LogLevel::DEBUG) << "加载: " << line;
}
in.close();
return true;
}
//servent调用上层的函数
std::string Translate(const std::string &word, InetAddr &client)
{
auto iter = _dict.find(word);
if (iter == _dict.end())
{
LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->None";
return "None";
}
LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->" << iter->second;
return iter->second;
}
~Dict()
{
}
private:
std::string _dict_path; // 路径+文件名
std::unordered_map<std::string, std::string> _dict;
};
2.第一次的inetaddr,就是包装ip和port来简化代码
cpp
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
// 网络地址和主机地址之间进行转换的类
class InetAddr
{
public:
InetAddr(struct sockaddr_in &addr) : _addr(addr)
{
_port = ntohs(_addr.sin_port); // 从网络中拿到的!网络序列
_ip = inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP
}
uint16_t Port() {return _port;}
std::string Ip() {return _ip;}
~InetAddr()
{}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
3.client,和之前的一样,说白了这里的翻译模型与之前的唯一区别就是设计了一个翻译类
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
// ./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. 创建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 2;
}
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
while(true)
{
std::string input;
std::cout << "Please Enter# ";
std::getline(std::cin, input);
int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&server, sizeof(server));
(void)n;
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
if(m > 0)
{
buffer[m] = 0;
std::cout << buffer << std::endl;
}
}
return 0;
}
4.servent.cc
cpp
#include <iostream>
#include <memory>
#include "Dict.hpp" // 翻译的功能
#include "UdpServer.hpp" // 网络通信的功能
// ./udpserver port
int main(int argc, char *argv[])
{
if(argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
// std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
// 1. 字典对象提供翻译功能
Dict dict;
dict.LoadDict();
// 2. 网络服务器对象,提供通信功能
//就是将dict中的translate函数作为上层功能来调用
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string &word, InetAddr&cli)->std::string{
return dict.Translate(word, cli);
});
usvr->Init();
usvr->Start();
return 0;
}
5.servent.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
using func_t = std::function<std::string(const std::string&, InetAddr&)>;
const int defaultfd = -1;
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd),
_port(port),
_isrunning(false),
_func(func)
{
}
void Init()
{
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error!";
exit(1);
}
LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(2);
}
LOG(LogLevel::INFO) << "bind success, sockfd : " << _sockfd;
}
void Start()
{
_isrunning = true;
while (_isrunning)
{
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
//这里就体现了InetAddr的意义,原本要传ip和port的,封装一下后就可以只传结构体了
InetAddr client(peer);
buffer[s] = 0;
std::string result = _func(buffer, client);
sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
}
}
}
~UdpServer()
{
}
private:
int _sockfd;
uint16_t _port;
bool _isrunning;
func_t _func;
};
2.基于线程池的多人聊天室
1.inetaddr的进阶版
cpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
// 网络地址和主机地址之间进行转换的类
class InetAddr
{
public:
// 网络转主机
InetAddr(struct sockaddr_in &addr) : _addr(addr)
{
_port = ntohs(_addr.sin_port);
// _ip = inet_ntoa(_addr.sin_addr);
//inet_ntoa有一个问题,其返回的是一个固定的char*的指针
//在多次调用inet_ntoa时,前面的inet_ntoa的返回值就被后面调用的inet_ntoa给覆盖了
char ipbuffer[64];
//使用inet_ntop,使用的是自己生成的缓冲区,不同inet_ntop使用不同的缓冲区就可以解决覆盖问题了
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
_ip = ipbuffer;
}
// 主机转网络
InetAddr(const std::string &ip, uint16_t port):_ip(ip), _port(port)
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
_addr.sin_port = htons(_port);
//inet_pton比inet_addr更安全
//local.sin_addr.s_addr = inet_addr(_ip.c_str());
}
uint16_t Port() {return _port;}
std::string Ip() {return _ip;}
const struct sockaddr_in &NetAddr() { return _addr; }
//这个重载是用于比较一个socket是否已经存在
bool operator==(const InetAddr &addr)
{
return addr._ip == _ip && addr._port == _port;
}
//用于打印数据
std::string StringAddr()
{
return _ip + ":" + std::to_string(_port);
}
~InetAddr()
{}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
2.route,用于广播一条信息给所有用户
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
using namespace LogModule;
using namespace MutexModule;
class Route
{
private:
bool IsExist(InetAddr &peer)
{
for (auto &user : _online_user)
{
if (user == peer)
{
return true;
}
}
return false;
}
void AddUser(InetAddr &peer)
{
LOG(LogLevel::INFO) << "新增一个在线用户: " << peer.StringAddr();
_online_user.push_back(peer);
}
void DeleteUser(InetAddr &peer)
{
for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
{
if (*iter == peer)
{
LOG(LogLevel::INFO) << "删除一个在线用户:" << peer.StringAddr() << "成功";
_online_user.erase(iter);
break;
}
}
}
public:
Route()
{
}
//所有线程共用一个fd,因此可以直接传sockfd
void MessageRoute(int sockfd, const std::string &message, InetAddr &peer)
{
//加锁的,因为在多线程时,Adduser,遍历user,和删除user使用的是同一个_online_user
//也就是说_online_user是公共资源
//正常来说应该将三个功能分别包装然后分别设计锁,此处简单处理直接加锁
//为什么线程池的互斥没有用?
//线程池本身互斥,但是里面的所有线程工作的都是同一个任务,此时这个任务里面的资源就很可能出现
//线程不安全的问题
LockGuard lockguard(_mutex);
if (!IsExist(peer))
{
AddUser(peer);
}
std::string send_message = peer.StringAddr() + "# " + message; // 127.0.0.1:8080# 你好
// TODO
for (auto &user : _online_user)
{
sendto(sockfd, send_message.c_str(), send_message.size(), 0, (const struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
}
// 这个用户一定已经在线了
if (message == "QUIT")
{
LOG(LogLevel::INFO) << "删除一个在线用户: " << peer.StringAddr();
DeleteUser(peer);
}
}
~Route()
{
}
private:
// 首次给我发消息,等同于登录,使用数据结构管理所有用户
std::vector<InetAddr> _online_user;
Mutex _mutex;
};
3.client
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Thread.hpp"
//设计为公共资源的原因是减少传参个数
int sockfd = 0;
std::string server_ip;
uint16_t server_port = 0;
pthread_t id;
using namespace ThreadModlue;
void Recv()
{
while (true)
{
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (m > 0)
{
buffer[m] = 0;
std::cerr << buffer << std::endl; // 2
}
}
}
void Send()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
const std::string online = "inline";
sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));
while (true)
{
std::string input;
std::cout << "Please Enter# "; // 1
std::getline(std::cin, input); // 0
int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
(void)n;
if (input == "QUIT")
{
pthread_cancel(id);
break;
}
}
}
// client 我们也要做多线程改造
// ./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. 创建socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 2;
}
// 2. 创建线程
//如果不创建线程,client就无法一边打字了,一边接收别人的信息了(不创建只能打完字才能接收到打字时别人发的信息)
//给线程发送任务,让发信息和接收信息同时运行从而保证一边打字,一边接收信息
Thread recver(Recv);
Thread sender(Send);
//启动线程
recver.Start();
sender.Start();
id = recver.Id();
//让线程开始工作
recver.Join();
sender.Join();
return 0;
}
4.servent.hpp,由于分层了,因此servent只用于接发信息,至于发信息的具体过程则由上层实现从而实现解耦
cpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
using func_t = std::function<void(int sockfd, const std::string&, InetAddr&)>;
const int defaultfd = -1;
// 你是为了进行网络通信的!
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd),
_port(port),
_isrunning(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;
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(2);
}
LOG(LogLevel::INFO) << "bind success, sockfd : " << _sockfd;
}
void Start()
{
_isrunning = true;
while (_isrunning)
{
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
InetAddr client(peer);
buffer[s] = 0;
_func(_sockfd, buffer, client);
}
}
}
~UdpServer()
{
}
private:
int _sockfd;
uint16_t _port;
bool _isrunning;
func_t _func; // 服务器的回调函数,用来进行对数据进行处理
};
cpp
#include <iostream>
#include <memory>
#include "Route.hpp"
#include "UdpServer.hpp" // 网络通信的功能
#include "ThreadPool.hpp"
using namespace ThreadPoolModule;
// 需求
// 1. 翻译系统,字符串当成英文单词,把英文单词翻译成为汉语
// 2. 基于文件来做
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;
}
// std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
// 1. 路由服务,这是处理数据的核心
Route r;
// 2. 线程池,之前数据的接收都由servent单进程处理,这里使用线程池
//将数据发送的交给子线程处理
auto tp = ThreadPool<task_t>::GetInstance();
// 3. 网络服务器对象,提供通信功能
//过程:servent接收数据,调用上层功能,功能生成一个子线程,在子线程中工作,主进程继续接收数据
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r, &tp](int sockfd, const std::string &message, InetAddr&peer){
//std::bind能绑定一个函数的参,返回的函数就会自动调用指定的参数了,因此std::bind多用于让系统自动调用一个含参函数
task_t t = std::bind(&Route::MessageRoute,&r, sockfd, message, peer);
tp->Enqueue(t);
});
//启动服务器
usvr->Init();
usvr->Start();
return 0;
}