UDP套接字的通信(实现英汉互译/程序替换/多线程聊天室/Windows与Linux通信)

本文主要是介绍UDP套接字的通信(实现英汉互译/程序替换/多线程聊天室/Windows与Linux通信),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

实现英汉互译

思路

我们在客户端发英文,服务端做翻译工作,让翻译好的中文再次发给我们的客户端,然后打印出来。

服务端代码

翻译的操作

创建一个txt文件里面包含英汉互译的数据

dict.txt

banana:香蕉
apple:苹果
pig:猪
beef:牛肉
hello:你好
对txt中的数据进行操作
分割函数

将英汉通过冒号分开。

// 分割函数
static bool cutString(const string &target, string *s1, string *s2, const string &sep)
{// apple:苹果auto pos = target.find(sep);if (pos == string::npos){return false;}*s1 = target.substr(0, pos);*s2 = target.substr(pos + sep.size());return true;
}
将文件数据插入map里面
// 按行将文件里面的数据给插入到map里面
static void initDict()
{dict.clear();ifstream in(dictTxt, std::ios::binary);if (!in.is_open()){std::cerr << "open file " << dictTxt << " error" << endl;exit(OPEN_ERR);}string line;std::string key, value;while (getline(in, line)){// cout << line << endl;if (cutString(line, &key, &value, ":")){dict.insert(make_pair(key, value));}}in.close();cout << "load dict success" << endl;
}
重新加载文件

通过捕捉2号(ctrl c)信号来进行重新加载文件。

void reload(int signo)
{(void)signo;initDict();
}// ./udpClient server_ip server_port
int main(int argc, char *argv[])
{signal(2, reload); // 通过发2号信号来使dict.txt中的数据进行更新}

网络通信的操作

将翻译后的数据发送给客户端
void handlerMessage(int sockfd, string message, uint16_t clientport, string clientip)
{// 就可以对message进行特定的业务处理,而不关心message怎么来的 --- server通信和业务逻辑解耦!// 婴儿版的业务逻辑string response_message;auto iter = dict.find(message);if (iter == dict.end())response_message = "unknown";elseresponse_message = iter->second;// 开始返回struct sockaddr_in client;bzero(&client, sizeof(client));client.sin_family = AF_INET;client.sin_port = htons(clientport);client.sin_addr.s_addr = inet_addr(clientip.c_str());// 在服务端收到客户端数据的时候我们获取到了客户端的端口号和ip,因此回调到了这个函数中,// 此时我们就可以使用客户端的端口号和ip来给客户端发送翻译后的信息sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr *)&client, sizeof(client));
}

客户端代码

创建socket

void initClient()
{// 1. 创建socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd == -1){cerr << "socket error: " << errno << " : " << strerror(errno) << endl;exit(2);}cout << "socket success: " << _sockfd << endl;// 2. client要不要bind[不需要的]  , client要不要显示的bind,需不需要程序员自己bind? 不需要// 写服务器的一家公司,写客户端是无数家公司 -- 因此让OS自动形成端口进行bind! -- OS在什么时候,如何bind
}

数据处理

将用户输入的数据发送给服务端,并且接受服务端翻译后的数据并进行打印。

void run()
{struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(_serverip.c_str());server.sin_port = htons(_serverport);string message;char buffer[1024];while (!_quit){// fprintf(stderr, "Please Enter# ");// fflush(stderr);// fgets(buffer, sizeof(buffer), stdin);cout << "Please Enter# ";cin >> message;// buffer[strlen(buffer) - 1] = 0;// message = buffer;sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));char recv_buffer[1024];// 接受翻译后的信息struct sockaddr_in peer;socklen_t len = sizeof(peer);//ssize_t s = recvfrom(_sockfd, recv_buffer, sizeof(recv_buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (s > 0){// 读取数据buffer[s] = 0;}cout << "服务器翻译成# " << recv_buffer << endl;// recv_buffer[0] = 0;memset(recv_buffer, 0, sizeof(buffer)); // 清空缓冲区}
}

成果展示

程序替换

思路

主要使用popen接口同时实现管道+创建子进程+程序替换的功能。将我们客户端输入的命令信息发送给服务端。服务端将该命令经过popen接口让它执行命令运行出来的结果放在一个文件中,然后在将文件中的内容读取出来发送给客户端。

popen接口

#include <stdio.h>FILE *popen(const char *command,const char *type);  // 相当于pipe+fork+exec* int pclose(FILE *stream);

参数

const char *command

未来要执行的命令字符串: 

ls -a -l 等  可以将这些命令执行的结果返回到一个文件当中

const char *type

对文件的操作方式"r" "w" "a" 等

返回值

返回 nullptr 说明执行失败了

服务端代码

void execCommand(int sockfd, string cmd, uint16_t clientport, string clientip)
{// 1. com解析,ls -a -l// 2. 如果必要,可能需要fork,exec*if (cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("remdir") != string::npos){cerr << clientip << " : " << clientport << " 正在做一个非法的操作: " << cmd << endl;return;}string response;FILE *fp = popen(cmd.c_str(), "r");if (fp == nullptr)response = cmd + " exec failed";char line[1024];// 按行读取while (fgets(line, sizeof(line), fp)){response += line;}pclose(fp);// 开始返回struct sockaddr_in client;bzero(&client, sizeof(client));client.sin_family = AF_INET;client.sin_port = htons(clientport);client.sin_addr.s_addr = inet_addr(clientip.c_str());// 在服务端收到客户端数据的时候我们获取到了客户端的端口号和ip,因此回调到了这个函数中,// 此时我们就可以使用客户端的端口号和ip来给客户端发送翻译后的信息sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&client, sizeof(client));
}

成果展示 

多线程聊天室

思路

通过在客户端的角度创建多线程,一个线程进行发消息,一个线程进行读消息。这样我们就能让多个线程(用户)一起聊天。

让用户的id(ip + "-" + port)作为key值,User作为value值。来形成一个个unordered_map类型的容器。如果用户上线了就将该对象添加到unordered_map容器里面,下线就从unordered_map容器删除。

通过输入"online"来将该用户添加到unordered_map容器里面,意味上线。

通过输入"offline"来将该用户从unordered_map容器里面删除,意味下线。

我们服务端收到客户端发来的数据的时候通过isOnline函数来判断该用户是否在unordered_map容器里面,如果在就将该信息发送给客户端并且客户端接受后打印出来,如果不在直接打印"未上线"。

用户信息代码代码 onlineUser.hpp

#pragma once#include <iostream>
#include <string>
#include <unordered_map>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>using namespace std;class User
{
public:User(const string &ip, const uint16_t &port): _ip(ip), _port(port){}~User(){}string ip(){return _ip;}uint16_t port(){return _port;}private:string _ip;uint16_t _port;
};class OnlineUser
{
public:OnlineUser() {}~OnlineUser() {}void addUser(const string &ip, const uint16_t &port){string id = ip + "-" + to_string(port);users.insert(make_pair(id, User(ip, port)));}void delUser(const string &ip, const uint16_t &port){string id = ip + "-" + to_string(port);users.erase(id);}bool isOnline(const string &ip, const uint16_t &port){string id = ip + "-" + to_string(port);return users.find(id) == users.end() ? false : true;}void broadcastMessage(int sockfd, const string &ip, const uint16_t &port, const string &message){for (auto &user : users){// 开始返回struct sockaddr_in client;bzero(&client, sizeof(client));client.sin_family = AF_INET;client.sin_port = htons(user.second.port());client.sin_addr.s_addr = inet_addr(user.second.ip().c_str());// 将用户id也加入string s = ip + "-" + to_string(port) + "# ";s += message;sendto(sockfd, s.c_str(), s.size(), 0, (struct sockaddr *)&client, sizeof(client));}}private:unordered_map<string, User> users;
};

服务端代码

// demo 3
void routeMessage(int sockfd, string message, uint16_t clientport, string clientip)
{// 判断是否上线// 上线就将该用户添加到onlineuserif (message == "online")onlineuser.addUser(clientip, clientport);// 下线就将该用户在onlineuser中删除if ((message == "offline"))onlineuser.delUser(clientip, clientport);// 如果以下if为真 那么说明用户已经上线,因此需要将用户发的信息进行路由if (onlineuser.isOnline(clientip, clientport)){// 消息的路由onlineuser.broadcastMessage(sockfd, clientip, clientport, message);}else{// 开始返回struct sockaddr_in client;bzero(&client, sizeof(client));client.sin_family = AF_INET;client.sin_port = htons(clientport);client.sin_addr.s_addr = inet_addr(clientip.c_str());string response_message = "你还没有上线,请先上线,运行: online";sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr *)&client, sizeof(client));}

客户端代码(多线程)

static void *readMessage(void *args)
{int sockfd = *(static_cast<int *>(args));pthread_detach(pthread_self());while (true){char buffer_recv[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t s = recvfrom(sockfd, buffer_recv, sizeof(buffer_recv) - 1, 0, (struct sockaddr *)&peer, &len);if (s >= 0)buffer_recv[s] = 0;cout << buffer_recv << endl;}return nullptr;
}
void run()
{pthread_create(&_reader, nullptr, readMessage, (void *)&_sockfd);struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(_serverip.c_str());server.sin_port = htons(_serverport);string message;char buffer[1024];while (!_quit){fprintf(stderr, "Please Enter# ");fflush(stderr);fgets(buffer, sizeof(buffer), stdin);buffer[strlen(buffer) - 1] = 0;message = buffer;sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));}

成果展示

Windows与Linux通信

思路

让Linux当服务端,Windows当客户端。让Windows与Linux通信,主要用到了一些Windows系统的socket创建的接口和sendto等接口,这些接口与Linux是及其相似的不做过多赘述。

Windows代码

windows_client.cpp

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<cstring>
#include<string>
#include<WinSock2.h>#pragma comment(lib,"ws2_32.lib") // 将ws2_32.lib的库using namespace std;// 显示的定义并且初始化ip和port
uint16_t serverport = 8080;
string  serverip = "124.223.97.182";int main()
{WSAData wsd;//启动Winsock//查看库的版本是否正确if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0){cout << "WSAStartup Error = " << WSAGetLastError() << endl;return 0;}else  cout << "WSAStartup Success" << endl;//创建套接字const SOCKET csock = socket(AF_INET, SOCK_DGRAM, 0);if (csock == SOCKET_ERROR){cout << "sock Error = " << WSAGetLastError() << endl;return 1;}else  cout << "socket Success" << endl;//定义一个 struct sockaddr_in类型的结构体struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);// 主机转网络server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 主机转网络 字符串转整型#define NUM 1024char inbuffer[NUM];string line;while (true){//发送逻辑cout << "Please Enter# ";getline(cin, line);int n = sendto(csock, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server));if (n < 0){cerr << "sendto error!" << endl;break;}struct sockaddr_in peer;int peerlen = sizeof(peer);//收取数据inbuffer[0] = 0; // C风格的清空n = recvfrom(csock, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&peer, &peerlen);if (n > 0){/*inbuffer[n] = 0;cout << "serever 返回的消息是# " << inbuffer << endl;*/}else break;}closesocket(csock);// 释放资源WSACleanup();return 0;
}

Linux代码

makefile

cc=g++udpServer:udpServer.cc$(cc) -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f udpServer

udpServer.hpp

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <functional>#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>using namespace std;namespace Server
{enum{SOCKET_ERR = 1,BIND_ERR,OPEN_ERR};const int NUM = 1024;static const string defaultIp = "0.0.0.0";typedef function<void(int, string, uint16_t, string)> func_t;class udpServer{public:udpServer(const func_t &cb, const uint16_t &port, const string ip = defaultIp): _callback(cb), _serverport(port), _serverip(defaultIp), _sockfd(-1){// cout << "拷贝构造" << endl;}void initServer(){// 1. 套接字的创建_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd == -1){cerr << "socket error :" << errno << " : " << strerror(errno) << endl;exit(SOCKET_ERR);}cout << "socket success : " << _sockfd << endl;// 2. bindstruct sockaddr_in local;bzero(&local, sizeof(local)); // 初始化locallocal.sin_family = AF_INET;// 1. 主机转网络 点分十进制转intlocal.sin_addr.s_addr = inet_addr(_serverip.c_str());// local.sin_addr.s_addr = INADDR_ANY;local.sin_port = htons(_serverport);int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n == -1){cerr << "bind error: " << errno << " : " << strerror(errno) << endl;exit(BIND_ERR);}}void start(){char buffer[NUM];for (;;){struct sockaddr_in peer;socklen_t len = sizeof(peer);//ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (s > 0){// 读取数据buffer[s] = 0;uint16_t clientport = ntohs(peer.sin_port);string clientip = inet_ntoa(peer.sin_addr);string message = buffer;cout << clientip << " [" << clientport << "]# " << message << endl;// 回调 文件描述符和客户端的端口号和ip_callback(_sockfd,message,clientport,clientip);}}}~udpServer(){}private:int _sockfd;uint16_t _serverport;string _serverip;func_t _callback;};
} // udpServer

udpServer.cc

#include "udpServer.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>using namespace Server;
using namespace std;
static void Usage(string proc)
{cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}void handlerMessage(int sockfd, string message, uint16_t clientport, string clientip)
{string response_message = message;response_message += " [server echo]";// 开始返回struct sockaddr_in client;bzero(&client, sizeof(client));client.sin_family = AF_INET;client.sin_port = htons(clientport);client.sin_addr.s_addr = inet_addr(clientip.c_str());sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr *)&client, sizeof(client));
}// ./udpClient server_ip server_port
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(1);}uint16_t port = atoi(argv[1]); // 字符串转整数unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port));usvr->initServer();usvr->start();return 0;
}

成果展示

这篇关于UDP套接字的通信(实现英汉互译/程序替换/多线程聊天室/Windows与Linux通信)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/405440

相关文章

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

基于Python开发Windows屏幕控制工具

《基于Python开发Windows屏幕控制工具》在数字化办公时代,屏幕管理已成为提升工作效率和保护眼睛健康的重要环节,本文将分享一个基于Python和PySide6开发的Windows屏幕控制工具,... 目录概述功能亮点界面展示实现步骤详解1. 环境准备2. 亮度控制模块3. 息屏功能实现4. 息屏时间

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

基于Linux的ffmpeg python的关键帧抽取

《基于Linux的ffmpegpython的关键帧抽取》本文主要介绍了基于Linux的ffmpegpython的关键帧抽取,实现以按帧或时间间隔抽取关键帧,文中通过示例代码介绍的非常详细,对大家的学... 目录1.FFmpeg的环境配置1) 创建一个虚拟环境envjavascript2) ffmpeg-py

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

在Windows上使用qemu安装ubuntu24.04服务器的详细指南

《在Windows上使用qemu安装ubuntu24.04服务器的详细指南》本文介绍了在Windows上使用QEMU安装Ubuntu24.04的全流程:安装QEMU、准备ISO镜像、创建虚拟磁盘、配置... 目录1. 安装QEMU环境2. 准备Ubuntu 24.04镜像3. 启动QEMU安装Ubuntu4