[Red informática] El servidor UDP implementa una sala de chat en red

prefacio

En el artículo anterior aprendimos brevemente qué es la programación de sockets. En este artículo utilizamos sockets UDP para implementar una sala de chat en línea simple.

Escribir un servidor de socket UDP

Variables miembro

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

función miembro

  • Constructor:

Hoy sabemos que para los servidores es necesario especificar el número de puerto. Cuando inicializamos el servidor, inicializamos tanto el bloqueo como el hilo.

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

  • incinerador de basuras

El destructor completa la liberación de recursos, no olvide esperar el proceso.

~UdpServer()
{
    
    
    pthread_mutex_destroy(&_mmtx);

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

    delete p;
    delete c;
}

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

  • agregar usuario

Debido a que UDP no está orientado a la conexión, para crear una sala de chat, debe registrar todos los hosts que han enviado mensajes al servidor para que se pueda reenviar la información. Debido a que los datos se insertan en el mapa, no son seguros para subprocesos y requieren bloqueo.

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


  • Ya hemos resaltado los detalles de la interfaz recv en la sección anterior y no entraremos en detalles aquí.
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);
    }
}

  • transmisiónAquí
    usamos una matriz temporal para almacenar los hosts que se transmitirán y luego enviamos los mensajes en la cola a todos los hosts en esta matriz. Entonces, ¿cuáles son los beneficios de hacer esto?

Primero copie al usuario y luego deje que este hilo envíe datos de forma independiente, lo que permite que otros hilos que usan el mapa obtengan el bloqueo más rápido, porque el proceso de copia es mucho más rápido que enviar datos.

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

Escribir un cliente UDP

La escritura del cliente es similar a la del servidor. Hay una cosa a la que debemos prestar atención. La programación de sockets requiere vincular la IP y el número de puerto. Hacemos esto en el lado del servidor. Entonces, ¿el cliente necesita vincular el número de puerto y la IP?

La respuesta es definitivamente necesaria, pero este trabajo no lo hacemos nosotros, sino el sistema operativo, que vincula automáticamente la IP y el número de puerto cuando se envía por primera vez.

La razón también es muy fácil de pensar: para el cliente, los números de puerto son aleatorios, por lo que, naturalmente, el sistema operativo debería ayudar.

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

Prueba de efecto

Se completa un servidor Udp tan simple.
Insertar descripción de la imagen aquí

Supongo que te gusta

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