[ネットワーク] UDPネットワークサーバー簡易シミュレーション実装

[ネットワーク] UDPネットワークサーバー簡易シミュレーション実装


UDPのカプセル化 :

UDP ネットワーク サーバー シミュレーションの実装: 主にコンパイル用の Makefile ファイルに分割されています

UDP客户端: udpClient.cc (クライアント呼び出し)、udpClient.hpp (クライアント実装)

UDP服务端: udpServer.cc (サーバー呼び出し)、udpServer.hpp (サーバー実装)

メイクファイル

メイクファイルを作成します。

cc=g++ などの変数は makefile で定義できます。

画像-20230505222243055

サーバー udpServer

udpサーバー.cc

クライアントが呼び出すロジック コード: udpServer のオブジェクトを構築し、それを初期化し、起動します。呼び出しロジックは次のとおりです。

#include "udpServer.hpp"
#include <memory>

using namespace std;
using namespace Server;

static void Usage(string  proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"  local_port\n\n";
}
//  ./udpServer  port
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(port));

    usvr->initServer();
    usvr->start();
    return 0;
}

udpServer.hpp

クライアントの実装コード ロジック: 初期化インターフェイスと起動インターフェイスを外部に提供します。

サーバーとして: 独自のサービス ポート番号 uint16_t _port が必要であり、ネットワーク サーバーには対応する文字列 _ip アドレス、ファイル記述子 _sockfd が必要です: さまざまなデータ通信を実行し、クラス内で読み取りおよび書き込み操作を実行します。

IP アドレスのタイプの場合: 文字列タイプはユーザー層のパラメーターとしてのみ渡されます。これは必要ありません。インターフェイス変換を呼び出すだけです。

画像-20230505232656063

初期化

UDP サーバーの初期化方法: 2 つの手順を完了します: 1. ソケットソケットを作成します2. ポート番号 port と ip をバインドします

1. ネットワーク通信にソケットを使用して作成する場合は、ソケットを作成します。

NAME
       socket - create an endpoint for communication

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);

パラメータデータ ドメイン: ドメイン、将来のソケットはネットワーク通信またはローカル通信用で、主に次の 2 つのタイプを使用します:
AF_UNIX、AF_LOCAL ローカル通信 unix(7)
AF_INET IPv4 インターネット プロトコル ip(7)
パラメータ タイプ: 提供されるソケット サービスのタイプ、SOCK_STREAM: ストリーミング サービス TCP ポリシー、SOCK_DGRAM: データグラム サービス、UDP ポリシーなど

パラメータ プロトコル: デフォルトは 0 で、最初の 2 つのタイプによって決定できます。

戻り値: 失敗した場合は -1、成功した場合はファイル記述子

2. ポート番号と ip:bind をバインドする

NAME
       bind - bind a name to a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

パラメータ sockfd: 通信用のファイル記述子、つまりソケット呼び出しの戻り値

パラメータ addr: struct sockaddr_in を使用して転送を強制します

パラメータ addrlen: 構造体の長さ

戻り値: 成功した場合は 0 を返し、失敗した場合は -1 を返します。

sockaddr_inデータを入力して渡す構造体を定義します。

画像

3 番目のパラメーターは sockaddr_in 構造体の内部です。

struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;			/* Port number.  */
    struct in_addr sin_addr;		/* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
			   __SOCKADDR_COMMON_SIZE -
			   sizeof (in_port_t) -
			   sizeof (struct in_addr)];
  };

__SOCKADDR_COMMON の定義:

typedef unsigned short int sa_family_t;
#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

##文字列を結合すること、つまり、受け取った sin_ と family を結合して sin_family を形成することです

構造を作成した後、まずデータをクリア (初期化) する必要があります。memset を使用できます。また、システムはインターフェイスも提供します

#include <strings.h>
void bzero(void *s, size_t n);

inet_addr: 1. 文字列を整数に変換します; 2. 次に、整数を対応するネットワーク シーケンスに変換します

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
//const char*cp:点分十进制风格的IP地址

コードの実装:

	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.绑定端口号port,ip
            struct sockaddr_in local;//定义了一个变量
            //结构体清空
            bzero(&local,sizeof(local));
            local.sin_family = AF_INET;//协议家族
            local.sin_port=htons(_port);//给别人发消息,port和ip也要发给对方,需要大小端转换
            local.sin_addr.s_addr = inet_addr(_ip.c_str());//1.ip转成整数 2.整数要转成网络序列:htonl();
            int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
            if(n==-1)
            {
                cerr<<"bind error: "<<errno<<" : "<<strerror(errno)<<endl;
                exit(BIND_ERR);
            }
            //UDP Server 的预备工作完成
        }

起動

サーバーの本質は無限ループであり、無限ループから抜け出せないプロセスが常駐メモリプロセスです。OSの本質は無限ループ

クライアントによって送信されたメッセージの場合、サーバーはメッセージに対して何らかの処理を行ってからクライアントに送信する場合があるため、最初にデータのrecvfromを読み取る必要があります。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

sockfd: 特定のソケット、buf: 特定のバッファーへの読み取り、len: どのくらいの長さ

flags: 読み取り方法、デフォルトは 0、読み取りをブロックします。

src_addr: 受信したメッセージに加えて、送信者、入力パラメータと出力パラメータを把握し、対応するメッセージの内容をどのクライアントから返すかを知る必要があります。 len: サイズはどれくらいですか

-1 を返すと失敗を意味し、成功するとバイト数を返します。

同時に、送信者を知る必要があるため、sockaddr_in 構造体を通じて知ることができます。

        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);
                //1.数据是什么?2.谁发的
                if(s>0)
                {
                    buffer[s] = 0;
                    string clientip = inet_ntoa(peer.sin_addr);//1.网络序列转化成本地序列2.4字节整数IP转化成点分十进制
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;
                    cout<<clientip<<"["<<clientport<<"]# "<<message<<endl;
                }

            }
        }

テスト

ここに画像の説明を挿入

ローカル ループバック: サーバー コードのテスト

./udpServer 127.0.0.1 8080

ネットワークのステータスを表示するには、次のコマンドを使用できますnetstat

-a: すべての接続のソケットを表示;
-e: ネットワークのその他の関連情報を表示;
-i: ネットワーク インターフェース情報フォームを表示;
-l: 監視対象サーバーのソケットを表示;
-n: IP アドレスを直接使用 (番号)、ドメインネームサーバーを経由せずに、
-p: Socket を使用しているプログラム識別コードとプログラム名を表示します。
-t: TCP 伝送プロトコルの接続状態を表示します。
-u: ソケットの接続状態を表示します。 UDP 送信プロトコル。

画像-20230506134553093

ここで、クラウド サーバーの IP アドレスをバインドすると、次のようになります。

./udpServer 43.143.177.75 8080

画像-20230506134837052

クラウド サーバーは仮想化サーバーであり、パブリック IP を直接バインドすることはできませんが、内部 IP (ifconfig) をバインドできます。仮想マシンまたは独立した実際の Linux 環境の場合は、IP をバインドできます。クラウド サーバーは他のユーザーからアクセスできます: 実際、Web サーバーの IP を指定することはお勧めできません。つまり、IP を明示的にバインドしないでください。サーバー IP は複数存在する可能性があります。特定の IP が 1 つだけバインドされている場合は、最終データは別の IP によって送信される可能性があります。アクセス ポート番号にはアクセスできないため、実サーバー IP は通常、INADDR_ANY (すべて 0、任意のアドレス) を使用して任意のアドレス バインドを表します。

画像-20230506140814619

クライアント udpClient

udpClient.cc

クライアントの呼び出し方法: ./udpClient server_ip server_port、クライアントがサーバーに接続したい場合は、相手の IP (パブリック ネットワーク IP) を知っている必要があり、呼び出しロジックは次のとおりです。

#include "udpClient.hpp"
#include <memory>

using namespace Client;
static void Usage(string  proc)
{
    cout<<"\nUsage:\n\t"<<proc<<" server_ip server_port\n\n";
}
//./udpCleint server_ip server_port
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;
}

udpClient.hpp

クライアントとして: 初期化インターフェイスと開始実行インターフェイスを提供します。

初期化

初期化インターフェイスの場合: サーバーにはソケットが必要で、クライアントにもソケットが必要です。

  • クライアントはバインドを表示する必要はありません

サーバーがバインドするとき、最も重要なことは IP をバインドすることではなく、ポートをバインドすることです。将来ポートを指定するには、クライアントがサーバーに対して明示的にポートをバインドする必要があり、任意に変更することはできません。

クライアントにはポートが必要ですが、それは重要ではありません。独自のポート番号がある限り、明示的にバインドする必要はありません。サーバーは 1 つの会社によって作成され、クライアントは無数の会社によって作成されます。OSバインド用のポートを自動的に形成します

コード

       void initClient()
        {
            //客户端创建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
        }

起動

実行インターフェイスの場合: データを送信する必要があり、データを送信するにはインターフェイス sendto が必要です。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

sockfd: どのソケット、buf: バッファ、len: 長さ、flags: 0、データがあれば送信、データがなければブロック

dest_addr: この構造体も送信先の struct sockaddr_in から強制的に転送され、相手の IP とポートが埋め込まれます。

addrlen: ポインタなし、入力パラメータ

コード

	    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;
            while(!_quit)
            {
                cout<<"Please Enter# ";
                cin>>message;

                //发送给远端服务器
                sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
            }
        }

最後に、実行してテストします (ここでは同じホスト間のテストですが、別のマシンの場合は、パラメーターを渡すときにパブリック ネットワーク IP を渡す必要があります)。

コード全体

//udpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
#include <functional>
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};

    class udpServer
    {
    public:
        udpServer(const uint16_t&port,const string&ip = defaultIP)
        :_port(port),_ip(ip),_sockfd(-1)
        {}
        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.绑定端口号port,ip
            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());
            local.sin_addr.s_addr = htons(INADDR_ANY);
            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[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);//1.网络序列转化成本地序列2.4字节整数IP转化成点分十进制
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;
                    cout<<clientip<<"["<<clientport<<"]# "<<message<<endl;
                }
            }
        }
    private:
        uint16_t _port;//端口号
        string _ip;//ip地址
        int _sockfd;//文件描述符
    };
}

//udpServer.cc
#include "udpServer.hpp"
#include <memory>
using namespace std;
using namespace Server;
static void Usage(string  proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"  local_port\n\n";
}
//  ./udpServer  port
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(port));
    usvr->initServer();
    usvr->start();
    return 0;
}


//udpClient.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
namespace Client
{
    using namespace std;
    class udpClient
    {

    public:
        udpClient(const string&serverip,const uint16_t &serverport)
        :_serverip(serverip),_serverport(serverport),_sockfd(-1),_quit(false)
        {}
        void initClient()
        {
            //客户端创建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;
        }
        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;
            while(!_quit)
            {
                cout<<"Please Enter# ";
                cin>>message;
                sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
            }
        }
    private:
        int _sockfd;
        string _serverip;
        uint16_t _serverport;
        bool _quit;
    };
}

//udpClient.cc
#include "udpClient.hpp"
#include <memory>
using namespace Client;
static void Usage(string  proc)
{
    cout<<"\nUsage:\n\t"<<proc<<" server_ip server_port\n\n";
}
//./udpCleint server_ip server_port
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;
}

おすすめ

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