目录
一.英汉词典
整体框架
Dict.txt
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
Dicp.hpp
namespace dict_ns
{
const std::string defaultpath = "./Dict.txt";
//用来分隔英汉的分隔符
const std::string sep = ": ";
class Dict
{
public:
Dict(const std::string &path = defaultpath) : _dict_conf_filepath(path)
{
Load();
}
//加载模块 :将英汉词典加载进服务端
bool Load()
{
}
//翻译模块 :将输入的单词翻译成对应的中文
std::string Translate(const std::string &word)
{
}
~Dict()
{}
private:
// 存储词典的英汉映射
std::unordered_map<std::string, std::string> _dict;
// 用于配置英汉词典的路径
std::string _dict_conf_filepath;
};
}
加载模块
//加载模块 :将英汉词典加载进服务端
bool Load()
{
//打开默认路径下的词典文件
std::ifstream in(_dict_conf_filepath);
if(!in.is_open())
{
LOG(FATAL,"open %s error\n",_dict_conf_filepath.c_str());
return false;
}
std::string line;
//逐行读取词典文件
while(std::getline(in,line))
{
if(line.empty()) continue;
auto pos = line.find(sep);
if(pos==std::string::npos) continue;
//截取英文单词
std::string word = line.substr(0,pos);
if(word.empty()) continue;
//截取对应的中文翻译
std::string chinese = line.substr(pos+sep.size());
if(chinese.empty()) continue;
LOG(DEBUG, "load info, %s: %s\n", word.c_str(), chinese.c_str());
//截取完毕,把它们一一映射进map里
_dict.insert(std::make_pair(word,chinese));
}
//关闭文件
in.close();
LOG(DEBUG, "load %s success\n", _dict_conf_filepath.c_str());
return true;
}
翻译模块
//翻译模块 :将输入的单词翻译成对应的中文
std::string Translate(const std::string &word)
{
auto iter = _dict.find(word);
if(iter == _dict.end())
{
return "not found";
}
// return _dict[word];
return iter->second;
}
最终效果
最后关键的一步就是我们应该如何利用function让服务端接入翻译功能
最后需要理解的就是回调函数func的调用以及如何利用bind来解决参数问题~
二.聊天室
设计思路:通过服务端接收来自各个客户端的信息,然后把信息传递给通信模块,让通信模块把数据传递到线程池中,由多线程分发给各个客户端信息达到客户端之间共享信息的目的~
核心代码
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/socket.h>
#include <functional>
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include "LockGuard.hpp"
using task_t = std::function<void()>;
class MessageRoute
{
private:
//判断现有用户是否已被记录为在线用户
bool IsExists(const InetAddr &addr)
{
for (auto a : _online_user)
{
//因为InetAddr为自定义类,还需要重载一下==
if (a == addr)
return true;
}
return false;
}
public:
MessageRoute()
{
pthread_mutex_init(&_mutex, nullptr);
}
// 添加用户
void AddUser(const InetAddr &addr)
{
LockGuard lockguard(&_mutex);
if(IsExists(addr)) return ;
_online_user.push_back(addr);
}
//删除用户
void DelUser(const InetAddr &addr)
{
LockGuard lockguard(&_mutex);
for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
{
if (*iter == addr)
{
_online_user.erase(iter);
break;
}
}
}
void RouteHelper(int sockfd,const std::string &message, InetAddr who)
{
LockGuard lockguard(&_mutex);
//3. 遍历所有在线用户,把信息发送给所有在线用户
for(auto user:_online_user)
{
//发送给用户的信息
std::string send_message = "\n[" + who.Ip() + ":" + std::to_string(who.Port()) + "]# " + message + "\n";
//把用户的自定义类转化为sockaddr_in
struct sockaddr_in clientaddr = user.Addr();
::sendto(sockfd, send_message.c_str(), send_message.size(), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
}
}
//这里就作为接收数据的地方,因为func会把三种数据带出来
void Route(int sockfd,const std::string &message, InetAddr who)
{
//1. 我们的用户在首次发送信息时,还需要把自己添加进在线用户中
AddUser(who);
//2. 如果客户端要退出,删除其在线记录
if(message == "Q"||message == "QUIT")
{
DelUser(who);
}
//构建任务对象
task_t t = std::bind(&MessageRoute::RouteHelper, this, sockfd, message, who);
//把函数对象放在线程池的任务队列中,最终线程池中的多线程就会去执行RouteHelper函数
ThreadPool<task_t>::GetInstance()->Enqueue(t);
// //3. 遍历所有在线用户,把信息发送给所有在线用户
// for(auto user:_online_user)
// {
// //发送给用户的信息
// std::string send_message = "\n[" + who.Ip() + ":" + std::to_string(who.Port()) + "]# " + message + "\n";
// //把用户的自定义类转化为sockaddr_in
// struct sockaddr_in clientaddr = user.Addr();
// ::sendto(sockfd, send_message.c_str(), send_message.size(), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
// }
}
~MessageRoute()
{
pthread_mutex_destroy(&_mutex);
}
private:
// 记录各个客户端的socket
std::vector<InetAddr> _online_user;
pthread_mutex_t _mutex;
};
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Thread.hpp"
#include "Comm.hpp"
using namespace ThreadModule;
//客户端初始化
int InitClient(std::string &serverip, uint16_t serverport, struct sockaddr_in *server)
{
// 1. 创建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return -1;
}
// 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!
// a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!
// b. 什么时候bind呢?首次发送数据的时候
// 构建目标主机的socket信息
memset(server, 0, sizeof(struct sockaddr_in));
server->sin_family = AF_INET;
server->sin_port = htons(serverport);
server->sin_addr.s_addr = inet_addr(serverip.c_str());
return sockfd;
}
//客户端接收信息
void recvmessage(int sockfd, std::string name)
{
// int fd = OpenDev("/dev/pts/2", O_WRONLY);
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char buffer[1024];
ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (n > 0)
{
buffer[n] = 0;
fprintf(stderr, "[%s]%s\n", name.c_str(), buffer); // stderr
//write(fd, buffer, strlen(buffer));
}
}
}
//客户端发送信息
void sendmessage(int sockfd, struct sockaddr_in &server, std::string name)
{
std::string message;
while (true)
{
printf("%s | Enter# ", name.c_str());
fflush(stdout);
std::getline(std::cin, message);
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
}
}
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}
// ./udpclient serverip serverport 向目标主机中的某个端口号发送数据
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
struct sockaddr_in serveraddr;
// 1. 创建socket
int sockfd = InitClient(serverip, serverport, &serveraddr);
if(sockfd ==-1)
{
return 1;
}
func_t r = std::bind(&recvmessage, sockfd, std::placeholders::_1);
func_t s = std::bind(&sendmessage, sockfd, serveraddr, std::placeholders::_1);
Thread Recver(r, "recver");
Thread Sender(s, "sender");
Recver.Start();
Sender.Start();
Recver.Join();
Sender.Join();
return 0;
}
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"
enum
{
SOCKET_ERROR = 1,
BIND_ERROR,
USAGE_ERROR
};
const static int defaultfd = -1;
// typedef std::function<std::string(const std::string&)> func_t;
//回调函数 那么回调出去的函数需要带哪些数据进行处理呢? sockfd:通过文件转发消息 message:消息 InetAddr:区分该消息属于谁
using hander_message_t = std::function<void (int sockfd,const std::string message,const InetAddr who)>;
class UdpServer
{
public:
UdpServer(uint16_t port,hander_message_t hander_message):_sockfd(defaultfd),_port(port),_isrunning(false),_hander_message(hander_message)
{}
void InitServer()
{
//1.创建udp socket 套接字 本质就是创建了一个文件
//模式:网络模式,UDP协议
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd<0)
{
LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);
exit(SOCKET_ERROR);
}
LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);
//socket只是创建了一个文件,那么未来远端该如何找到呢?
//2.0 填充sockaddr_in结构,里面是网络信息
struct sockaddr_in local;// struct sockaddr_in 系统提供的数据类型。local是变量,用户栈上开辟空间。int a = 100; a = 20;
bzero(&local, sizeof(local));//清空数据
local.sin_family = AF_INET;//地址类型,标明为网络通信
local.sin_port = htons(_port); // port要经过网络传输给对面,先到网络,_port:主机序列-> 主机序列,转成网络序列
//a. 字符串风格的点分十进制的IP地址转成 4 字节IP,例如127.0.0.1,这是给人看的不是给机器看的
//b. 主机序列,转成网络序列
//in_addr_t inet_addr(const char *cp) //-> 同时完成 a & b
//local.sin_addr.s_addr = inet_addr(_ip.c_str()); // "192.168.3.1" -> 字符串风格的点分十进制的IP地址 -> 4字节IP
//默认IP为0
local.sin_addr.s_addr = INADDR_ANY; // htonl(INADDR_ANY);
//2.1 bind,将文件与网络信息(ip+port)关联在一起
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if (n < 0)
{
LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);
exit(BIND_ERROR);
}
LOG(INFO, "socket bind success\n");
}
void Start()
{
//一直运行,直到管理者不想管理
_isrunning = true;
while(true)
{
char message[64];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);//初始化
//1.先让服务端接收/读取数据
// 实际读取字节数 作为读取缓冲区 期望长度 对方的网路信息(ip+端口号)
ssize_t n = recvfrom(_sockfd,message,sizeof(message)-1,0,(struct sockaddr*)&peer,&len);
if (n > 0)
{
message[n] = 0;
//因为客户端的网络信息是由bind自动绑定的,所以我们一般不清楚其网络信息,这里是用一个类把其解析出来
InetAddr addr(peer);
LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), message);
//服务端仅读取数据,发送数据由通信模块进行
_hander_message(_sockfd,message,addr);
// 2. 我们要将server收到的数据,发回给对方
//sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len);
}
}
_isrunning = false;
}
~UdpServer()
{}
private:
int _sockfd; // socket所创的文件描述符
//std::string _ip; // 服务器所用的协议
uint16_t _port; // 服务器所用的端口号
bool _isrunning;
hander_message_t _hander_message;
};
答疑解惑
目前疑点:
各个客户端是怎么保持通信即消息同步的?
如何正确理解多个bind?
在多线程的情况下线程应该是争抢任务?目前还无法理解,如同第一点
流程梳理
三.全部代码
英汉词典 · b53dec9 · 玛丽亚后/keep - Gitee.com
聊天室 · c878802 · 玛丽亚后/keep - Gitee.com
破防的一天呢~