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

news/2024/5/18 12:35:53 标签: udp, 网络协议, 网络

实现英汉互译

思路

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

服务端代码

翻译的操作

创建一个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";
    else
        response_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)
{
    // 判断是否上线
    // 上线就将该用户添加到onlineuser
    if (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));
    }

成果展示


http://www.niftyadmin.cn/n/478711.html

相关文章

快速匹配两张表格中的数据

最近发现Excel中的VLOOKUP函数用来匹配数据非常方便&#xff0c;记录一下参考的教程&#xff0c;方便大家以后查找。 &#xff08;1&#xff09;使用VLOOKUP匹配数据的具体步骤 添加链接描述 &#xff08;2&#xff09;匹配结果中出现#N/A的原因及解决办法 添加链接描述

leecode-搜索二维矩阵

题目 题目 分析 不能全if &#xff0c;得写else if 啊 因为j–会修改j&#xff01;&#xff01;&#xff01; 代码 class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int nmatrix.size();//n行int mmatrix[0].size(…

Springboot上传图片和回显示图片

本次演示的是直接使用Springboot上传到服务器而不是七牛云等oss,springboot对于前端传输的文件数据类型格式的封装为MultipartFile,前端上传的图片是被存在服务端的缓存区的,当controller处理的时候,缓存区就被清空,所以需要转存,使用transferTo Api 前端采用elementui 直接上传…

uni-app 微信小程序 onReachBottom 不生效

问题描述&#xff1a; uni-app 微信小程序&#xff0c;页面滑到底部&#xff0c;onReachBottom 没有生效 解决&#xff1a; 最外层容器设置 min-height: 101vh 代码&#xff1a; pages.json 配置 {"path": "","style": { "navigationBar…

Element ui 取消点击空白处弹框关闭的效果

目录 属性&#xff1a; 描述 属性&#xff1a; element组件库的Dialog对话框默认是可以通过点击 modal 关闭 Dialog&#xff0c;即点击空白处弹框可关闭。 描述 在 el-dialog中close-on-click-modal含义是&#xff1a;点击空白处是否关闭&#xff0c;默认true&#xff1b;如…

为什么你总学不会编程?到底差什么?

为什么你总学不会编程&#xff1f;到底差什么&#xff1f; 笔者看到太多太多的人花上钱、耗费一两年的时间都学不会编程&#xff0c;甚至一门C语言都反反复复学不完、学不会&#xff0c;游走在大门边缘&#xff0c;总是入不了门&#xff0c;到底是什么因为什么&#xff1f; 因…

王爽《汇编语言》期末考试题库(附答案)

单选题 第一章 PC机的最小信息单位是&#xff08; &#xff09;。 A. bit B. 字节 C. 字长 D. 字 A PC机的最小信息单位是比特(bit)&#xff0c;常用来表示一位二进制数字&#xff08;0或1&#xff09;。字节(byte)是计算机中常用的数据单位&#xff0c;一个字…

数学建模—层次分析法

数模算法1&#xff1a;层次分析法 适用问题&#xff1a;评价类问题&#xff0c;决策&#xff08;方案选择类&#xff09; ** input**&#xff1a;判断矩阵&#xff08;A&#xff09; AHP处理 output:权重&#xff08;得分&#xff09;向量 1.整体导图 2.算法步骤及代码 2.1算…