16.udp_socket(二) - 详解
一.概念回顾
建议先学上篇博客,再向下学习,上篇博客的链接如下:
二.Dictionary的更改
1.UdpServer.hpp的修改



#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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;
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
#include
#include
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 _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 dict_sptr = std::make_shared();
std::unique_ptr svr_uptr = std::make_unique(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
#include
#include
#include
#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> _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(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;

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 um = std::make_shared();
std::unique_ptr svr_uptr = std::make_unique([&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 um = std::make_shared();
std::unique_ptr svr_uptr = std::make_unique(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;
}


浙公网安备 33010602011771号