[Rede de Computadores] Servidor UDP implementa sala de bate-papo em rede

Prefácio

No artigo anterior, aprendemos brevemente o que é programação de soquete. Neste artigo, usamos soquetes UDP para implementar uma sala de chat online simples.

Escrevendo um servidor de soquete UDP

Variáveis ​​de membro

// 1. socket的id,相当于文件id
int _sock;
// 2. port
uint16_t _port;

// 3 一个线程负责收放消息;另一个线程负责发消息
Thread *c; // 收消息
Thread *p; // 发消息

// 4 环形队列来实现生产消费
RingQueue<std::string> rq;

// 5 信息要群发给所有人,要记录所有人的ip和端口号
std::unordered_map<std::string, sockaddr_in> _users;

// 6 一把锁保证在读取要发送给哪些人时是安全的
//  在读取_users时,另一个线程收到了消息,会修改这个map
pthread_mutex_t _mmtx;

função de membro

  • Construtor:

Hoje sabemos que para servidores o número da porta precisa ser especificado. Quando inicializamos o servidor, inicializamos o bloqueio e o thread.

UdpServer(uint16_t port = default_port) : _port(port)
{
    
    
    pthread_mutex_init(&_mmtx,nullptr);

    p = new Thread(1,std::bind(&UdpServer::Recv,this));
    c = new Thread(1,std::bind(&UdpServer::BroadCast,this));
}

  • destruidor

O destruidor conclui a liberação do recurso, não se esqueça de aguardar o processo.

~UdpServer()
{
    
    
    pthread_mutex_destroy(&_mmtx);

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

    delete p;
    delete c;
}

  • Começar
void start()
{
    
    
    // 1 创建socket接口,打开网络文件
    _sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (_sock < 0)
    {
    
    
        std::cerr << "create socket error" << std::endl;
        exit(SOCKED_ERR);
    }
    std::cout << "create socket success" << std::endl;

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

    local.sin_family = AF_INET;
    local.sin_port = htons(_port);      // host to network short
    local.sin_addr.s_addr = INADDR_ANY; // bind本主机任意ip
    if (bind(_sock, (sockaddr *)&local, sizeof(local)) < 0)
    {
    
    
        std::cerr << "bind socket error" << std::endl;
        exit(BIND_ERR);
    }
    std::cout << "bind socket success" << std::endl;

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

  • adicionar usuário

Como o UDP não é orientado à conexão, para construir uma sala de chat, você deve registrar todos os hosts que enviaram mensagens ao servidor para que as informações possam ser encaminhadas. Como os dados são inseridos no mapa, eles não são seguros para threads e requerem bloqueio.

void addUser(std::string &name, sockaddr_in &peer)
{
    
    
    LockGuard lg(&_mmtx);
    _users[name] = peer;
}


  • Já destacamos os detalhes da interface recv na seção anterior e não entraremos em detalhes aqui.
void Recv()
{
    
    
    char buffer[1024];
    // 网络服务都是循环!
    while (true)
    {
    
    
        // recv里有两个输出型参数
        sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int n = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&peer, &len);
        if (n > 0)
        {
    
    
            buffer[n] = 0;
        }
        else
        {
    
    
            continue;
        }

        // 发消息人的ip和port
        std::string client_ip = inet_ntoa(peer.sin_addr); // net->ascii
        uint16_t client_port = ntohs(peer.sin_port);

        // 构造了发消息人的姓名
        std::string name = client_ip + "-" + to_string(client_port);

        addUser(name, peer);
        rq.push(name + buffer);
    }
}

  • broadcastAqui
    usamos um array temporário para armazenar os hosts a serem transmitidos e, em seguida, enviamos as mensagens na fila para todos os hosts neste array. Então, quais são os benefícios de fazer isso?

Copie o usuário primeiro e depois deixe este thread enviar dados de forma independente. Isso permite que outros threads que usam o mapa obtenham o bloqueio mais rapidamente, porque o processo de cópia é muito mais rápido do que o envio de dados.

void BroadCast()
{
    
    
    while (true)
    {
    
    
        std::string send_string;
        rq.pop(&send_string);

        std::vector<sockaddr_in> v; // 临时数组,存放要发给那些人

        {
    
    
            LockGuard lg(&_mmtx);
            for (auto user : _users)
            {
    
    
                v.push_back(user.second);
            }
        }

        for (auto user : v)
        {
    
    
            sendto(_sock, send_string.c_str(), send_string.size(), 0, (sockaddr *)&user, sizeof(user));
        }
    }
}

Escrevendo um cliente UDP

A escrita do cliente é semelhante à do servidor. Há uma coisa que precisamos prestar atenção. A programação do soquete requer a ligação do IP e do número da porta. Fazemos isso no lado do servidor. Então, o cliente precisa vincular o número da porta e o IP?

A resposta é definitivamente necessária, mas esse trabalho não é feito por nós, mas sim pelo sistema operacional, que vincula automaticamente o IP e o número da porta no primeiro envio.

O motivo também é muito fácil de pensar: para o cliente, os números das portas são aleatórios, então naturalmente o sistema operacional deve ajudar.

void *recv(void *args)
{
    
    
    int sock = *(static_cast<int *>(args));
    char buffer[1024];
    sockaddr_in temp;
    socklen_t len = sizeof(temp);

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

int main(int argc, char *argv[])
{
    
    
    if (argc != 3)
    {
    
    
        std::cout << "请输入程序名 + 对方ip + 对方端口号" << std::endl;
        exit(USAGE_ERR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
    
    
        std::cerr << "create socket error" << std::endl;
        exit(SOCKED_ERR);
    }
    // 系统在我们第一次调用系统调用发送的时候,会自动给我绑定ip和端口号

    // 填充server的信息
    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());

    //
    pthread_t r_id;
    pthread_create(&r_id, nullptr, recv, (void*)&sock);

    while(true)
    {
    
    
        std::string message;
        std::cout << "请输入..." << std::endl;
        getline(std::cin,message);

        sendto(sock,message.c_str(),message.size(),0,(sockaddr*)&server,sizeof(server));
    }

    pthread_join(r_id,nullptr);

    return 0;
}

Teste de efeito

Um servidor UDP tão simples está concluído.
Insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/m0_73209194/article/details/132113837
Recomendado
Clasificación