16.udp_socket(二)
一.概念回顾
建议先学上篇博客,再向下学习,上篇博客的链接如下:
https://blog.csdn.net/weixin_60668256/article/details/154700406?fromshare=blogdetail&sharetype=blogdetail&sharerId=154700406&sharerefer=PC&sharesource=weixin_60668256&sharefrom=from_link
二.Dictionary的更改
1.UdpServer.hpp的修改



#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <cstring>
#include <errno.h>
#include <strings.h>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"using namespace LogModule;const static int gsockfd = -1;
const static std::string gdefaultip = "127.0.0.1";//表示本地主机
const static uint16_t gdefaultport = 8080;using func_t = std::function<std::string(const std::string&)>;class UdpServer
{
public:UdpServer(uint16_t port = gdefaultport,func_t func):_sockfd(gsockfd),_addr(port),_isrunning(false),_func(func){}void InitServer(){// 1.创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket: " << strerror(errno);Die(SOCKET_ERR);}LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;// 2.1 bind :: 设置进入内核中int n = ::bind(_sockfd,_addr.NetAddr(),_addr.NetAddrLen());if(n < 0){LOG(LogLevel::FATAL) << "bind: " << strerror(errno);Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success";}void Start(){_isrunning = true;while(true){char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = ::recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if(n > 0){//将英文转换成为中文std::string result = _func(inbuffer);::sendto(_sockfd,result.c_str(),result.size(),0,CONV(&peer),sizeof(peer));}}_isrunning = false;}~UdpServer(){if(_sockfd > gsockfd){::close(gsockfd);}}private:int _sockfd;InetAddr _addr;bool _isrunning; //服务器运行状态//业务func_t _func;
};#endif
2.Dict.txt的导入
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
3.Dictionary.hpp框架实现
#pragma once#include <iostream>
#include <string>
#include <unordered_map>const std::string gpath = "./";
const std::string gdictname = "Dict.txt";class Dictionary
{
public:Dictionary(const std::string&path = gpath,const std::string& filename = gdictname):_path(path),_filename(gdictname){}bool LoadDictionary(){}std::string translate(const std::string& word){}~Dictionary(){}
private:std::unordered_map<std::string,std::string> _dictionary;std::string _path;std::string _filename;
};
4.LoadDictionary()的实现
bool LoadDictionary(){std::string file = _path + _filename;std::ifstream in(file.c_str());if(!in.is_open()){LOG(LogLevel::ERROR) << "open file " << file << " error";return false;}std::string line;while(std::getline(in,line)){std::string key;std::string value;if(SplitString(line,&key,&value,gsep)){_dictionary.insert(std::make_pair(key,value));}}in.close();}

5.SplitString()的实现
bool SplitString(std::string& line,std::string* key,std::string* value,const std::string& sep)
{auto pos = line.find(sep);if(pos == std::string::npos){return false;}*key = line.substr(0,pos);*value = line.substr(pos + sep.size());if(key->empty() || value->empty()){return false;}return true;
}
6.Translate()的实现
std::string translate(const std::string& word){auto iter = _dictionary.find(word);if(iter == _dictionary.end()){return "None";}return iter->second;}
7.测试代码 + debug
#include "UdpServer.hpp"
#include "Dictionary.hpp"// ./server_udp localport
int main(int argc,char* argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " localport" << std::endl;Die(USAGE_ERR);}uint16_t port = std::stoi(argv[1]);ENABLE_CONSOLE_LOG();std::shared_ptr<Dictionary> dict_sptr = std::make_shared<Dictionary>();std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port,[&dict_sptr](const std::string& word){return dict_sptr->translate(word);});svr_uptr->InitServer();svr_uptr->Start();return 0;
}
但是我们发现,输入没有回显

void Print(){for(auto& iter : _dictionary){std::cout << iter.first << " : " << iter.second << std::endl;}}
我们可以加一个Print(),进行加载之后我们对应的单词的打印


void Start(){_isrunning = true;while(true){char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = ::recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if(n > 0){//将英文转换成为中文inbuffer[n] = 0;std::string result = _func(inbuffer);::sendto(_sockfd,result.c_str(),result.size(),0,CONV(&peer),sizeof(peer));}}_isrunning = false;}


三.简易版聊天室
1.聊天室设计
#pragma once#include <iostream>
#include <string>
#include <list>
#include <memory>
#include "InetAddr.hpp"
#include "Log.hpp"using namespace LogModule;class UserInterface
{
public:virtual ~UserInterface() = 0;virtual void SendTo(const std::string& message) = 0;
};class User : public UserInterface
{
public:User(const InetAddr& id):_id(id){}void SendTo(const std::string& message)override{LOG(LogLevel::DEBUG) << "send message to " << _id.Addr() << " info: " << message;}~User(){}
private:InetAddr _id;
};//对用户消息进行路由
//所有用户先进行管理
class UserManager
{
private:std::list<std::shared_ptr<UserInterface>> _inline_user;
};


这里我们使用的室list,(后续的增删用户操作较多,转发消息都是O(n))
然后由用户管理进行消息转发
2.AddUser()(添加用户)的实现
首先我们对应的用户有了,就不用再进行添加了
class User : public UserInterface
{
public:User(const InetAddr& id):_id(id){}void SendTo(int sockfd,const std::string& message)override{LOG(LogLevel::DEBUG) << "send message to " << _id.Addr() << " info: " << message;int n = ::sendto(sockfd,message.c_str(),message.size(),0,_id.NetAddr(),_id.NetAddrLen());(void)n;}bool operator==(const InetAddr& u)override{return _id == u;}~User(){}
private:InetAddr _id;
};

bool operator==(const InetAddr& addr){return _ip == addr._ip;}
class UserInterface
{
public:virtual ~UserInterface() = default;virtual void SendTo(int sockfd,const std::string& message) = 0;virtual bool operator==(const InetAddr& u) = 0;
};
void AddUser(const InetAddr& id){for(auto& user : _online_user){if(*user == id){return;}}_online_user.push_back(std::make_shared<User>(id));}

3.DelUser()的实现
4.Router()的实现
这就是观测者模式
void Router(int sockfd,const std::string& message){for(auto& user:_online_user){user->SendTo(sockfd,message);}}

5.User的SendTo()实现
void SendTo(int sockfd,const std::string& message)override{LOG(LogLevel::DEBUG) << "send message to " << _id.Addr() << " info: " << message;int n = ::sendto(sockfd,message.c_str(),message.size(),0,_id.NetAddr(),_id.NetAddrLen());(void)n;}
6.UdpServer设置回调函数
a.定义回调函数类型
using adduser_t = std::function<void(const InetAddr& id)>;

b.定义成员变量



c.消息发送时增加对应的用户
void Start(){_isrunning = true;while(true){char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = ::recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if(n > 0){//1.消息内容 && 2.谁给我发的InetAddr cli(peer);inbuffer[n] = 0;//2.新增用户_adduser(cli);}}_isrunning = false;}
d.ServerMain.cc的回调方法
int main(int argc,char* argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " localport" << std::endl;Die(USAGE_ERR);}uint16_t port = std::stoi(argv[1]);ENABLE_CONSOLE_LOG();std::shared_ptr<UserManager> um = std::make_shared<UserManager>();std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>([&um](InetAddr& id){um->AddUser(id);},port);svr_uptr->InitServer();svr_uptr->Start();return 0;
}

所以当我们对应的用户只要进行发送消息,那么我们就能收到用户add的消息

void Start(){_isrunning = true;while(true){char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = ::recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if(n > 0){//1.消息内容 && 2.谁给我发的InetAddr cli(peer);inbuffer[n] = 0;//2.新增用户_adduser(cli);std::string clientinfo = cli.Ip() + " : " + std::to_string(cli.Port()) + " # " + inbuffer;LOG(LogLevel::DEBUG) << clientinfo;std::string echo_string = "echo# ";echo_string += inbuffer;::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,CONV(&peer),sizeof(peer));}}_isrunning = false;}


7.线程池做router(转发)
将我们对应的单例线程池的相关代码全部都拷贝到项目中



8.代码修改
void RegisterService(adduser_t adduser,route_t route){_adduser = adduser;_route = route;}
我们在类内定义一个RegisterService方法,然后初始化服务器之后,我们再对该方法进行传参
std::shared_ptr<UserManager> um = std::make_shared<UserManager>();std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);svr_uptr->InitServer();svr_uptr->RegisterService([&um](InetAddr& id){um->AddUser(id);},[&um](int sockfd,const std::string& message){um->Router(sockfd,message);});

9.客户端问题解决

这个问题我们要进行解决,所以我们的客户端也要进行多线程进行访问
void* Recver(void* args)
{while(true){struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];int n = ::recvfrom(sockfd,buffer,sizeof(buffer)-1,0,CONV(&temp),&len);if(n > 0){buffer[n] = 0;std::cout << buffer << std::endl;}}return nullptr;
}


