【Linuxサーバー】複数人チャットグループの実装 ---- コード添付

UDPビルドサーバー

予備知識

UDPプロトコルを理解する

ここでは UDP (User Datagram Protocol) についても直感的に理解できますが、これについては後ほど詳しく説明します。

  • トランスポート層プロトコル
  • 接続がありません
  • 信頼性の低い伝送
  • データグラム指向
ネットワークバイトオーダー

メモリ内のマルチバイト データは、メモリ アドレスに応じてビッグ エンディアンとリトル エンディアンに分割されることはすでに知られていますが、ディスク ファイル内のマルチバイト データも、メモリ
のオフセットストリームもビッグ エンディアンとリトル エンディアンに分けられますが、ネットワーク データ ストリームのアドレスはどのように定義すればよいでしょうか?

  • 送信ホストは通常​​、送信バッファ内のデータをメモリ アドレスの下位から上位への順序で送信します。
  • 受信ホストは、ネットワークから受信したバイトを受信バッファに順番に、またメモリ アドレスの下位から上位の順序で格納します。
  • したがって、ネットワーク データ フローのアドレスは次のように指定する必要があります。最初に送信されるデータは低いアドレスを持ち、後で送信されるデータは高いアドレスを持ちます。
  • TCP/IP プロトコルでは、ネットワーク データ フローがビッグ エンディアンのバイト順、つまり下位アドレスと上位バイトである必要があると規定しています
  • ホストがビッグエンド マシンであるかリトルエンド マシンであるかに関係なく、データは TCP/IP で指定されたネットワーク バイト オーダーに従って送受信されます。
  • 現在の送信ホストがリトル エンディアンの場合は、最初にデータをビッグ エンディアンに変換する必要があります。それ以外の場合は、無視して直接送信します。
   #include <arpa/inet.h>
   uint32_t htonl(uint32_t hostlong);

   uint16_t htons(uint16_t hostshort);

   uint32_t ntohl(uint32_t netlong);

   uint16_t ntohs(uint16_t netshort);
  • これらの関数名は覚えやすく、h はホスト (ローカル)、n はネットワーク (ネットワーク)、l は 32 ビットの長整数、s は 16 ビットの短整数を表します
  • たとえば、htonl は、IP アドレスの変換と送信の準備など、32 ビット長の整数をホスト バイト オーダーからネットワーク バイト オーダーに変換することを意味します。
  • ホストがリトル エンディアンの場合、これらの関数はパラメータをビッグ エンディアンに変換してから戻ります。
  • ホストがビッグエンディアンの場合、これらの関数は変換を実行せず、パラメータを変更せずに返します。
ソケットプログラミングインターフェース

`//ソケット ファイル記述子を作成します (TCP/UDP、クライアント + サーバー) intソケット(int ドメイン、int タイプ、int プロトコル);`

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);*

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

sockaddr 構造体
  • Pv4 と IPv6 のアドレス形式は netinet/in.h で定義されています。IPv4 アドレスは、16 ビットのアドレス タイプ、16 ビットのポート番号、および 32 ビットの IP アドレスを含む sockaddr_in 構造体で表されます。1

  • IPv4 と IPv6 のアドレスの種類は、それぞれ定数 AF_INET と AF_INET6 として定義されているため、ある sockaddr 構造体の先頭アドレスさえ取得できれば、それがどのような型の sockaddr 構造体であるか、またその中のアドレスを知る必要はありません。構造は、アドレス タイプ フィールドに基づいて決定できます。

  • ソケット API は struct sockaddr * タイプで表すことができ、使用する場合は強制的に sockaddr_in に変換する必要があります。この利点は、IPv4、IPv6、および UNIX ドメイン ソケットのさまざまなタイプを受け取ることができるプログラムの汎用性です。パラメータとしての sockaddr 構造体ポインタ。

    sockaddr構造体、sockaddr_in構造体、sockaddr_un構造体があります

sockaddr_in 構造体は、ネットワーク内の通信に使用されます。

sockaddr_un 構造体は、ローカルのプロセス間通信に使用されます。

知っておくべきことは、sockaddr 構造体、sockaddr_in 構造体、および sockaddr_un 構造体がポリモーフィズムを構成するということです。

sockaddr 構造体は基本クラス、sockaddr_in 構造体および sockaddr_un 構造体は派生クラスです。

ここで理解する必要があるのは、sockaddr_in 構造だけです。

ここに画像の説明を挿入します

サーバーの構築

サーバーの初期化

1. ソケットインターフェイスを作成し、ネットワークファイルを開きます

2. サーバーにIPとポートを指定します。

3.bind は、sockaddr ソケットフィールドをネットワークファイル記述子にバインドします。

コード:

void Init()
    {
    
    
        //1. 创建socket接口,打开网络文件
        _sock=socket(AF_INET,SOCK_DGRAM,0);
        if(_sock==-1)
        {
    
    
            std::cerr<<"socket error"<<strerror(errno)<<std::endl;
            exit(SOCKET_ERR);
        }
        std::cout<<"creat socket success:"<<_sock<<std::endl;

        //2.给服务器指明IP 和 PORT
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        
        local.sin_family=AF_INET;
        //端口号和IP地址都需要通过网络发送给目标,所以需要转换成网络序列
        local.sin_port=htons(_port);
        //字符串风格的IP需要转换成4字节int   ,1.1.1.1 -》》int

        local.sin_addr.s_addr=INADDR_ANY;//char* 类型的 c_str();
        
        //此时的 local 还只是一个临时变量
        // bind 将 addr套接字字段和 网络文件描述符 进行绑定
       if(bind(_sock,(sockaddr*)&local,sizeof(local))==-1)
       {
    
    
        std::cerr<<"bind error"<<strerror(errno)<<std::endl;
        exit(BIND_ERR);
       }
       std::cout<<"bind success"<<std::endl;
    }

この時点で、サーバーの初期化は成功しました。次のステップはサーバーを起動することです。

サーバーを起動します

クライアントからメッセージを受信し、処理してクライアントに返す必要があります。

これは、サーバーには次の 2 つの最も基本的な機能が必要であることを意味します。

ネットワーク メッセージの受信とネットワーク経由でのメッ​​セージの送信

  • 受信したメッセージを保存するバッファを定義する
  • また、メッセージの送信者を知り、顧客の IP とポートを保存するための sockaddr_in 構造体を定義する必要もあります。
  • 最後にクライアントにメッセージを返します

コード:

 void Start()
    {
    
    
        char buffer[1024];
        while(true)
        {
    
    
            struct sockaddr_in client;
            socklen_t len=sizeof(client);//client是一个接收型参数,存储了给服务器发送消息的客户端的IP+端口号
            
            int n=recvfrom(_sock,buffer,sizeof(buffer)-1,0,(sockaddr*)&client,&len);
            if(n>0) buffer[n]='\0';
            else continue;

            std::string clientIp=inet_ntoa(client.sin_addr);
            int clientport=ntohs(client.sin_port);
            std::cout<<clientIp<<'-'<<clientport<<" "<<"client echo#"<<buffer<<std::endl;

            sendto(_sock,buffer,strlen(buffer),0,(sockaddr*)&client,len);
        }
    }

サーバー全体の簡単な書き込みが完了しました。

コード:

udp_server.hpp

#pragma once 
#include<sys/socket.h>
#include<sys/types.h>
#include<iostream>
#include<cstring>
#include<errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include"err.hpp"



class UdpServer{
    
    
public:

public:
    static const u_int16_t DEFAULT_PORT=8080;
    UdpServer(uint16_t port=DEFAULT_PORT):_port(port)
    {
    
    
        std::cout<<"PORT:"<<_port<<std::endl;
    }

    void Init()
    {
    
    
        //1. 创建socket接口,打开网络文件
        _sock=socket(AF_INET,SOCK_DGRAM,0);
        if(_sock==-1)
        {
    
    
            std::cerr<<"socket error"<<strerror(errno)<<std::endl;
            exit(SOCKET_ERR);
        }
        std::cout<<"creat socket success:"<<_sock<<std::endl;

        //2.给服务器指明IP 和 PORT
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        
        local.sin_family=AF_INET;
        //端口号和IP地址都需要通过网络发送给目标,所以需要转换成网络序列
        local.sin_port=htons(_port);
        //字符串风格的IP需要转换成4字节int   ,1.1.1.1 -》》int

        local.sin_addr.s_addr=INADDR_ANY;//char* 类型的 c_str();
        
        //此时的 local 还只是一个临时变量
        // bind 将 addr套接字字段和 网络文件描述符 进行绑定
       if(bind(_sock,(sockaddr*)&local,sizeof(local))==-1)
       {
    
    
        std::cerr<<"bind error"<<strerror(errno)<<std::endl;
        exit(BIND_ERR);
       }
       std::cout<<"bind success"<<std::endl;
    }

    void Start()
    {
    
    
        char buffer[1024];
        while(true)
        {
    
    
            struct sockaddr_in client;
            socklen_t len=sizeof(client);//client是一个接收型参数,存储了给服务器发送消息的客户端的IP+端口号
            
            int n=recvfrom(_sock,buffer,sizeof(buffer)-1,0,(sockaddr*)&client,&len);
            if(n>0) buffer[n]='\0';
            else continue;

            std::string clientIp=inet_ntoa(client.sin_addr);
            int clientport=ntohs(client.sin_port);
            std::cout<<clientIp<<'-'<<clientport<<" "<<"client echo#"<<buffer<<std::endl;

            sendto(_sock,buffer,strlen(buffer),0,(sockaddr*)&client,len);
        }
    }

private:
    int _sock;
    uint16_t _port;

    //std::string _ip;//  云服务器,或者一款服务器,一般不要指明某一个确定的IP, 让我们的udpserver在启动的时候,bind本主机上的任意IP
};

udp_server.cc

#include"udp_server.hpp"
#include<iostream>
#include<memory>

using namespace std;

static void usage(string prc)
{
    
    
    cout<<"Usage\n\t"<<prc<<"port\n"<<endl;
}

int main(int argc,char* argv[])
{
    
    
    if(argc!=2)
    {
    
    
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port=atoi(argv[1]);
    unique_ptr<UdpServer> us(new UdpServer(port));
    us->Init();
    us->Start();
    return 0;
}
簡単なクライアント実装

次にクライアントを作成しますが、クライアントの作成は基本的にサーバーのポートに基づいて行われます。

この段階では、サーバーにメッセージを送信し、サーバーからメッセージを受信するだけで済みます。

プロセス:

  • ソケットを作成する
  • クライアントにメッセージを送信したい場合は、その IP とポートを知る必要があるため、サーバー情報が含まれた sockaddr_in 構造体が必要です。
  • あとはメッセージを送受信するだけです

ただし、クライアントを作成するときに注意する必要があることがいくつかあります。

1. サーバーのポート番号と IP アドレスは自分でバインドする必要がありますが、クライアントはそれを必要としますか?
回答: クライアントもバインドする必要がありますが、手動でバインドする必要はなく、OS が自動的にバインドします。
理由: クライアントが競合を開始しないように、クライアントのポートは OS によってランダムに割り当てられる必要があります (例: 当社のモバイル端末には多数のソフトウェア (クライアント) が存在します。自分でバインドすると、一部のサービスのポート番号が変更される可能性があります)占有されているため開始できません。)

2. サーバー自体をバインドする必要があるのはなぜですか?
理由:
1. サーバーのポートは自由に変更できない、周知のポートなので自由に変更できない
2. 同一企業のポート番号を統一、標準化する必要がある。

コード:
udp_client.cc

#include<sys/socket.h>
#include<sys/types.h>
#include<iostream>
#include<cstring>
#include<errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include"err.hpp"
using namespace std;


//127.0.0.1(本地回环) 表示的是当前主机,用于进行本地通讯或者测试
static void Usage(string prc)
{
    
    
    cout<<"Usage\t\n"<<prc<<" serverip serverport\n"<<endl;
}

int main(int argc,char* argv[])//./server 目标IP 目标PORT
{
    
    
    if(argc!=3)
    {
    
    
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

   int sock=socket(AF_INET,SOCK_DGRAM,0);
   if(sock<0)
   {
    
    
    cerr<<"socket fail"<<strerror(errno)<<endl;
    exit(SOCKET_ERR);
   }


    struct sockaddr_in server;//目标服务器的IP+PORT
    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());
   
   while(true)
   {
    
    
        char buffer[1024];
        cout<<"请输入#";
        cin>>buffer;

     //1. client 这里要不要bind呢?要的!socket通信的本质[clientip:clientport, serverip:serverport]
    // 2.client不需要自己bind,也不要自己bind,操作系统自动给我们进行bind -- 为什么?client的port要随机让OS分配防止client出现启动冲突(我们的手机终端上有很多的软件(客户端),如果自己bind,会可能导致某些服务的端口号被占用,无法启动)
    // 3.server 为什么要自己bind?1. server的端口不能随意改变,众所周知且不能随意改变的 2. 同一家公司的port号需要统一规范化
        sendto(sock,buffer,strlen(buffer),0,(sockaddr*)&server,sizeof(server));

        char Rebuffer[1024];
        sockaddr_in tmp;
        socklen_t len=sizeof(tmp);
       int n=recvfrom(sock,Rebuffer,sizeof(Rebuffer)-1,0,(sockaddr*)&tmp,&len);
        if(n>0) Rebuffer[n]='\0';
        std::cout<<serverip<<'-'<<serverport<<" "<<"server echo#"<<Rebuffer<<std::endl;
   }
    return 0;
}

エラーコード:
err.hppを処理する必要があります。

#pragma once

enum ERR{
    
    
    SOCKET_ERR=1,
    BIND_ERR,
    USAGE_ERR
};

サーバーには、echoメッセージを処理せずに単にメッセージを返す単純なサーバーを実装しました。

効果を実感
ここに画像の説明を挿入します

グループチャットシステムを構築する

事前に構築・処理したサーバーをもとに、簡単なグループチャット機能を実現できます

つまり、メッセージをサーバーに送信すると、サーバーはそのメッセージをグループ チャットの全員に転送します。

したがって、次の変更を加える必要があります。

  1. 顧客メッセージを保存するにはコンテナが必要で、接続されているすべての顧客は sockaddr_in 構造に保存されます。
  2. 送信されたメッセージを保存するためにバッファーも必要です。
  3. 同時に、異なるクライアントが同じバッファにメッセージを送信すると、マルチスレッドの同時実行の問題が発生し、ロックが必要になることに注意してください。
  4. また、メッセージの送受信時に単一のプロセスがブロックされるため、サーバーはマルチスレッドである必要があります。

コンポーネント:
RingQueue.hpp

#pragma once

#include<pthread.h>
#include<semaphore.h>
#include<vector>

const int DEFAULT_CAP=10; 
template<class T>
class RingQueue{
    
    
public:
    
    RingQueue(int num=DEFAULT_CAP):_cap(num),_rq(num),_consume_step(0),_produce_step(0)
    {
    
    
        sem_init(&_consume_sem,0,0);
        sem_init(&_produce_sem,0,_cap);
    }
    ~RingQueue()
    {
    
    
        sem_destroy(&_consume_sem);
        sem_destroy(&_produce_sem);
    }
    void lock(pthread_mutex_t& m)
    {
    
    
        pthread_mutex_lock(&m);
    }
    void unlock(pthread_mutex_t& m)
    {
    
    
        pthread_mutex_unlock(&m);
    }

    void P(sem_t& s)
    {
    
    
        sem_wait(&s);
    }
    void V(sem_t& s)
    {
    
    
        sem_post(&s);
    }
    void push(const T& in)
    {
    
    
        P(_produce_sem);

        lock(_produce_mutex);
        _rq[_produce_step++]=in;
        _produce_step%=_cap;
        unlock(_produce_mutex);

        V(_consume_sem);
        
    }
    void pop(T* out)
    {
    
    
        P(_consume_sem);

        lock(_consume_mutex);
        *out=_rq[_consume_step++];
        _consume_step%=_cap;
        unlock(_consume_mutex);

        V(_produce_sem);
    }

private:
    std::vector<T> _rq;
    int _cap;
    //消费者和生产者之间的同步关系通过信号量进行调节
    sem_t _consume_sem;
    sem_t _produce_sem;
    //当多线程运行时,生产者和生产者,消费锁者和消费者之间的互斥关系需要通过锁来维持
    pthread_mutex_t _consume_mutex;
    pthread_mutex_t _produce_mutex;
    int _consume_step;
    int _produce_step;
};

スレッド.hpp

#pragma once

#include <pthread.h>
#include <iostream>
#include <string>
#include <string.h>
#include <functional>

using namespace std;

string to_hex(uint64_t n)
{
    
    
    char arr[32];
    snprintf(arr, sizeof(arr), "0x%x", n);
    return arr;
}
class Thread
{
    
    
public:
    // typedef void(*func_t)();
    using func_t = std::function<void()>;
    typedef enum
    {
    
    
        NEW = 0,
        RUNNING,
        EXISTED
    } ThreadStatus;

    Thread(int num, func_t func) : _tid(0), _func(func)
    {
    
    
        char name[108];
        snprintf(name, sizeof(name), "thread-%d", num);
        _name = name;
        _status = NEW;
    }
    static void *runHelper(void *args)
    {
    
    
        Thread *ts = (Thread *)args;
        ts->_func();
        return nullptr;
    }

    void run()
    {
    
    
        int n = pthread_create(&_tid, nullptr, runHelper, this);
        if (n != 0)
        {
    
    
            exit(1);
        }
        _status = RUNNING;
    }

    int get_status()
    {
    
    
        return _status;
    }
    void join()
    {
    
    
        int n = pthread_join(_tid, nullptr);
        if (n != 0)
            exit(2);
        cout << _name << " "
             << "has joined" << endl;
        _status = EXISTED;
    }
    const string &get_name()
    {
    
    
        return _name;
    }
    string get_id()
    {
    
    
        return to_hex(_tid);
    }
    // pthread_t get_id()
    // {
    
    
    //     return _tid;
    // }

    ~Thread()
    {
    
    
    }

private:
    pthread_t _tid;
    string _name;
    ThreadStatus _status;
    func_t _func;
    // void* _args;//函数调用时需要传的参数
};

err.hpp

#pragma once

enum ERR{
    
    
    SOCKET_ERR=1,
    BIND_ERR,
    USAGE_ERR
};

コード:
udp_server.hpp

#pragma once
#include <sys/socket.h>
#include <sys/types.h>
#include <iostream>
#include <cstring>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>
#include <pthread.h>
#include "RingQueue.hpp"
#include "LockGuard.hpp"
#include "Thread.hpp"
#include "err.hpp"

using func_t = std::function<std::string(std::string)>;

class UdpServer
{
    
    
public:
public:
    static const u_int16_t DEFAULT_PORT = 8080;
    UdpServer(uint16_t port = DEFAULT_PORT) : _port(port)
    {
    
    
        std::cout << "PORT:" << _port << std::endl;
        pthread_mutex_init(&_lock, nullptr);

        // p=new Thread(1,);
        p = new Thread(1, std::bind(&UdpServer::Receive, this));
        c = new Thread(1, std::bind(&UdpServer::send, this));
    }

    void Start()
    {
    
    
        // 1. 创建socket接口,打开网络文件
        _sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sock == -1)
        {
    
    
            std::cerr << "socket error" << strerror(errno) << std::endl;
            exit(SOCKET_ERR);
        }
        std::cout << "creat socket success:" << _sock << std::endl;

        // 2.给服务器指明IP 和 PORT
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));

        local.sin_family = AF_INET;
        // 端口号和IP地址都需要通过网络发送给目标,所以需要转换成网络序列
        local.sin_port = htons(_port);
        // 字符串风格的IP需要转换成4字节int   ,1.1.1.1 -》》int

        local.sin_addr.s_addr = INADDR_ANY; // char* 类型的 c_str();

        // 此时的 local 还只是一个临时变量
        //  bind 将 addr套接字字段和 网络文件描述符 进行绑定
        if (bind(_sock, (sockaddr *)&local, sizeof(local)) == -1)
        {
    
    
            std::cerr << "bind error" << strerror(errno) << std::endl;
            exit(BIND_ERR);
        }
        std::cout << "bind success" << std::endl;

        p->run();
        c->run();
    }

    void Add_user(std::string ip, int port, struct sockaddr_in user)
    {
    
    
        std::string name = ip + " + " + to_string(port);
        LockGuard lockguard(&_lock);
        _online[name] = user;
    }

    void Receive()
    {
    
    
        char buffer[1024];
        while (true)
        {
    
    
            struct sockaddr_in client;
            socklen_t len = sizeof(client); // client是一个接收型参数,存储了给服务器发送消息的客户端的IP+端口号

            int n = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&client, &len);
            if (n > 0)
                buffer[n] = '\0';
            else
                continue;

            std::string clientIp = inet_ntoa(client.sin_addr);
            int clientport = ntohs(client.sin_port);
            Add_user(clientIp, clientport, client);
            _rq.push(buffer);
            std::cout << clientIp << '-' << clientport << " "<< "client echo#" << buffer << std::endl;
        }
    }

    void send()
    {
    
    
        while (true)
        {
    
    
            std::string message;
            _rq.pop(&message);

            LockGuard lockguard(&_lock);
            //访问临界资源,需要锁
            for (auto &user : _online)
            {
    
    
                sendto(_sock, message.c_str(), strlen(message.c_str()), 0, (sockaddr *)&user.second, sizeof(user.second));
            }
        }
    }

    ~UdpServer()
    {
    
    
        pthread_mutex_destroy(&_lock);

        p->join();
        c->join();
    }

private:
    int _sock;
    uint16_t _port;
    func_t _server;
    std::unordered_map<std::string, sockaddr_in> _online;
    RingQueue<std::string> _rq;
    pthread_mutex_t _lock;
    Thread *p;
    Thread *c;

    // std::string _ip;//  云服务器,或者一款服务器,一般不要指明某一个确定的IP, 让我们的udpserver在启动的时候,bind本主机上的任意IP
};

udp_server.cc

#include "udp_server.hpp"
#include <iostream>
#include <memory>

using namespace std;

static void usage(string prc)
{
    
    
    cout << "Usage\n\t" << prc << "port\n"
         << endl;
}


bool isPass(const string &command)
{
    
    
    bool pass = true;
    auto pos = command.find("rm");
    if (pos != std::string::npos)
        pass = false;
    pos = command.find("mv");
    if (pos != std::string::npos)
        pass = false;
    pos = command.find("while");
    if (pos != std::string::npos)
        pass = false;
    pos = command.find("kill");
    if (pos != std::string::npos)
        pass = false;
    return pass;
}
string excuteCommand(const string &s)
{
    
    
    // 可能有些人会传递一些比较恶劣的代码,如rm ,所以我们需要进行安全检查
    if (!isPass(s))
        return "you are a bad man!";

    FILE *fp = popen((s.c_str()), "r");
    if (fp == NULL)
        return "None";
    // 获取结果
    string result;
    char line[2048];
    while (fgets(line, sizeof(line), fp) != NULL)
    {
    
    
        result += line;
    }
    pclose(fp);

    return result;
}

int main(int argc, char *argv[])
{
    
    
    if (argc != 2)
    {
    
    
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port = atoi(argv[1]);
    // unique_ptr<UdpServer> us(new UdpServer(excuteCommand,port));
    unique_ptr<UdpServer> us(new UdpServer(port));

    us->Start();
    return 0;
}

udp_client.cc

#include <iostream>
#include <bits/stdc++.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Thread.hpp"
#include "err.hpp"
#include <cstring>

using namespace std;

// 127.0.0.1(本地回环) 表示的是当前主机,用于进行本地通讯或者测试
static void Usage(string prc)
{
    
    
    cout << "Usage\t\n"
         << prc << " serverip serverport\n"
         << endl;
}

void *recver(void *args)
{
    
    
    int sock = *(static_cast<int *>(args));

    while (true)
    {
    
    
        // 接受
        char buffer[2048];
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        int n = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &len);
        if (n > 0)
        {
    
    
            buffer[n] = 0;
            std::cout << buffer << std::endl; // 1
        }
    }
}

int main(int argc, char *argv[]) //./server 目标IP 目标PORT
{
    
    
    if (argc != 3)
    {
    
    
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
    
    
        cerr << "socket fail" << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    struct sockaddr_in server; // 目标服务器的IP+PORT
    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());

    pthread_t tid;
    pthread_create(&tid, nullptr, recver, &sock);

    while (true)
    {
    
    
        char buffer[1024];
        cout << "请输入#";
        // cin>>buffer;
        cin.getline(buffer, 1024);

        // 1. client 这里要不要bind呢?要的!socket通信的本质[clientip:clientport, serverip:serverport]
        // 2.client不需要自己bind,也不要自己bind,操作系统自动给我们进行bind -- 为什么?client的port要随机让OS分配防止client出现启动冲突(我们的手机终端上有很多的软件(客户端),如果自己bind,会可能导致某些服务的端口号被占用,无法启动)
        // 3.server 为什么要自己bind?1. server的端口不能随意改变,众所周知且不能随意改变的 2. 同一家公司的port号需要统一规范化
        sendto(sock, buffer, strlen(buffer), 0, (sockaddr *)&server, sizeof(server));
    }
    return 0;
}

結果の表示:

ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/m0_64579278/article/details/133414318