『 Linux 』利用UDP套接字实现简单群聊
文章目录
- 服务端通过传入命令处理实现远程命令执行
- 使用Windows编辑UDP客户端实现Windows远程控制Linux
- 接收套接字的其他信息
- UDP套接字简单群聊服务端
- UDP套接字简单群聊客户端
- 运行测试及分离输入输出
- 参考代码
服务端通过传入命令处理实现远程命令执行

『 Linux 』利用UDP套接字简单进行网络通信 中实现了利用UDP套接字实现简单的网络通信(参考代码[gitee - udp process]);
具体思路是实现利用UDP套接字实现一个客户端和一个服务端,客户端向服务端发送数据,服务端进行数据的拼接并返回拼接后的数据,或者是传入一个回调函数,服务端通过调用回调函数调用popen接口创建子进程执行命令;
std::string HandlerCommand(const std::string& cmd) {// 打开管道,执行命令FILE* fp = popen(cmd.c_str(), "r");if (!fp) {perror("popen");return "error";}std::string ret;char buffer[4096];// 循环读取命令输出while (true) {char* res = fgets(buffer, sizeof(buffer), fp);if (res == nullptr) break; // 到达文件末尾,或出错ret += std::string(buffer); // 将命令输出追加到返回字符串中}// 关闭管道,并获取命令执行的返回值int status = pclose(fp);if (status == -1) {perror("pclose");return "error";}// 返回命令执行结果return ret;
}
可以创建一个子函数使用string::find判断命令中是否存在不安全的操作,如rm,sudo等;
bool isSave(const std::string& cmd) {std::vector<std::string> unsaves = {"rm", "while", "sudo","mv", "cp", "yum"};int pos = 0;for (auto& world : unsaves) {pos = cmd.find(world);if (pos != std::string::npos) {return false;}}return true;
}std::string HandlerCommand(const std::string& cmd) {// 打开管道,执行命令if (!isSave(cmd)) {return "the cmd unsave";}FILE* fp = popen(cmd.c_str(), "r");if (!fp) {perror("popen");return "error";}std::string ret;char buffer[4096];// 循环读取命令输出while (true) {char* res = fgets(buffer, sizeof(buffer), fp);if (res == nullptr) break; // 到达文件末尾,或出错ret += std::string(buffer); // 将命令输出追加到返回字符串中}// 关闭管道,并获取命令执行的返回值int status = pclose(fp);if (status == -1) {perror("pclose");return "error";}// 返回命令执行结果return ret;
}
测试结果为:

这里的服务端地址在测试时可以使用环回地址,即127.0.0.1或127.0.0.0;
-
环回地址
环回地址是一个特殊的
IP地址,用于主机自己与自己通信,不与任何物理网络接口相关联;通常用于进行
CS测试,即客户端与服务端之间的测试;在
IPv4中的环回地址通常为127.0.0.0/8网段;IPv6中的环回地址通常为::1;主机名
local host通常会被映射到环回地址,环回地址通常是安全的,因为数据不会离开主机;
本质上Xshell等软件就是通过类似的原理实现本地与远端服务器进行网络通信(所用协议不同);
使用Windows编辑UDP客户端实现Windows远程控制Linux

服务端已经实现,此处不考虑服务端;
#define _CRT_SECURE_NO_WARNINGS 1 // 使用VS编译器预防警告#include <stdio.h>
#include <tchar.h>
#include <iostream>
#include <WinSock2.h>
#include <Windows.h>
#include <WS2tcpip.h>
#include <string>#pragma comment(lib, "ws2_32.lib")enum ERR {SOCKETERR = 1,INETPTONERR
};// 封装客户端
class UdpClient {
public:UdpClient(const std::string&ip,UINT16 port):sockfd_(0),ip_(ip),port_(port),len_(0) // 初始化客户端的值{memset(&local_, 0, sizeof(local_)); // 初始化struct stockaddr结构体为0if (WSAStartup(MAKEWORD(2, 2), &wsd_)) {} // 取消警告 }void Run() {/*进行套接字的运行与数据的发送接收及管理*/Init(); // 进行套接字的初始化std::string message; // 用于存储需要发送给服务端的字符串数据char buffer[1024] = { 0 }; // 用于存储服务端返回给客户端的内容while (true) {std::cout << "Please Enter@ "; // 消息提示符std::getline(std::cin,message); // 从键盘中接收需要发送给服务端的数据int sd = sendto(sockfd_, message.c_str(), message.size(), 0, (sockaddr*)&local_, len_); // 调用sendto发送给服务端if (sd < 0) {std::cout << "sendto err " << std::endl;}struct sockaddr_in tmp; // 创建 sockaddr_in 作为服务端发来数据包的其他信息(IP,端口等)memset(&tmp,0, sizeof(tmp)); // 初始化结构体socklen_t len = sizeof(tmp); // 作为输出型参数接收服务端发来数据包的大小SSIZE_T n = recvfrom(sockfd_, buffer, 1023, 0, (struct sockaddr*)&tmp, &len); // 使用 recvfrom 接收来自服务端的信息if (n > 0) {buffer[n] = 0;std::cout << buffer << std::endl; // 当做字符串进行打印}}}~UdpClient() {WSACleanup();closesocket(sockfd_); // 关闭套接字文件描述符}
protected:void Init() {/*进行套接字的初始化工作*/// 设置 sockaddr_in 结构体local_.sin_family = AF_INET; // 设置网络传输为IPv4协议local_.sin_port = htons(port_); // 设置端口号 - 需要发送给服务端需要转网络字节序if (inet_pton(AF_INET, ip_.c_str(), &local_.sin_addr) != 1) {std::cerr << "Init - sin_addr erro" << std::endl;exit(INETPTONERR);}len_ = sizeof(local_); // 计算 sockaddr_in 结构体大小// 创建套接字sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 创建UDP套接字并存储套接字文件描述符if (sockfd_ < 0) {std::cerr << "soket fail" << std::endl;exit(ERR::SOCKETERR);}}private:SOCKET sockfd_;std::string ip_;UINT16 port_;struct sockaddr_in local_;WSADATA wsd_;socklen_t len_;
};int main() {std::string ip; UINT16 port;// 打印提示符信息并输入IP与端口std::cout << "Please Enter your IP@ ";std::cin >> ip;std::cout << "Please Enter your port@ ";std::cin >> port;// 实例化一个客户端实例并传入IP与端口UdpClient client(ip, port);// 调用客户端的运行(运行中自动进行初始化)client.Run();return 0;
}
这里封装了一个Windows版本的Client客户端;
-
头文件和预处理指令
包含了必要的头文件,如
WinSock2.h用于网络编程;#define _CRT_SECURE_NO_WARNINGS 1用于禁用某些Visual Studio的安全警告;#pragma comment(lib, "ws2_32.lib")链接Windows Socket网络库; -
错误枚举
enum ERR {SOCKETERR = 1,INETPTONERR };定义了两种错误类型,用于错误处理;
-
UdpClient类主要的客户端类,封装了
UDP客户端的功能;-
构造函数
UdpClient(const std::string&ip,UINT16 port):sockfd_(0),ip_(ip),port_(port),len_(0)用于初始化客户端,设置
IP地址和端口号,调用了WSAStartup初始化Winsock; -
Run方法void Run()客户端的主要运行方法,首先调用
Init函数进行初始化,然后进入一个无限循环,不断从用户获取输入并发送到服务器,并接收服务器的响应; -
析构函数
~UdpClient()清理资源,关闭套接字文件描述符,调用
WSACleanup; -
Init函数void Init()初始化套接字和地址结构;
设置
sockadddr_in结构并创建UDP套接字;客户端不需要显式
bind,当调用sendto将数据发送给服务端时将自动生成一个port端口bind当前IP; -
私有成员
private:SOCKET sockfd_; // 套接字文件描述符std::string ip_; // IPUINT16 port_; // 端口号struct sockaddr_in local_; // sockaddr_in 结构体WSADATA wsd_; // WSADATA 变量socklen_t len_; // sockaddr_in 结构体的大小套接字描述符,
IP地址,端口号,地址族结构体等; -
main函数从用户获取
IP地址和端口号;创建
UdpClient实例;调用
client.Run()开始运行客户端;
-
主要流程为用户输入服务器IP和端口并创建UdpClient对象;
调用Run函数:
- 初始化套接字(调用
Init); - 进入循环:
- 获取用户输入
- 发送到服务器
- 接收服务器响应并显示

由于协议分层,Windows与Linux不同平台可以使用同一套协议簇进行网络通信;
因为即使是不同平台下Socket API网络接口是一致的;
接收套接字的其他信息

当服务端接收到客户端所发的信息时这个数据包中存放的除了数据以外还包含着客户端的基本信息,如IP地址和端口号;
/* UdpServer.hpp */
#include "log.hpp" // 日志头文件#define BUF_SIZE 1024// 定义函数类型别名
using Comfunc_t = std::function<std::string(const std::string &)>;
using Echofunc_t = std::function<std::string(const std::string &,const std::string &, uint16_t)>;Log log_;// 错误码枚举
enum { SOCK_CREATE_FAIL = 1, SOCK_BIND_FAIL };class UdpServer {public:// 构造函数UdpServer(const uint16_t port = defaultport): sockfd_(0), port_(port), isrunning_(false) {}~UdpServer() {}// 初始化服务器void Init() {// 创建 UDP socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd_ < 0) {log_(FATAL, "socket create fail , the errornum : %d\n", sockfd_);exit(SOCK_CREATE_FAIL);}log_(INFO, "socket create sucess , sockfd : %d", sockfd_);// 绑定地址和端口struct sockaddr_in localsr;bzero(&localsr, sizeof(localsr));localsr.sin_family = AF_INET;localsr.sin_port = htons(port_);localsr.sin_addr.s_addr = INADDR_ANY;socklen_t locallen = sizeof(localsr);if (bind(sockfd_, (const struct sockaddr *)&localsr, locallen) < 0) {log_(FATAL, "socket bind fail, err string :%s", strerror(errno));exit(SOCK_BIND_FAIL);}log_(INFO, "socket bind sucess , sockfd : %d", sockfd_);}// 运行服务器void Run(Echofunc_t EchoHandler) {isrunning_ = true;char inbuf[BUF_SIZE] = {0};while (isrunning_) {struct sockaddr_in client;socklen_t len = sizeof(client);bzero(&client, sizeof(client));// 接收客户端消息size_t n = recvfrom(sockfd_, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr *)&client, &len);if (n < 0) {log_(WARNING, "recvfrom fail, err string :%s", strerror(errno));continue;}log_(INFO, "recvfrom sucess");uint16_t port = ntohs(client.sin_port); // 接收客户端数据并提取对应的IP和port将其序列化(网络字节序转主机字节序)std::string ip = inet_ntoa(client.sin_addr);inbuf[n] = 0;// 处理数据std::string info = inbuf;std::string echo_string = EchoHandler(inbuf, ip, port);std::cout << echo_string << std::endl;// 发送回复sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0,(const struct sockaddr *)&client, len);}}private:int sockfd_; // 套接字文件描述符uint16_t port_; // 端口号bool isrunning_; // 运行状态static const uint16_t defaultport; // 默认端口
};// 设置默认端口
const uint16_t UdpServer::defaultport = 8080;#endif
其中下面这段代码为接收到客户端所发的数据并提取对应的IP地址和端口号将其进行序列化,即网络字节序转主机字节序;
uint16_t port = ntohs(client.sin_port); // 接收客户端数据并提取对应的IP和port将其序列化(网络字节序转主机字节序)std::string ip = inet_ntoa(client.sin_addr);
UDP套接字简单群聊服务端

/* UdpServer.hpp */
#ifndef UDPSERVER_HPP
#define UDPSERVER_HPP#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>#include <cstring>
#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>#include "log.hpp"#define BUF_SIZE 1024// 定义回调函数类型,用于处理接收到的消息
using Echofunc_t = std::function<std::string(const std::string &,const std::string &, uint16_t)>;Log log_;// 错误枚举,用于标识不同的错误类型
enum { SOCK_CREATE_FAIL = 1, SOCK_BIND_FAIL };class UdpServer {public:// 构造函数,初始化服务器参数UdpServer(const uint16_t port = defaultport): sockfd_(0), port_(port), isrunning_(false) {}// 析构函数~UdpServer() {}// 初始化服务器,创建并绑定socketvoid Init() {// 创建UDP socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd_ < 0) {log_(FATAL, "socket create fail , the errornum : %d\n", sockfd_);exit(SOCK_CREATE_FAIL);}log_(INFO, "socket create sucess , sockfd : %d", sockfd_);// 设置服务器地址结构struct sockaddr_in localsr;bzero(&localsr, sizeof(localsr));localsr.sin_family = AF_INET;localsr.sin_port = htons(port_);localsr.sin_addr.s_addr = INADDR_ANY;socklen_t locallen = sizeof(localsr);// 绑定socket到指定地址和端口if (bind(sockfd_, (const struct sockaddr *)&localsr, locallen) < 0) {log_(FATAL, "socket bind fail, err string :%s", strerror(errno));exit(SOCK_BIND_FAIL);}log_(INFO, "socket bind sucess , sockfd : %d", sockfd_);}// 检查用户是否已存在,如果是新用户则添加到在线用户列表void CheckUsr(const struct sockaddr_in &client) {uint16_t port = ntohs(client.sin_port);std::string ip = inet_ntoa(client.sin_addr);// 检查用户是否首次登录auto it = online_user_.find(ip);if (it == online_user_.end()) {online_user_[ip] = client;std::cout << "The " << ip << " first login..." << std::endl;}}// 向所有在线用户广播消息void Broadcast(const std::string &info, const std::string &ip,uint16_t port) {for (const auto &usr : online_user_) {// 构造广播消息std::string massage ="[" + ip + ":" + std::to_string(port) + " echo]# " + info;socklen_t len = sizeof(usr.second);// 发送消息给每个在线用户sendto(sockfd_, massage.c_str(), massage.size(), 0,(struct sockaddr *)(&usr.second), len);}}// 运行服务器,处理接收到的消息void Run(Echofunc_t EchoHandler) {isrunning_ = true;char inbuf[BUF_SIZE] = {0};while (isrunning_) {// 接收客户端数据struct sockaddr_in client;socklen_t len = sizeof(client);bzero(&client, sizeof(client));size_t n = recvfrom(sockfd_, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr *)&client, &len);if (n < 0) {log_(WARNING, "recvfrom fail, err string :%s", strerror(errno));continue;}// 解析客户端信息uint16_t port = ntohs(client.sin_port);std::string ip = inet_ntoa(client.sin_addr);inbuf[n] = 0; // 确保字符串以null结尾// 检查并更新用户状态CheckUsr(client);std::string info = inbuf;// 广播接收到的消息Broadcast(info, ip, port);}}private:int sockfd_; // socket文件描述符uint16_t port_; // 服务器端口bool isrunning_; // 服务器运行状态标志static const uint16_t defaultport; // 默认端口std::unordered_map<std::string, struct sockaddr_in> online_user_; // 在线用户列表
};const uint16_t UdpServer::defaultport = 8080; // 设置默认端口为8080#endif
这段代码包含了必要的系统和标准库头文件;
-
构造函数
// 构造函数,初始化服务器参数UdpServer(const uint16_t port = defaultport): sockfd_(0), port_(port), isrunning_(false) {}用于初始化服务器的基本参数,包括套接字文件描述符,端口号,运行标识符;
-
Init函数void Init()-
创建
UDP socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0);创建一个
UDP socket套接字,AF_INET表示使用IPv4,SOCK_DGRAM表示使用UDP协议,0表示默认协议;如果创建失败记录错误并退出程序;
-
设置服务器地址结构
// 设置服务器地址结构struct sockaddr_in localsr;bzero(&localsr, sizeof(localsr));localsr.sin_family = AF_INET;localsr.sin_port = htons(port_);localsr.sin_addr.s_addr = INADDR_ANY;socklen_t locallen = sizeof(localsr);创建
sockadd_in结构体localsr并调用bzero清空结构体;设置地址族为
IPv4,设置端口并转网络字节序,同样的设置IP地址并转网络字节序; -
绑定
socketif (bind(sockfd_, (const struct sockaddr *)&localsr, locallen) < 0) {log_(FATAL, "socket bind fail, err string :%s", strerror(errno));exit(SOCK_BIND_FAIL);}log_(INFO, "socket bind sucess , sockfd : %d", sockfd_);将
socket绑定到指定的端口和地址,绑定成功或失败都会使用日志插件打印对应信息;
-
-
Run函数void Run(Echofunc_t EchoHandler);该函数用于运行服务端,其中
EchoHandler是一个回调函数,用于处理接收到的数据;-
初始化
isrunning_ = true;char inbuf[BUF_SIZE] = {0};将运行状态设置为
true,初始化inbuf数组用于接收数据; -
主循环
while (isrunning_){/* ... */}服务端会持续运行直至
isrunning_被设置为false; -
接收数据
// 接收客户端数据struct sockaddr_in client;socklen_t len = sizeof(client);bzero(&client, sizeof(client));size_t n = recvfrom(sockfd_, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr *)&client, &len);创建一个
sockaddr_in结构体用来存储客户端地址信息,并调用bzro清空客户端地址结构;socklen_t len设置地址结构的长度;使用
recvfrom函数接收数据,并返回接收到的字节数给size_t n; -
错误处理
if (n < 0) {log_(WARNING, "recvfrom fail, err string :%s", strerror(errno));continue;}如果
recvfrom返回负值说明调用失败,记录错误并进行下一次循环; -
处理接收到的数据
// 解析客户端信息uint16_t port = ntohs(client.sin_port);std::string ip = inet_ntoa(client.sin_addr);inbuf[n] = 0; // 确保字符串以null结尾解析客户端的信息,包括
IP,端口号等等;如果
n>0说明数据接收成功,该值即为接收的长度,将数组对应的位置赋值0(即\0)将该数组看成是字符串; -
检查更新用户状态
// 检查并更新用户状态CheckUsr(client);调用
CheckUsr函数检查用户状态并进行更新; -
广播消息
std::string info = inbuf;// 广播接收到的消息Broadcast(info, ip, port);将接收到的消息调用
Broadcast函数广播接收到的消息;
-
-
CheckUsr检查更新用户函数void CheckUsr(const struct sockaddr_in &client);参数
client表示传入一个地址结构,该地址结构保存客户端的信息;-
解析客户端信息
uint16_t port = ntohs(client.sin_port);std::string ip = inet_ntoa(client.sin_addr);解析客户端信息,包括
IP地址与端口号,将网络字节序转化为主机字节序; -
检查用户是否存在
auto it = online_user_.find(ip);if (it == online_user_.end()) {online_user_[ip] = client;std::cout << "The " << ip << " first login..." << std::endl;}该类中的
unordered_map<std::string, struct sockaddr_in> online_user_存放着用户的信息,检查该哈希表中是否存在该用户的信息,有则无行为,无则添加;
-
-
Broadcast广播消息函数该函数用于遍历哈希表,将消息广播回当前在线的所有用户;
-
遍历哈希表(在线用户)
for (const auto &usr : online_user_){ /* ... */ }遍历所有在线用户;
-
构造广播消息
// 构造广播消息std::string massage ="[" + ip + ":" + std::to_string(port) + " echo]# " + info;创建一个包含发送者
IP,端口号和原始消息的格式化字符串; -
发送消息
socklen_t len = sizeof(usr.second);// 发送消息给每个在线用户sendto(sockfd_, massage.c_str(), massage.size(), 0,(struct sockaddr *)(&usr.second), len);对每个在线的用户调用
sendto函数发送构造的消息,其中sockfd_是服务器的socket;massage.c_str()和massage.size()用于提供消息内容和长度;(struct sockaddr *)(&usr.second)为目标用户的地址;
-
/* Main.cc */
#include <iostream>
#include <vector>
#include "UdpServer.hpp"// 打印使用说明
void Usage(std::string proc) {std::cout << "\n\tUsage: " << proc << " port[1024+]\n" << std::endl;
}// 回声处理函数
std::string EchoHandler(const std::string& datastr, const std::string& clientip, uint16_t clientport) {std::string echo_string = "[" + clientip + ":" + std::to_string(clientport) + " echo]# " + datastr;return echo_string;
}int main(int argc, char* argv[]) {if (argc != 2) {Usage(argv[0]);exit(0);}// 创建并初始化UDP服务器std::unique_ptr<UdpServer> svr(new UdpServer(std::stoi(argv[1])));svr->Init();// 运行服务器svr->Run(EchoHandler);return 0;
}
-
EchoHandler函数std::string EchoHandler(const std::string& datastr, const std::string& clientip, uint16_t clientport) {std::string echo_string = "[" + clientip + ":" + std::to_string(clientport) + " echo]# " + datastr;return echo_string; }这是一个回声处理函数,用于接收客户端发送的数据,客户端
IP和端口号,然后构造一个包含这些信息的字符串并返回; -
main函数// 创建并初始化UDP服务器std::unique_ptr<UdpServer> svr(new UdpServer(std::stoi(argv[1])));svr->Init();// 运行服务器svr->Run(EchoHandler);主函数创建并初始化
UDP服务器随后运行;
UDP套接字简单群聊客户端

/* UdpClient.hpp */
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>#include <cstring>
#include <iostream>
#include <memory>
#include <string>// 线程数据结构,用于在线程间传递必要的信息
struct ThreadData {sockaddr_in local; // 服务器地址信息int sockfd; // 套接字文件描述符
};// 打印使用说明
void Usage(std::string proc) {std::cout << "\n\tUsage: " << proc << " serverip serverport\n" << std::endl;
}// 接收消息的线程函数
void* recv_message(void* data) {ThreadData* td = (ThreadData*)data;char buffer[1024] = {0}; // 接收缓冲区while (true) {struct sockaddr_in temp;bzero(&temp, sizeof(temp));socklen_t len = sizeof(temp);// 接收来自服务器的消息ssize_t n =recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if (n > 0) {buffer[n] = 0; // 确保字符串正确终止std::cout << buffer << std::endl; // 打印接收到的消息}}
}// 发送消息的线程函数
void* send_message(void* data) {ThreadData* td = (ThreadData*)data;std::string message;while (true) {std::cout << "Please Enter@";getline(std::cin, message); // 获取用户输入socklen_t len = sizeof(td->local);// 发送消息到服务器int sdebug = sendto(td->sockfd, message.c_str(), message.size(), 0,(struct sockaddr*)&td->local, len);if (sdebug < 0) {std::cout << "sendto fail, err: " << strerror(errno) << std::endl;}}
}int main(int argc, char* argv[]) {// 检查命令行参数if (argc != 3) {Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);ThreadData tdata;// 初始化服务器地址结构bzero(&tdata.local, sizeof(tdata.local));tdata.local.sin_family = AF_INET;tdata.local.sin_port = htons(serverport);tdata.local.sin_addr.s_addr = inet_addr(serverip.c_str());// 创建UDP套接字tdata.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (tdata.sockfd < 0) {std::cout << "socket fail" << std::endl;exit(-1);}// 创建接收和发送线程pthread_t recver, sender;pthread_create(&recver, nullptr, recv_message, &tdata);pthread_create(&sender, nullptr, send_message, &tdata);// 等待线程结束(实际上这里的线程不会结束)pthread_join(recver, nullptr);pthread_join(sender, nullptr);// 关闭套接字(实际上这行代码永远不会被执行)close(tdata.sockfd);return 0;
}
这个客户端使用了双线程,即一个线程用于接收数据,一个线程用于发送数据以避免getline函数阻塞线程导致的无法正确接收消息;
-
ThreadData结构体定义了再线程间共享的数据结构,包含了服务器的地址信息和套接字描述符;
// 线程数据结构,用于在线程间传递必要的信息 struct ThreadData {sockaddr_in local; // 服务器地址信息int sockfd; // 套接字文件描述符 }; -
recv_message函数该函数主要用于接收从服务端中转发的数据;
// 接收消息的线程函数 void* recv_message(void* data) {ThreadData* td = (ThreadData*)data;char buffer[1024] = {0}; // 接收缓冲区while (true) {struct sockaddr_in temp;bzero(&temp, sizeof(temp));socklen_t len = sizeof(temp);// 接收来自服务器的消息ssize_t n =recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if (n > 0) {buffer[n] = 0; // 确保字符串正确终止std::cout << buffer << std::endl; // 打印接收到的消息}} }创建一个
sockaddr_in结构体用于存储服务端的基本信息,而后调用recvfrom函数接收消息;接收消息后将消息末尾处添加
\0作字符串并进行打印; -
send_message函数该函数为发送消息的线程函数,即循环读取用户输入信息而后调用
sendto函数将消息发送到服务器;-
循环读取用户输入信息
while (true) {std::cout << "Please Enter@";getline(std::cin, message); // 获取用户输入socklen_t len = sizeof(td->local);// ...} -
调用
sendto函数发送消息int sdebug = sendto(td->sockfd, message.c_str(), message.size(), 0,(struct sockaddr*)&td->local, len);if (sdebug < 0) {std::cout << "sendto fail, err: " << strerror(errno) << std::endl;}调用函数后判断消息是否成功发出;
-
main函数main函数首先检查命令行参数判断是否需要调用Usage用户手册;初始化
ThreadData结构体用于存储服务器对应信息;创建
UDP套接字并创建两个线程,一个用于接收消息,一个用于发送消息;
-
运行测试及分离输入输出


运行测试结果显示UDP网络通信成功,但这里的输入输出混在在一起;
可以将服务端转发的数据重定向到另一个窗口以实现单终端输入单中端输出;
使用ls /dev/pts查看当前主机下多少Bash存在;
$ ls /dev/pts
0 1 2 3 ptmx
多个终端情况下使用echo "hello" > /dev/pts/[cmdnumber]检查哪个终端将接收到数据从而指定哪个终端作为输入哪个作为输出;
$ echo "hello world" > /dev/pts/1
hello world
根据对应的终端号使用open打开该文件并调用dup2重定向到对应的文件(终端)中实现输入输出分离;
std::string terminal = "/dev/pts/1";int OpenTerminal() {int fd = open(terminal.c_str(), O_WRONLY);if (fd < 0) {std::cerr << "open terminal error" << std::endl;return 1;}dup2(fd, 2);return 0;
}
由于该处dup2的文件描述符为2(标准错误流);
故在客户端中接收到服务端发出的数据应使用std::cerr进行打印;
void* recv_message(void* data) {// 接收缓冲区 ...while (true) {// 接收来自服务器的消息 ...std::cerr << buffer << std::endl; // 使用标准错误cerr打印接收到的消息}}
}
结果如下:

参考代码

[Gitee - 半介莽夫 / Dio夹心小面包]
相关文章:
『 Linux 』利用UDP套接字实现简单群聊
文章目录 服务端通过传入命令处理实现远程命令执行使用Windows编辑UDP客户端实现Windows远程控制Linux接收套接字的其他信息UDP套接字简单群聊服务端UDP套接字简单群聊客户端运行测试及分离输入输出 参考代码 服务端通过传入命令处理实现远程命令执行 『 Linux 』利用UDP套接字…...
【数据结构与算法 | 图篇】最小生成树之Kruskal(克鲁斯卡尔)算法
1. 前言 克鲁斯卡尔算法(Kruskals algorithm)是一种用于寻找加权图的最小生成树(Minimum Spanning Tree, MST)的经典算法。这种算法是由约瑟夫克鲁斯卡尔(Joseph Kruskal)提出的,并且适用于所有…...
了解常用的代码检查工具
在软件开发领域,代码检查工具是确保代码质量、提高开发效率、促进团队协作的重要工具。这些工具通过自动化分析代码,帮助开发者发现潜在的错误、漏洞、代码异味等问题,并提供修复建议或重构方案。以下是一些常用的代码检查工具,它…...
BUUCTF PWN wp--warmup_csaw_2016
第一步 先checksec一下(没有启用NX保护、PIE、完整的RELRO和栈保护,还有具有RWX权限的内存段。) 分析一下这个文件的保护机制: Arch: amd64-64-little 这表示该可执行文件是为64位的AMD64架构编译的,并且使用的是小…...
dockerfile搭建部署LNMP
目录 实验 架构: 实验步骤: nginx部分 mysql部分 php部分 实验 实验:用dockerfile搭建LNMP论坛 架构: 一台docker虚拟机 docker部署nginx 1.22 指定ip地址172.111.0.10 docker部署mysql 8.0.30 指定ip地址…...
Rust : 数据分析利器polars用法
Polars虽牛刀小试,就显博大精深,在数据分析上,未来有重要一席。 下面主要列举一些常见用法。 一、toml 需要说明的是,在Rust中,不少的功能都需要对应features引入设置,这些需要特别注意,否则编译…...
Qt第一课
作者前言 🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂 🎂 作者介绍: 🎂🎂 🎂 🎉🎉🎉…...
论“graphics.h”库,easyx
前言 别人十步我则百,别人百步我则千 你是否有这样的想法,把图片到入进c里,亦或者能实时根据你发出的信息而做出回应的程序,graphics.h这个库完美满足了你的需求,那今天作者就给大家介绍一下这个库,并做一些…...
如何在寂静中用电脑找回失踪的手机?远程控制了解一下
经过一番努力,我终于成功地将孩子哄睡了。夜深人静,好不容易有了一点自己的时间,就想刷手机放松放松,顺便看看有没有重要信息。但刚才专心哄孩子去了,一时就忘记哄孩子之前,顺手把手机放哪里去了。 但找过手…...
Android 实现动态换行显示的 TextView 列表
在开发 Android 应用程序时,我们经常需要在标题栏中显示多个 TextView,而这些 TextView 的内容长度可能不一致。如果一行内容过长,我们希望它们能自动换行;如果一行占不满屏幕宽度,则保持在一行内。本文将带我们一步步…...
Golang | Leetcode Golang题解之第352题将数据流变为多个不相交区间
题目: 题解: type SummaryRanges struct {*redblacktree.Tree }func Constructor() SummaryRanges {return SummaryRanges{redblacktree.NewWithIntComparator()} }func (ranges *SummaryRanges) AddNum(val int) {// 找到 l0 最大的且满足 l0 < val…...
Ubuntu安装mysql 以及远程连接mysql Windows—适合初学者的讲解(详细)
目录 准备工作 一.Xshell中操作 (1)在虚拟机中安装mysql (2)连接Windows数据库 (3)进入linux数据库。 (4)修改mysql配置文件 二.Windows命令窗口操作 需要软件虚拟机,Xsh…...
【数学建模】MATLAB快速入门
文章目录 1. MATLAB界面与基本操作1.1 MATLAB的基本操作 2. MATLAB字符串和文本2.1 string变量2.2 char变量 3. MATLAB的矩阵运算 1. MATLAB界面与基本操作 初始界面: 刚开始的界面只要一个命令行窗口,为了使编辑界面出现我们需要新建一个文件ÿ…...
【ubuntu24.04】k8s 部署5:配置calico 镜像拉取
kubeadm - 中国大陆版建议:初始化Kubeadm –apiserver-advertise-address 这个地址是本地用于和其他节点通信的IP地址 –pod-network-cidr pod network 地址空间 sudo kubeadm init --image-repository registry.aliyuncs.com/google_containers --apiserver-advertise-add…...
Elasticsearch 的数据备份与恢复
在生产环境中,数据的安全性和可靠性至关重要。对于基于 Elasticsearch 的系统而言,数据备份与恢复是确保数据完整性、应对灾难恢复的关键操作。本文将详细介绍 Elasticsearch 中如何进行数据备份与恢复,帮助管理员构建一个可靠的数据保护策略…...
Ps:首选项 - 暂存盘
Ps菜单:编辑/首选项 Edit/Preferences 快捷键:Ctrl K Photoshop 首选项中的“暂存盘” Scratch Disks选项卡通过合理配置和管理暂存盘,可以显著提高 Photoshop 的运行性能,特别是在处理复杂的设计项目或大型图像文件时。选择合适…...
力扣217题详解:存在重复元素的多种解法与复杂度分析
在本篇文章中,我们将详细解读力扣第217题“存在重复元素”。通过学习本篇文章,读者将掌握如何使用多种方法来解决这一问题,并了解相关的复杂度分析和模拟面试问答。每种方法都将配以详细的解释,以便于理解。 问题描述 力扣第217…...
享元模式:轻量级对象共享,高效利用内存
享元模式(Flyweight Pattern)是一种结构型设计模式,用于减少对象数量、降低内存消耗和提高系统性能。它通过共享相似对象的内部状态,减少重复创建的对象。下面将具体介绍享元模式的各个方面: 组成 抽象享元࿰…...
人工智能-自然语言处理(NLP)
人工智能-自然语言处理(NLP) 1. NLP的基础理论1.1 语言模型(Language Models)1.1.1 N-gram模型1.1.2 词嵌入(Word Embeddings)1.1.2.1 词袋模型(Bag of Words, BoW)1.1.2.2 TF-IDF&a…...
基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(三)---创建自定义激光雷达Componet组件
前言 本系列教程旨在使用UE5配置一个具备激光雷达深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2和UE5仿真的通讯,达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
