【Network programming】UDP simple implementation of translation software and network chat room

1. Introduction

In the previous chapter [Network Programming] the demo version of the UDP network server realized the sending and receiving of data between the client and the server. In the previous chapter, we directly asked the server to print out the received data. insert image description here
But the server does not just receive the data, it also needs to process the task .
So we can set a callback function on the server side:

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

Used to process received information.

2. Realization of translation software

2.1 Loading the dictionary

To realize that the client sends a word and receives the translation result of the server.
Then first you need to load English and Chinese into unordered_mapthe dictionary.

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

Load data into a dictionary from a file:
insert image description here

const std::string path = "./Dict.txt";

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

// 分割字符串
static bool cutstring(std::string s, std::string *key, std::string *val)
{
    
    
    size_t pos = s.find(":");
    if(pos == std::string::npos)
    {
    
    
        return false;
    }
    *key = s.substr(0, pos);
    *val = s.substr(pos + 1);
    return true;
}

static void InitDict()
{
    
    
    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 Process data and pass it to the client

Next, the data is processed and the results of the processing are fed back to the client.

Handling the data is relatively simple:

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

Next, we need to ressend it to the client. After the previous learning, sendtowe need to build a structure to indicate who to send it to.
When the structure is built and used, sendtoit is found that there must be a file descriptor, so it must be _sockfdpassed in when calling the callback function.

// 回调方法
void handler(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;
    }
    struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
}

2.3 The client gets the result

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

2.4 Results

Client:
insert image description here
Server:
insert image description here

2.5 Executing the naming function

Now we can make a small change, instead of letting the server do the translation, but to process the task, for example, if we send pwd, it will return the path of the server at this time.
The other logic here does not need to be changed, only the callback function needs to be modified.

Introduce a command line parsing interface :
popen

#include <stdio.h>

FILE *popen(const char *command, const char *type);

int pclose(FILE *stream);

Let’s first recall the previous handwritten suggestion shell [linux] process control details , we first need to split the command line, extract each option, and then fork to create a child process and then replace the program.
And this function contains all the above functions , and there is one more function to create a pipeline.
Parameter introduction:

command: The string passed in, such as ls -a -l
type: how to open the file (r/w/a)
For example, if it is opened in r read-only mode now, the result will be directly extracted from the pipeline.

void execcommand(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);
    struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
}

Client:
insert image description here
Server:
insert image description here

3. Realization of network chat room

What's the chat room away from? The message we send will be forwarded by the server, so that every online client can see the sent message, thus realizing group chat.

Each client sends a onlinemessage indicating that it is online, and the server will send all the data to the online client.

So first of all, we need to manage all users.

3.1 Manage users

For each user we use IP and port to identify uniqueness.

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

For each user we can use a hash table to manage:

class OnlineUsers
{
    
    
public:
    void adduser(const std::string& ip, const uint16_t& port)
    {
    
    
        std::string id = ip + "@" + std::to_string(port);
        users.insert({
    
    id, User(ip, port)});
    }

    void deluser(const std::string& ip, const uint16_t& port)
    {
    
    
        std::string id = ip + "@" + std::to_string(port);
        users.erase(id);
    }

    bool isonline(const std::string& ip, const uint16_t& port)
    {
    
    
        std::string id = ip + "@" + std::to_string(port);
        return users.find(id) != users.end();
    }
private:
    std::unordered_map<std::string, User> users;
};

In the callback function, if the received message is online, add the user to the hash table. If it is offline, delete it from the hash table.

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

3.2 Send message

When we want to send a message, we actually send a message to all users in the hash table sendto. Before this, it is necessary to determine whether it is online :

void groupmsg(int _sockfd, std::string ip, uint16_t port, std::string msg)
{
    
    
    if(msg == "online") onlinemap.adduser(ip, port);
    if(!onlinemap.isonline(ip, port))// 不在线
    {
    
    
        struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
        return;
    }
    // 广播消息
}

Now to broadcast a message, add OnlineUsersa member function to broadcast a message in the class.
Its parameters should include _sockfd, msg, but also who sent it ( ip, port).

void broadmsg(int _sockfd, const std::string& msg, std::string ip, uint16_t port)
{
    
    
    for(auto& e : users)
    {
    
    
        struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
    }
}

3.3 Multi-thread processing

Because the client cannot immediately receive the message and print it out (blocking stays at receiving the message), in order to solve this problem we can use multi-threading , one thread is dedicated to receiving messages, and one thread is dedicated to sending messages.

Then we can let the main thread be responsible for sending messages, and the child thread is responsible for receiving messages.

static void* 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, (struct sockaddr*)&tmp, &tmplen);
        if(n > 0) buf[n] = 0;
        std::cout << buf << "\n";
    }
}

void start()
{
    
    
    pthread_create(&_get, nullptr, getmsg, (void*)&_sockfd);
    struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
    }
}

3.4 Results

Server: Client
insert image description here
:
insert image description here
Because there is only one terminal here, the printing is confusing, but you can see the phenomenon. If you want to optimize, you can redirect the output data to the pipeline file, and then open a terminal to read the pipeline, so that You can send and get two windows.

4. Source code

//UDPClient.hpp
#pragma once 
#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>

class UDPClient
{
    
    
public:
    UDPClient(const std::string& serverip, const uint16_t& port)
        : _serverip(serverip)
        , _serverport(port)
        , _sockfd(-1)
    {
    
    }

    void InitClient()
    {
    
    
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd == -1)
        {
    
    
            std::cerr << "socket error" << errno << " : " << strerror(errno) << std::endl;
            exit(1);
        }
    }

    static void* 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, (struct sockaddr*)&tmp, &tmplen);
            if(n > 0) buf[n] = 0;
            std::cout << buf << "\n";
        }
    }

    void start()
    {
    
    
        pthread_create(&_get, nullptr, getmsg, (void*)&_sockfd);
        struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
        }
    }
private:
    uint16_t _serverport;
    std::string _serverip;
    int _sockfd;
    pthread_t _get;
};

// UDPClient.cc
#include "UDPClient.hpp"
#include <memory>

int main(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(new UDPClient(ip, port));
    ptr->InitClient();
    ptr->start();
    return 0;
}

// UDPServer.hpp
#pragma once 
#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>

static const std::string defaultip = "0.0.0.0";// 默认IP

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

class UDPServer
{
    
    
public:
    UDPServer(const func_t& func, const uint16_t& port, const std::string ip = defaultip)
        : _port(port)
        , _ip(ip)
        , _sockfd(-1)
        , _func(func)
    {
    
    }

    void InitServer()
    {
    
    
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd == -1)
        {
    
    
            std::cerr << "socket error" << errno << " : " << strerror(errno) << std::endl;
            exit(1);
        }
        // 绑定IP与port
        struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
        assert(n != -1);
    }

    void start()
    {
    
    
        char buf[1024];
        while(1)
        {
    
    
            struct sockaddr_in peer;
            socklen_t len = sizeof peer;
            ssize_t s = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&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;

// 分割字符串
static bool cutstring(std::string s, std::string *key, std::string *val)
{
    
    
    size_t pos = s.find(":");
    if(pos == std::string::npos)
    {
    
    
        return false;
    }
    *key = s.substr(0, pos);
    *val = s.substr(pos + 1);
    return true;
}

static void InitDict()
{
    
    
    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";
}

// 回调方法
void handler(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;
    }
    struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
}

void execcommand(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);
    struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
}

OnlineUsers onlinemap;

void groupmsg(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))// 不在线
    {
    
    
        struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
        return;
    }
    // 广播消息
    onlinemap.broadmsg(_sockfd, msg, ip, port);
}


int main(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(new UDPServer(groupmsg, port));
    ptr->InitServer();
    ptr->start();
    return 0;
}

// users.hpp
#pragma once

#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>

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

class OnlineUsers
{
    
    
public:
    void adduser(const std::string& ip, const uint16_t& port)
    {
    
    
        std::string id = ip + "@" + std::to_string(port);
        users.insert({
    
    id, User(ip, port)});
    }

    void deluser(const std::string& ip, const uint16_t& port)
    {
    
    
        std::string id = ip + "@" + std::to_string(port);
        users.erase(id);
    }

    bool isonline(const std::string& ip, const uint16_t& port)
    {
    
    
        std::string id = ip + "@" + std::to_string(port);
        return users.find(id) != users.end();
    }

    void broadmsg(int _sockfd, const std::string& msg, std::string ip, uint16_t port)
    {
    
    
        for(auto& e : users)
        {
    
    
            struct sockaddr_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, (struct sockaddr*)&si, sizeof si);
        }
    }
private:
    std::unordered_map<std::string, User> users;
};


Guess you like

Origin blog.csdn.net/qq_66314292/article/details/130668674