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 
#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;
}

posted @ 2025-12-09 22:41  clnchanpin  阅读(1)  评论(0)    收藏  举报