0


【网络编程】UDP简单实现翻译软件与网络聊天室

文章目录

一、引入

在上一章【网络编程】demo版UDP网络服务器实现实现了客户端和服务端之间的数据的发送与接收,上一章我们是直接让服务端把接收到的数据打印出来。在这里插入图片描述
但是服务端并不是只接收到数据就完了,它还要处理任务
所以我们可以在服务端设置一个回调函数:

typedef std::function<void(std::string,uint16_t, std::string)> func_t;

用来处理接收到的信息。

二、翻译软件实现

2.1 加载字典

要实现客户端发送一个单词,接收到服务端翻译的结果。
那么首先需要把英语和汉语加载进

unordered_map

词典中。

static std::unordered_map<std::string, std::string> Dict;

从文件中把数据加载进词典中:
在这里插入图片描述

const std::string path ="./Dict.txt";static std::unordered_map<std::string, std::string> Dict;// 分割字符串staticboolcutstring(std::string s, std::string *key, std::string *val){
    size_t pos = s.find(":");if(pos == std::string::npos){returnfalse;}*key = s.substr(0, pos);*val = s.substr(pos +1);returntrue;}staticvoidInitDict(){
    std::ifstream ifs(path);if(!ifs.is_open())// 打开失败{
        std::cout <<"ifstream open fail"<< std::endl;exit(1);}
    std::string sline;
    std::string key, val;while(getline(ifs, sline))// 按行读取{if(cutstring(sline,&key,&val)){
            Dict.insert({key, val});}else{
            std::cout <<"cutstring fail"<< std::endl;exit(1);}}
    ifs.close();
    std::cout <<"Dict Load Success\n";}

2.2 处理数据并传递给用户端

接下来处理数据并且要把处理的结果反馈给用户端。

处理数据比较简单:

std::string res;auto it = Dict.find(msg);if(it == Dict.end()){
    res ="not find";}else{
    res = it->second;}

接下来要把

res

发送给客户端,经过前面的学习

sendto

需要构建一个结构体表示要传给谁。
构建完结构体使用

sendto

的时候发现要有文件描述符,所以调用回调函数的时候要把

_sockfd

传递进去。

// 回调方法voidhandler(int _sockfd, std::string ip,uint16_t port, std::string msg){
    std::string res;auto it = Dict.find(msg);if(it == Dict.end()){
        res ="not find";}else{
        res = it->second;}structsockaddr_in si;bzero(&si,sizeof si);
    si.sin_family = AF_INET;
    si.sin_port =htons(port);
    si.sin_addr.s_addr =inet_addr(ip.c_str());sendto(_sockfd, res.c_str(), res.size(),0,(structsockaddr*)&si,sizeof si);}

2.3 客户端获取结果

// 获取结果char buf[1024];
sockaddr_in tmp;
socklen_t tmplen =sizeof tmp;
size_t n =recvfrom(_sockfd, buf,sizeof buf -1,0,(structsockaddr*)&tmp,&tmplen);if(n >0) buf[n]=0;
std::cout <<"翻译结果:"<< buf <<"\n";

2.4 结果

客户端:
在这里插入图片描述
服务端:
在这里插入图片描述

2.5 执行命名功能

现在我们可以做一个小小的改动,不让服务端做翻译了,而是处理任务,例如我们发送pwd,他要返回服务端此时的路径。
这里其他的逻辑都不用变,只需要修改回调函数即可。

介绍一个命令行解析接口
**

popen

**

#include<stdio.h>

FILE *popen(constchar*command,constchar*type);intpclose(FILE *stream);

先回忆一下之前手写的建议shell【linux】进程控制详述,我们首先要命令行分割,把每个选项都提取出来,然后fork创建子进程再程序替换。
而这个函数包含了上述的所有功能,并且还多了一个创建管道的功能。
参数介绍:

command

:传递进来的字符串,比如

ls -a -l
type

:以什么方式打开文件(r/w/a)
比方说现在以r只读方式打开,就直接从管道中提取出结果。

voidexeccommand(int _sockfd, std::string ip,uint16_t port, std::string msg){
    std::string res;
    FILE* fp =popen(msg.c_str(),"r");if(fp ==nullptr) res = msg +" execute fail";else{char line[1024];while(fgets(line,sizeof line, fp)){
            res += line;}}pclose(fp);structsockaddr_in si;bzero(&si,sizeof si);
    si.sin_family = AF_INET;
    si.sin_port =htons(port);
    si.sin_addr.s_addr =inet_addr(ip.c_str());sendto(_sockfd, res.c_str(), res.size(),0,(structsockaddr*)&si,sizeof si);}

客户端:
在这里插入图片描述
服务端:
在这里插入图片描述

三、网络聊天室实现

聊天室是什么远离呢?我们发送消息会进过服务端的转发,让每个在线的客户端都能看到发送的消息,这样就实现了群聊。

每个客户端发送一个

online

表示上线了,服务端就会把所有的数据都发送给上线的客户端。

所以首先我们先要把所有的用户管理起来。

3.1 管理用户

对于每个用户我们用IP和port来标识唯一性。

classUser{public:User(const std::string& ip,constuint16_t& port):_ip(ip),_port(port){}public:
    std::string _ip;uint16_t _port;};

对于每个用户我们可以用哈希表来管理:

classOnlineUsers{public:voidadduser(const std::string& ip,constuint16_t& port){
        std::string id = ip +"@"+ std::to_string(port);
        users.insert({id,User(ip, port)});}voiddeluser(const std::string& ip,constuint16_t& port){
        std::string id = ip +"@"+ std::to_string(port);
        users.erase(id);}boolisonline(const std::string& ip,constuint16_t& port){
        std::string id = ip +"@"+ std::to_string(port);return users.find(id)!= users.end();}private:
    std::unordered_map<std::string, User> users;};

在回调函数中,如果收到的消息是online,就把用户添加进哈希表。如果是offline,就从哈希表中删除。

if(msg =="online") onlinemap.adduser(ip, port);if(msg =="offline") onlinemap.deluser(ip, port);

3.2 发送消息

我们要发送消息其实就是给哈希表中所有的用户都

sendto

消息。在这之前要先判断是否在线

voidgroupmsg(int _sockfd, std::string ip,uint16_t port, std::string msg){if(msg =="online") onlinemap.adduser(ip, port);if(!onlinemap.isonline(ip, port))// 不在线{structsockaddr_in si;bzero(&si,sizeof si);
        si.sin_family = AF_INET;
        si.sin_port =htons(port);
        si.sin_addr.s_addr =inet_addr(ip.c_str());
        std::string res ="你还没有上线,请先上线";sendto(_sockfd, res.c_str(), res.size(),0,(structsockaddr*)&si,sizeof si);return;}// 广播消息}

现在要广播消息,那么就在

OnlineUsers

类中增加一个广播消息的成员函数。
它的参数要包含

_sockfd, msg

,还要有是谁发送的(

ip

,

port

)。

voidbroadmsg(int _sockfd,const std::string& msg, std::string ip,uint16_t port){for(auto& e : users){structsockaddr_in si;bzero(&si,sizeof si);
        si.sin_family = AF_INET;
        si.sin_port =htons(e.second._port);
        si.sin_addr.s_addr =inet_addr(e.second._ip.c_str());
        std::string res = ip +"@"+ std::to_string(port)+"# ";
        res += msg;sendto(_sockfd, res.c_str(), res.size(),0,(structsockaddr*)&si,sizeof si);}}

3.3 多线程处理

因为客户端不能立即收到消息打印出来(阻塞停留在接收消息),为了解决这个问题我们可以使用多线程,一个线程专门接收消息,一个线程专门发送消息。

那么我们可以让主线程负责发送消息,子线程负责接收消息。

staticvoid*getmsg(void*args){pthread_detach(pthread_self());int sockfd =*(static_cast<int*>(args));while(1){char buf[1024];
        sockaddr_in tmp;
        socklen_t tmplen =sizeof tmp;
        size_t n =recvfrom(sockfd, buf,sizeof buf -1,0,(structsockaddr*)&tmp,&tmplen);if(n >0) buf[n]=0;
        std::cout << buf <<"\n";}}voidstart(){pthread_create(&_get,nullptr, getmsg,(void*)&_sockfd);structsockaddr_in si;bzero(&si,sizeof(si));
    si.sin_family = AF_INET;
    si.sin_addr.s_addr =inet_addr(_serverip.c_str());
    si.sin_port =htons(_serverport);
    std::string msg;while(1){
        std::cout <<"Please input: ";
        std::cin >> msg;sendto(_sockfd, msg.c_str(), msg.size(),0,(structsockaddr*)&si,sizeof si);}}

3.4 结果

服务端:
在这里插入图片描述
客户端:
在这里插入图片描述
这里因为只有一个终端所以打印的比较混乱,但是能看到现象就行,如果想要优化就可以把输出的数据重定向到管道文件中,再打开一个终端读取管道,这样就可以实现发送和获取两个窗口。

四、源码

//UDPClient.hpp#pragmaonce #include<iostream>#include<string>#include<cerrno>#include<sys/types.h>#include<sys/socket.h>#include<unistd.h>#include<arpa/inet.h>#include<strings.h>#include<netinet/in.h>#include<string.h>#include<cassert>#include<pthread.h>classUDPClient{public:UDPClient(const std::string& serverip,constuint16_t& port):_serverip(serverip),_serverport(port),_sockfd(-1){}voidInitClient(){// 创建套接字
        _sockfd =socket(AF_INET, SOCK_DGRAM,0);if(_sockfd ==-1){
            std::cerr <<"socket error"<< errno <<" : "<<strerror(errno)<< std::endl;exit(1);}}staticvoid*getmsg(void*args){pthread_detach(pthread_self());int sockfd =*(static_cast<int*>(args));while(1){char buf[1024];
            sockaddr_in tmp;
            socklen_t tmplen =sizeof tmp;
            size_t n =recvfrom(sockfd, buf,sizeof buf -1,0,(structsockaddr*)&tmp,&tmplen);if(n >0) buf[n]=0;
            std::cout << buf <<"\n";}}voidstart(){pthread_create(&_get,nullptr, getmsg,(void*)&_sockfd);structsockaddr_in si;bzero(&si,sizeof(si));
        si.sin_family = AF_INET;
        si.sin_addr.s_addr =inet_addr(_serverip.c_str());
        si.sin_port =htons(_serverport);
        std::string msg;while(1){
            std::cout <<"Please input: ";
            std::cin >> msg;sendto(_sockfd, msg.c_str(), msg.size(),0,(structsockaddr*)&si,sizeof si);}}private:uint16_t _serverport;
    std::string _serverip;int _sockfd;
    pthread_t _get;};// UDPClient.cc#include"UDPClient.hpp"#include<memory>intmain(int argc,char* argv[]){if(argc !=3){
        std::cout <<"incorrect number of parameters"<< std::endl;exit(1);}
    std::string ip = argv[1];uint16_t port =atoi(argv[2]);
    std::unique_ptr<UDPClient>ptr(newUDPClient(ip, port));
    ptr->InitClient();
    ptr->start();return0;}// UDPServer.hpp#pragmaonce #include<iostream>#include<string>#include<cerrno>#include<sys/types.h>#include<sys/socket.h>#include<unistd.h>#include<arpa/inet.h>#include<strings.h>#include<netinet/in.h>#include<string.h>#include<cassert>#include<functional>staticconst std::string defaultip ="0.0.0.0";// 默认IPtypedef std::function<void(int, std::string,uint16_t, std::string)> func_t;classUDPServer{public:UDPServer(const func_t& func,constuint16_t& port,const std::string ip = defaultip):_port(port),_ip(ip),_sockfd(-1),_func(func){}voidInitServer(){// 创建套接字
        _sockfd =socket(AF_INET, SOCK_DGRAM,0);if(_sockfd ==-1){
            std::cerr <<"socket error"<< errno <<" : "<<strerror(errno)<< std::endl;exit(1);}// 绑定IP与portstructsockaddr_in si;bzero(&si,sizeof si);
        si.sin_family = AF_INET;// 协议家族
        si.sin_port =htons(_port);// 端口号,注意大小端问题// si.sin_addr.s_addr = inet_addr(_ip.c_str());// ip
        si.sin_addr.s_addr = INADDR_ANY;// 绑定int n =bind(_sockfd,(structsockaddr*)&si,sizeof si);assert(n !=-1);}voidstart(){char buf[1024];while(1){structsockaddr_in peer;
            socklen_t len =sizeof peer;
            ssize_t s =recvfrom(_sockfd, buf,sizeof(buf)-1,0,(structsockaddr*)&peer,&len);if(s >0){
                buf[s]=0;// 结尾
                std::string cip =inet_ntoa(peer.sin_addr);uint16_t cport =ntohs(peer.sin_port);
                std::string msg = buf;//std::cout << "[" << cip << "@" << cport << "]# " << msg << std::endl;_func(_sockfd, cip, cport, msg);}}}private:uint16_t _port;
    std::string _ip;int _sockfd;
    func_t _func;};// UDPServer.cc#include"UDPServer.hpp"#include"users.hpp"#include<memory>#include<cstdio>#include<unordered_map>#include<fstream>const std::string path ="./Dict.txt";static std::unordered_map<std::string, std::string> Dict;// 分割字符串staticboolcutstring(std::string s, std::string *key, std::string *val){
    size_t pos = s.find(":");if(pos == std::string::npos){returnfalse;}*key = s.substr(0, pos);*val = s.substr(pos +1);returntrue;}staticvoidInitDict(){
    std::ifstream ifs(path);if(!ifs.is_open())// 打开失败{
        std::cout <<"ifstream open fail"<< std::endl;exit(1);}
    std::string sline;
    std::string key, val;while(getline(ifs, sline))// 按行读取{if(cutstring(sline,&key,&val)){
            Dict.insert({key, val});}else{
            std::cout <<"cutstring fail"<< std::endl;exit(1);}}
    ifs.close();
    std::cout <<"Dict Load Success\n";}// 回调方法voidhandler(int _sockfd, std::string ip,uint16_t port, std::string msg){
    std::string res;auto it = Dict.find(msg);if(it == Dict.end()){
        res ="not find";}else{
        res = it->second;}structsockaddr_in si;bzero(&si,sizeof si);
    si.sin_family = AF_INET;
    si.sin_port =htons(port);
    si.sin_addr.s_addr =inet_addr(ip.c_str());sendto(_sockfd, res.c_str(), res.size(),0,(structsockaddr*)&si,sizeof si);}voidexeccommand(int _sockfd, std::string ip,uint16_t port, std::string msg){
    std::string res;
    FILE* fp =popen(msg.c_str(),"r");if(fp ==nullptr) res = msg +" execute fail";else{char line[1024];while(fgets(line,sizeof line, fp)){
            res += line;}}pclose(fp);structsockaddr_in si;bzero(&si,sizeof si);
    si.sin_family = AF_INET;
    si.sin_port =htons(port);
    si.sin_addr.s_addr =inet_addr(ip.c_str());sendto(_sockfd, res.c_str(), res.size(),0,(structsockaddr*)&si,sizeof si);}

OnlineUsers onlinemap;voidgroupmsg(int _sockfd, std::string ip,uint16_t port, std::string msg){if(msg =="online") onlinemap.adduser(ip, port);if(msg =="offline") onlinemap.deluser(ip, port);if(!onlinemap.isonline(ip, port))// 不在线{structsockaddr_in si;bzero(&si,sizeof si);
        si.sin_family = AF_INET;
        si.sin_port =htons(port);
        si.sin_addr.s_addr =inet_addr(ip.c_str());
        std::string res ="你还没有上线,请先上线";sendto(_sockfd, res.c_str(), res.size(),0,(structsockaddr*)&si,sizeof si);return;}// 广播消息
    onlinemap.broadmsg(_sockfd, msg, ip, port);}intmain(int argc,char* argv[]){if(argc !=2){
        std::cout <<"incorrect number of parameters"<< std::endl;exit(1);}//InitDict();uint16_t port =atoi(argv[1]);
    std::unique_ptr<UDPServer>ptr(newUDPServer(groupmsg, port));
    ptr->InitServer();
    ptr->start();return0;}// users.hpp#pragmaonce#include<iostream>#include<string>#include<unordered_map>#include<sys/types.h>#include<sys/socket.h>#include<unistd.h>#include<arpa/inet.h>#include<strings.h>#include<netinet/in.h>#include<string.h>classUser{public:User(const std::string& ip,constuint16_t& port):_ip(ip),_port(port){}public:
    std::string _ip;uint16_t _port;};classOnlineUsers{public:voidadduser(const std::string& ip,constuint16_t& port){
        std::string id = ip +"@"+ std::to_string(port);
        users.insert({id,User(ip, port)});}voiddeluser(const std::string& ip,constuint16_t& port){
        std::string id = ip +"@"+ std::to_string(port);
        users.erase(id);}boolisonline(const std::string& ip,constuint16_t& port){
        std::string id = ip +"@"+ std::to_string(port);return users.find(id)!= users.end();}voidbroadmsg(int _sockfd,const std::string& msg, std::string ip,uint16_t port){for(auto& e : users){structsockaddr_in si;bzero(&si,sizeof si);
            si.sin_family = AF_INET;
            si.sin_port =htons(e.second._port);
            si.sin_addr.s_addr =inet_addr(e.second._ip.c_str());
            std::string res = ip +"@"+ std::to_string(port)+"# ";
            res += msg;sendto(_sockfd, res.c_str(), res.size(),0,(structsockaddr*)&si,sizeof si);}}private:
    std::unordered_map<std::string, User> users;};


标签: 网络 udp 网络协议

本文转载自: https://blog.csdn.net/qq_66314292/article/details/130668674
版权归原作者 命由己造~ 所有, 如有侵权,请联系我们删除。

“【网络编程】UDP简单实现翻译软件与网络聊天室”的评论:

还没有评论