欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 游戏 > 【网络】网络聊天室udp

【网络】网络聊天室udp

2024/10/24 4:47:27 来源:https://blog.csdn.net/m0_70088010/article/details/140583079  浏览:    关键词:【网络】网络聊天室udp

网络聊天室udp

  • 一、低耦合度代码
    • 1、代码
    • 2、测试结果
  • 二、高耦合度代码
    • 1、服务端小改
      • (1)维护一个unordered_map用户列表
      • (2)服务端代码
      • (3)客户端不改的情况下结果展示
    • 2、大改客户端(udp全双工用多线程)
        • i、结果
        • ii、代码分析
        • iii、源代码
    • 3、ls /dev/pts/
    • 4、重定向输出dup2后在终端打印效果
    • 5、利用终端创建一个简易的聊天室
      • (1)代码
      • (2)结果展示
      • (3)也可以在终端用一个命令
    • 6、代码汇总


一、低耦合度代码

1、代码

在这里插入图片描述

2、测试结果

在这里插入图片描述

二、高耦合度代码

1、服务端小改

(1)维护一个unordered_map用户列表

在这里插入图片描述

(2)服务端代码

#pragma once #include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unordered_map>
#include "Log.hpp"using func_t = std::function<std::string(const std::string&, const std::string&, uint16_t)>; // 将返回值为string,参数为const string&的函数包装起来extern Log log;uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";enum 
{SOCKET_ERR=1,BIND_ERR
};class UdpServer
{
public:// 构造函数UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip): _socketfd(0), _port(port), _ip(ip), _isrunning(false){}void Init(){// 1.创建udp套接字socket_socketfd = socket(AF_INET, SOCK_DGRAM, 0);// 创建失败if (_socketfd < 0){log(Fatal, "socket create error,socketfd:%d", _socketfd);exit(SOCKET_ERR);}// 创建成功log(Info, "socket create sucess,socketfd:%d", _socketfd);// 2.绑定端口号bind socketstruct sockaddr_in local; // 网络套接字结构体bzero(&local, sizeof(local)); // 将该套接字结构体对象全部清零local.sin_family = AF_INET; // 类型:ipv4local.sin_port = htons(_port); // 端口号:是在网络中来回发送的,我发过去要让对面知道我发的端口号是什么,所以必须是网络字节序列local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1.string->unit_32 2.来回通信对方要知道发送的ip,所以ip的unit_32必须是网络序列的int n = bind(_socketfd, (const struct sockaddr *)&local, sizeof(local));if (n < 0){log(Fatal, "bind error, erron:%d, errno string:%s", errno, strerror(errno));exit(BIND_ERR);}log(Info, "bind sucess");}void CheckUser(const struct sockaddr_in &client, const std::string& clientip, uint16_t clientport){auto it = _online_user.find(clientip);if (it == _online_user.end()){// 添加_online_user.insert({clientip, client});std::cout << "{ " << clientip << " }" << "[ " << clientport << "]" << "#addr onlineuser sucess" << std::endl;}}void Broadcast(const std::string& info, const std::string& clientip, uint16_t clientport){for (const auto& user : _online_user){std::string message = "{ ";message += clientip;message += " }";message += "[ ";message += std::to_string(clientport);message += " ]#";message += info;socklen_t len = sizeof(user.second);sendto(_socketfd, message.c_str(), message.size(), 0, (struct sockaddr*)&(user.second), len);}}void Run(/*func_t func*/) // 对代码进行分层{_isrunning = true;char inbuffer[1024];while (_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_socketfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if (n < 0){log(Warning, "recvfrom error");continue;}uint16_t clientport = ntohs(client.sin_port); // 网络序列转主机序列,拿到portstd::string clientip = inet_ntoa(client.sin_addr); // 将网络序列的ip地址转化成为主机的序列的ip地址,风格是字符串类型的// 判断是不是新用户,新用户则添加到onlineuserCheckUser(client, clientip, clientport);std::string info = inbuffer;// 转发给所有人Broadcast(info, clientip, clientport);}}// 析构函数~UdpServer(){if (_socketfd > 0) {close(_socketfd);}}
private:int _socketfd; // 网络文件描述符,表示socket返回的文件描述符uint16_t _port; // 表明服务器进程的端口号std::string _ip; // ip地址,任意地址绑定为0bool _isrunning; // 判断是否运行std::unordered_map<std::string, struct sockaddr_in> _online_user; // client列表 主机序列的ip地址,网络序列的套接字信息
};

(3)客户端不改的情况下结果展示

在这里插入图片描述
出现一个比较奇葩的问题,我上面linux客户端是先链接的,发出一个消息halo,发现能够显示出来,我下面的vs是后面链接的,发了个haha,发现是能够显示的,但是我上面linux客户端发现halo被haha覆盖住了,而我们知道udp是个全双工的,就是接收消息和发送消息是可以分开的,可以套在多线程中的,但是上面消息却会被覆盖,因为在客户端的getline那边会阻塞住,会让优先级发送消息,我客户端要一直按回车才能收到别人发的消息,而不能实现消息的自动弹出,客户端只能一条一条收到消息,所以我们在客户端需要使用多线程版本的。我想实现一个我不发消息也能看到别人群聊发的消息。

2、大改客户端(udp全双工用多线程)

我们因为上面客户端不改变的情况下发现的是我要一直摁回车才能收到别人发的消息,其本质原因就是因为getline会阻塞住消息,即一条消息一发一条消息一收,所以我们下面客户端改成全双工,即接消息和发消息分开,这样能够实现我不发消息依旧能够收到别人发的消息,不需要按回车,我们看一下现象,再分析代码:

i、结果

在这里插入图片描述

ii、代码分析

在这里插入图片描述

iii、源代码
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>void Usage(std::string proc)
{std::cout << "\n\rUsages: " << proc << "serverip serverport\n" << std::endl;
}struct ThreadData
{struct sockaddr_in server;int socketfd;
};void* recv_message(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);char buffer[1024];while (true){// 收数据 -- 从socket文件中的数据拿出来到buffer中,并将收到的对方的个人信息进行保存到temp中struct sockaddr_in temp;socklen_t len2 = sizeof(temp);ssize_t n = recvfrom(td->socketfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len2);if (n > 0){buffer[n] = 0;// 打印数据std::cout << buffer << std::endl;}}
}void* send_message(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);std::string message;socklen_t len = sizeof(td->server);while (true){// 数据std::cout << "Please Enter# ";getline(std::cin, message);// 发送数据 -- 把数据发送到socketfd文件中,并将server信息提炼出来发送给server,可以理解成唤醒serversendto(td->socketfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->server), len);}
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1]; // serveripuint16_t serverport = std::stoi(argv[2]); // serverportstruct ThreadData td;// 给谁发bzero(&td.server, sizeof(td.server));td.server.sin_family = AF_INET;td.server.sin_port = htons(serverport);td.server.sin_addr.s_addr = inet_addr(serverip.c_str());td.socketfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字if (td.socketfd < 0){std::cout << "socket create error" << std::endl;return 1;}pthread_t recver;pthread_t sender;pthread_create(&recver, nullptr, recv_message, &td);pthread_create(&sender, nullptr, send_message, &td);pthread_join(recver, nullptr);pthread_join(sender, nullptr);close(td.socketfd);return 0;
}

3、ls /dev/pts/

我们发现的是/dev/pts/的3号文件是我们的1号终端,我们发送消息给3号文件就是发送给了1号终端了。
在这里插入图片描述

4、重定向输出dup2后在终端打印效果

test.cc:
在这里插入图片描述

g++ test.cc

在这里插入图片描述

5、利用终端创建一个简易的聊天室

(1)代码

在这里插入图片描述

(2)结果展示

在这里插入图片描述

(3)也可以在终端用一个命令

这里的3是你需要ls 命令去看哪个终端能用的文件信息的,因为linux下一切皆文件,那么终端也是文件。
在这里插入图片描述

6、代码汇总

udpserver.hpp:

#pragma once #include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unordered_map>
#include "Log.hpp"using func_t = std::function<std::string(const std::string&, const std::string&, uint16_t)>; // 将返回值为string,参数为const string&的函数包装起来extern Log log;uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";enum 
{SOCKET_ERR=1,BIND_ERR
};class UdpServer
{
public:// 构造函数UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip): _socketfd(0), _port(port), _ip(ip), _isrunning(false){}void Init(){// 1.创建udp套接字socket_socketfd = socket(AF_INET, SOCK_DGRAM, 0);// 创建失败if (_socketfd < 0){log(Fatal, "socket create error,socketfd:%d", _socketfd);exit(SOCKET_ERR);}// 创建成功log(Info, "socket create sucess,socketfd:%d", _socketfd);// 2.绑定端口号bind socketstruct sockaddr_in local; // 网络套接字结构体bzero(&local, sizeof(local)); // 将该套接字结构体对象全部清零local.sin_family = AF_INET; // 类型:ipv4local.sin_port = htons(_port); // 端口号:是在网络中来回发送的,我发过去要让对面知道我发的端口号是什么,所以必须是网络字节序列local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1.string->unit_32 2.来回通信对方要知道发送的ip,所以ip的unit_32必须是网络序列的int n = bind(_socketfd, (const struct sockaddr *)&local, sizeof(local));if (n < 0){log(Fatal, "bind error, erron:%d, errno string:%s", errno, strerror(errno));exit(BIND_ERR);}log(Info, "bind sucess");}void CheckUser(const struct sockaddr_in &client, const std::string& clientip, uint16_t clientport){auto it = _online_user.find(clientip);if (it == _online_user.end()){// 添加_online_user.insert({clientip, client});std::cout << "{ " << clientip << " }" << "[ " << clientport << "]" << "#addr onlineuser sucess" << std::endl;}}void Broadcast(const std::string& info, const std::string& clientip, uint16_t clientport){for (const auto& user : _online_user){std::string message = "{ ";message += clientip;message += " }";message += "[ ";message += std::to_string(clientport);message += " ]#";message += info;socklen_t len = sizeof(user.second);sendto(_socketfd, message.c_str(), message.size(), 0, (struct sockaddr*)&(user.second), len);}}void Run(/*func_t func*/) // 对代码进行分层{_isrunning = true;char inbuffer[1024];while (_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_socketfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if (n < 0){log(Warning, "recvfrom error");continue;}uint16_t clientport = ntohs(client.sin_port); // 网络序列转主机序列,拿到portstd::string clientip = inet_ntoa(client.sin_addr); // 将网络序列的ip地址转化成为主机的序列的ip地址,风格是字符串类型的// 判断是不是新用户,新用户则添加到onlineuserCheckUser(client, clientip, clientport);std::string info = inbuffer;// 转发给所有人Broadcast(info, clientip, clientport);}}// 析构函数~UdpServer(){if (_socketfd > 0) {close(_socketfd);}}
private:int _socketfd; // 网络文件描述符,表示socket返回的文件描述符uint16_t _port; // 表明服务器进程的端口号std::string _ip; // ip地址,任意地址绑定为0bool _isrunning; // 判断是否运行std::unordered_map<std::string, struct sockaddr_in> _online_user; // client列表 主机序列的ip地址,网络序列的套接字信息
};

udpclient.cc:

#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "terminal.hpp"void Usage(std::string proc)
{std::cout << "\n\rUsages: " << proc << "serverip serverport\n" << std::endl;
}struct ThreadData
{struct sockaddr_in server;int socketfd;std::string serverip;
};void* recv_message(void* args)
{openterminal();ThreadData* td = static_cast<ThreadData*>(args);char buffer[1024];while (true){// 清空memset(buffer, 0, sizeof(buffer));// 收数据 -- 从socket文件中的数据拿出来到buffer中,并将收到的对方的个人信息进行保存到temp中struct sockaddr_in temp;socklen_t len2 = sizeof(temp);ssize_t n = recvfrom(td->socketfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len2);if (n > 0){buffer[n] = 0;// 打印数据std::cerr << buffer << std::endl;}}
}void* send_message(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);std::string message;socklen_t len = sizeof(td->server);std::string welcome = td->serverip;welcome += "coming...";sendto(td->socketfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->server), len);while (true){// 数据std::cout << "Please Enter# ";getline(std::cin, message);// 发送数据 -- 把数据发送到socketfd文件中,并将server信息提炼出来发送给server,可以理解成唤醒serversendto(td->socketfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->server), len);}
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1]; // serveripuint16_t serverport = std::stoi(argv[2]); // serverportstruct ThreadData td;// 给谁发bzero(&td.server, sizeof(td.server));td.server.sin_family = AF_INET;td.server.sin_port = htons(serverport);td.server.sin_addr.s_addr = inet_addr(serverip.c_str());td.socketfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字if (td.socketfd < 0){std::cout << "socket create error" << std::endl;return 1;}td.serverip = serverip;pthread_t recver;pthread_t sender;pthread_create(&recver, nullptr, recv_message, &td);pthread_create(&sender, nullptr, send_message, &td);pthread_join(recver, nullptr);pthread_join(sender, nullptr);close(td.socketfd);return 0;
}

main.cc:

#include "udp.hpp"
#include "Log.hpp"
#include <memory>
#include <cstdio>
#include <vector>Log log;void Usage(std::string proc)
{std::cout << "\n\rUsages: " << proc << "port[1024+]\n" << std::endl;
}std::string Handler(const std::string& info, const std::string& ip, uint16_t port)
{std::cout << "{ " << ip << " }" << "[ " << port << "]" << "# " << info << std::endl;std::string res = "recv a message# ";res += info;std::cout << res << std::endl;return res;
}bool SafeCheck(const std::string& cmd)
{std::vector<std::string> word_key = {"rm","top","cp","yum","while","kill","unlink""uninstall","top"};for (auto &word : word_key){auto pos = cmd.find(word);if (pos != std::string::npos){return false;}}return true;
}std::string ExcuteCommand(const std::string& cmd)
{std::cout << "get a massage:" << cmd << std::endl;// 做一个保护if (!SafeCheck(cmd)) return "bad man";FILE* fp = popen(cmd.c_str(), "r"); // 管道创建好,子进程创建好,子进程通过管道放到父进程if (nullptr == fp){perror("popen failed");return "error";}std::string result;char buffer[4096];while (true){char* ok = fgets(buffer, sizeof(buffer), fp); // 写到buffer缓冲区中if (ok == nullptr){break;}result += buffer;}pclose(fp);return result;
}// 以后用的是./udpserver + port
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> svr(new UdpServer(port)); // new一个对象svr->Init(); // 初始化svr->Run(/*Handler*/);  // 跑起来
}

terminal.hpp:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string>std::string terminal = "/dev/pts/3";int openterminal()
{int fd = open(terminal.c_str(), O_WRONLY);if (fd < 0){std::cerr << "open terminal err" << std::endl;return 1;}dup2(fd, 2); // 重定向到标准错误return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com