[Linux server] Implementation of multi-person chat group ---- code attached

UDP build server

Preliminary knowledge

Understand UDP protocol

Here we also have an intuitive understanding of UDP (User Datagram Protocol); we will discuss it in detail later.

  • transport layer protocol
  • no connection
  • Unreliable transmission
  • datagram oriented
network byte order

We already know that multi-byte data in memory is divided into big-endian and little-endian relative to the memory address. Multi-byte data in disk files is also divided into big-endian and little-endian relative to
the offset Streams are also divided into big-endian and little-endian. So how to define the address of the network data stream?

  • The sending host usually sends the data in the sending buffer in order from low to high memory address;
  • The receiving host stores the bytes received from the network in the receiving buffer in order, and also in the order of memory addresses from low to high;
  • Therefore, the address of the network data flow should be specified as follows: the data sent first has a low address, and the data sent later has a high address.
  • The TCP/IP protocol stipulates that network data flow should be in big-endian byte order, that is, low address and high byte .
  • Regardless of whether the host is a big-end machine or a little-end machine, data will be sent/received according to the network byte order specified by TCP/IP;
  • If the current sending host is little endian, you need to convert the data to big endian first; otherwise, ignore it and send it directly;
   #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);
  • These function names are easy to remember, h represents host (local), n represents network (network), l represents a 32-bit long integer, and s represents a 16-bit short integer .
  • For example, htonl means converting a 32-bit long integer from host byte order to network byte order, such as converting an IP address and preparing to send it.
  • If the host is little-endian, these functions convert the parameters to big-endian and then return;
  • If the host is big-endian, these functions do not perform conversion and return the parameters unchanged.
socket programming interface

``//Create socket file descriptor (TCP/UDP, client + server) int socket(int domain, int type, int protocol);`

// 绑定端口号 (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 structure
  • The address format of Pv4 and IPv6 is defined in netinet/in.h. The IPv4 address is represented by the sockaddr_in structure, including 16-bit address type, 16-bit port number and 32-bit IP address.1

  • IPv4 and IPv6 address types are defined as constants AF_INET and AF_INET6 respectively. In this way, as long as the first address of a certain sockaddr structure is obtained, there is no need to know which type of sockaddr structure it is, and the address in the structure can be determined based on the address type field. content.

  • The socket API can be represented by the struct sockaddr * type, which needs to be forced to be converted into sockaddr_in when used; the advantage of this is the versatility of the program, which can receive IPv4, IPv6, and UNIX Domain Socket various types of sockaddr structure pointers as parameter;

    There are sockaddr structure, sockaddr_in structure, sockaddr_un structure

The sockaddr_in structure is used for communication in the network.

The sockaddr_un structure is used for local inter-process communication.

What we should know is that the sockaddr structure, sockaddr_in structure, and sockaddr_un structure constitute polymorphism.

The sockaddr structure is the base class, the sockaddr_in structure, and the sockaddr_un structure are derived classes.

We only need to understand the sockaddr_in structure now.

Insert image description here

Build server

Initialize server

1. Create a socket interface and open the network file

2. Specify the IP and PORT to the server

3.bind binds the sockaddr socket field to the network file descriptor

Code:

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

At this point, the initialization of our server is successful, and the next step is to start the server.

Start the server

We need to receive messages from the client, process the messages and return them to the client.

This means that the server needs to have the two most basic functions:

Receive network messages and send messages over the network

  • Define a buffer to store received messages
  • We also need to know who sent me the message and define a sockaddr_in structure to save the customer's IP and PORT.
  • Finally, return the message to the client

Code:

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

The simple writing of the entire server is completed:

Code:

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;
}
Easy client implementation

Next, we write the client. The writing of the client is basically based on the port of the server.

At this stage, we only need to send messages to the server and receive messages from the server.

process:

  • create socket
  • If we want to send a message to the client, we need to know its IP and PORT, so we need a sockaddr_in structure filled with server information.
  • Then just send and receive messages

However, there are some things that need to be paid attention to when writing the client.

1. The port number and IP address of the server need to be bound by ourselves, so does the client need it?
Answer: The client also needs to be bound, but we do not need to manually bind it ourselves. The OS does it for us.
Reason: The client's port must be randomly allocated by the OS to prevent client startup conflicts (for example: there are a lot of software (clients) on our mobile terminals. If you bind by yourself, it may cause the port numbers of some services to be occupied and unable to start. )

2. Why does the server need to bind itself?
Reasons:
1. The port of the server cannot be changed at will. It is well known and cannot be changed at will.
2. The port number of the same company needs to be unified and standardized.

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

It is necessary to process the error code:
err.hpp

#pragma once

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

In the server, we implemented a simple echoserver that does not process the message, but simply returns the message.

achieve effect
Insert image description here

Build a group chat system

Based on the previously built server and processed, a simple group chat function can be realized.
We can understand group chat like this:

That is, you send your message to the server, and the server forwards it to everyone in the group chat.

So we need to make the following changes:

  1. A container is needed to store customer messages, and all connected customers are saved in the sockaddr_in structure.
  2. A buffer is also needed to save the sent messages.
  3. At the same time, it should be noted that if different clients send messages to the same buffer, there will be multi-thread concurrency issues and locks will be required.
  4. The server also needs to be multi-threaded because a single process will be blocked in receiving or sending messages.

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

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

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

Display of results:

Insert image description here

Guess you like

Origin blog.csdn.net/m0_64579278/article/details/133414318