【ネットワーク】UDP 適用シナリオ

翻訳機能

私たちが作成した UDP サーバーは、データの受信を完了するだけでなく、処理タスクも実行する必要があります。

サーバー udpServer.hpp にコールバック関数 _callback を設定でき、特定のロジックは udpServer.cc を通じて外部から渡されます。

コードは次のようになります。

 		void start()
        {
            // 服务器的本质其实就是一个死循环
            char buffer[gnum];
            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;
                    string clientip = inet_ntoa(peer.sin_addr); 
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;

                    cout << clientip <<"[" << clientport << "]# " << message << endl;//把收到的消息打印出来
                    _callback(_sockfd, clientip, clientport, message);
                }
            }
        }

翻訳機能: クライアントは単語を入力し、それをサーバーに送信し、サーバーから翻訳結果を受け取ります。

まず辞書の dict を提供します。英語と中国語に対応するファイル dicTxt をunordered_map辞書にロードします。このときの unowned_map は辞書の内容を保存します

dict.txt: 外部ファイルは自分で入力および補足できます。ここにあるのはサンプル テスト コードです。

apple:苹果
world:世界
hello:你好
goodman:你是一个好人
const std::string dictTxt="./dict.txt";//文件
unordered_map<string, string> dict;//字典

次に、辞書を初期化します。ファイルを開き、ファイル内の各行をキーと値、つまり英語と中国語に分割し、結果を辞書に挿入します。つまり、結果を辞書に入れます。コードは次のようになります。次のように:

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;
}

static void initDict()
{
    ifstream in(dictTxt, std::ios::binary);
    if(!in.is_open())
    {
        cerr << "open file " << dictTxt << " error" << endl;
        exit(OPEN_ERR);
    }
    string line;
    std::string key, value;
    while(getline(in, line))//读取文件
    {
        if(cutString(line, &key, &value, ":"))
        {
            dict.insert(make_pair(key, value));
        }
    }

    in.close();

    cout << "load dict success" << endl;
}

udpServer.cc の関数 handlerMessage を通じてデータを処理し、処理結果をクライアントにフィードバックします。

void handlerMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    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());

    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr*)&client, sizeof(client));
}

クライアント udpClient.hpp はメッセージ cin を入力し、sendto をサーバーに送信します。その後、サーバー udpServer.hpp はコールバック関数を呼び出してメッセージを翻訳します。変換が完了すると、最終結果が sendto でクライアントに送信されます。クライアント udpClient.hpp は、recvfrom 翻訳後の結果を受信し、最後に翻訳結果を出力します。

 		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 cmdline[1024];
            while (!_quit)
            {
                cout<<"Please Enter#";
                cin>>message;
                sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));

                char buffer[1024];
                struct sockaddr_in temp;
                socklen_t temp_len = sizeof(temp);
                size_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&temp_len);
                if(n>0) buffer[n] = 0;
                cout<<"服务器的翻译结果# "<<buffer<<endl;   
            }
        }

以下にテスト実行結果を示します。

//udpServer.cc
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    initDict();
    std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port));
}
//udpClient.cc
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    unique_ptr<udpClient> ucli(new udpClient(serverip, serverport));
    ucli->initClient();
    ucli->run();
    return 0;
}

まずサーバーを起動します。

画像-20230530191842224

起動クライアントで情報を入力し、サーバーにメッセージを送信し、サーバーがメッセージを受信して​​印刷し、翻訳結果をクライアントに返し、クライアントが翻訳結果を印刷する、上記の全体プロセスです。

画像-20230530191942072

画像-20230530192002279

コマンドライン解析

Popen インターフェースを借用: (pipe+fork、exec* と同等の関数)

#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

commandls -a -l: 渡される文字列 ( ;など) type: ファイル (r/w/a) を開く方法。関数 execCommand を通じて呼び出すことができます。この関数を実現するには、udpServer.cc ファイル内の受信関数を変更するだけで済みます。

void execCommand(int sockfd, string clientip, uint16_t clientport, string cmd)
{
    //1. cmd解析,ls -a -l
    //先禁止一下非法操作,防止有人搞破坏
    if(cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("rmdir") != 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());
    sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&client, sizeof(client));
}

テスト走行:

//udpServer.cc
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<udpServer> usvr(new udpServer(execCommand, port));
}

画像-20230530194723712

画像-20230530194732874

インターネットチャットルーム

ユーザーを管理する必要があり、ユーザーごとに IP とポートを使用して一意性を識別するため、多くのユーザーをハッシュ テーブルを使用して統合管理できます。

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());
            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;
};

コールバック関数では、受信したメッセージがオンラインの場合、ユーザーをハッシュ テーブルに追加します。オフラインの場合はハッシュテーブルから削除します

if (message == "online") onlineuser.addUser(clientip, clientport);
if (message == "offline") onlineuser.delUser(clientip, clientport);
OnlineUser onlineuser;
void routeMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    if (message == "online") onlineuser.addUser(clientip, clientport);
    if (message == "offline") onlineuser.delUser(clientip, clientport);
    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 = "你还没有上线,请先上线,运行: online";

        sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&client, sizeof(client));
    }
}

マルチスレッド: クライアント udpClient.hpp はメッセージをすぐに受信して出力できません。この問題を解決するには、マルチスレッドを使用できます。1 つのスレッドはメッセージの受信専用で、もう 1 つのスレッドはメッセージの送信専用です。メインスレッドはメッセージの送信を担当し、サブスレッドはメッセージの受信を担当します。

static void *readMessage(void *args)
        {
            int sockfd = *(static_cast<int *>(args));
            pthread_detach(pthread_self());
            while (true)
            {
                char buffer[1024];
                struct sockaddr_in temp;
                socklen_t temp_len = sizeof(temp);
                size_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &temp_len);
                if (n >= 0)
                    buffer[n] = 0;
                cout << buffer << 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 cmdline[1024];
            while (!_quit)
            {
                fprintf(stderr, "Enter# ");
                fflush(stderr);
                fgets(cmdline, sizeof(cmdline), stdin);
                cmdline[strlen(cmdline)-1] = 0;
                message = cmdline;
                sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
            }
        }

画像-20230530213639848

画像-20230530213650726

UDPのWindowsとLinux

UDP の実装は、さまざまなプラットフォームで対話できます。ここでは、サーバーとして Linux を使用し、接続するクライアントとして Windows を使用します。

Windows 側のコード:

#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)


#include <iostream>
#include <string>
#include <cstring>
#include <WinSock2.h>

#pragma comment(lib,"ws2_32.lib")

using namespace std;
uint16_t serverport = 8080;
string serverip = "8.134.152.121";

int main()
{
	WSAData wsd;
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		cout << "WSAStartup Error = " << WSAGetLastError() << endl;
		return 0;
	}
	else
	{
		cout << "WSAStartup Success" << endl;
	}

	SOCKET csock = socket(AF_INET, SOCK_DGRAM, 0);
	if (csock == SOCKET_ERROR)
	{
		cout << "socket Error = " << WSAGetLastError() << endl;
		return 1;
	}
	else
	{
		cout << "socket success" << endl;
	}
	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 1024
	char 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;
		n = recvfrom(csock, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&peer, &peerlen);
		if (n > 0)
		{
			inbuffer[n] = 0;
			cout << "server 返回的消息是#" << inbuffer << endl;
		}
		else break;
	}
	closesocket(csock);
	WSACleanup();
	return 0;
}

Linux コード:

pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

namespace Server
{
    using namespace std;
    static const string defaultIp = "0.0.0.0";
    static const int gnum = 1024;
    enum  {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,OPEN_ERR};

    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),_port(port),_ip(ip),_sockfd(-1)
        {}

        void initServer()
        {
            _sockfd = socket(AF_INET,SOCK_DGRAM,0);
            if(_sockfd == -1)
            {
                cerr<<"sdocket error: "<<errno<<" : "<<strerror(errno)<<endl;
                exit(SOCKET_ERR);
            }
            cout<<"socket success: "<<" : "<<_sockfd<<endl;

            struct sockaddr_in local;
            bzero(&local,sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = inet_addr(_ip.c_str());

            int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
            if(n == -1)
            {
                cerr<<"bind errpr: "<<errno<<" : "<<strerror(errno)<<endl;
                exit(BIND_ERR);
            }
        }

        void start()
        {
            char buffer[gnum];
            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;
                    string clientip = inet_ntoa(peer.sin_addr);
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;

                    cout<<clientip<<"["<<clientport<<"]#"<< message<<endl;

                    _callback(_sockfd,clientip,clientport,message);
                }
            }
        }

        ~udpServer()
        {
            
        }

    private:
        int _sockfd;
        uint16_t _port;
        string _ip;
        func_t _callback;
    };
}
//udpServer.cc
#include <iostream>
#include <memory>
#include "udpServer.hpp"
using namespace std;
using namespace Server;

static void Usage(string proc)
{
    cout<<"\nUsage:\n\t"<<proc<<" locla_port\n\n";
}

void handlerMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
    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));
}

int main(int argc,char*argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage,port));
    usvr->initServer();
    usvr->start();
    return 0;
}

おすすめ

転載: blog.csdn.net/weixin_60478154/article/details/130958990