El servidor udp [Linux] realiza la traducción inglés-chino y la operación remota

Todas las pequeñas características divertidas ~

Directorio de artículos

  • prefacio
  • 1. El servidor udp realiza la traducción en línea inglés-chino en Internet
  • Dos, el servidor UDP realiza la operación remota de la red
  • Resumir


prefacio

En el artículo anterior, explicamos los pasos de implementación del servidor UDP en detalle y explicamos cada interfaz utilizada en detalle. En el artículo anterior, solo teníamos una función de comunicación de red simple, y muchos lugares no eran perfectos. Entonces, en este artículo modificaremos el código utilizado en el artículo anterior para escribir una versión en línea de traducción inglés-chino y una gran sala de chat en línea.


1. El servidor udp realiza la traducción en línea inglés-chino en Internet

Nos falta la parte del procesamiento de datos en un artículo, así que primero diseñemos cómo procesar archivos:

Comenzamos definiendo un tipo de función con un contenedor:

 El significado de este código es renombrar el wrapper cuyo valor de retorno es void, cuyos parámetros son string, uint16_t y string, y renombrarlo a func_t. El propósito de esto es usar el número de puerto, ip, etc. al procesar datos parámetro Luego definimos un objeto de tipo función dentro de la clase:

 Luego necesitamos pasar un método de función cuando el usuario crea el servidor. El propósito es permitir que el servidor haga este método en el futuro, así que lo inicializamos en el constructor:

udpServer(const func_t &cb,const uint16_t& port,const string ip = defaultIp)
            :_port(port)
            ,_ip(ip)
            ,_sockfd(-1)
            ,_callback(cb)
        {

        }

Luego dejamos que el servidor llame a nuestro método de devolución de llamada al final de iniciar el servidor. En la actualidad, simplemente dejamos que el servidor pase la IP y el número de puerto del cliente y los datos enviados al método de devolución de llamada:

 A continuación, agregaremos un método de devolución de llamada al archivo server.cc y pasaremos este método al servidor al crear el servidor:

void handerMessage(string clientip,uint16_t clientport,string message)
{
    //就可以对message进行特定的业务处理,而不关心message怎么来的---------server通信和业务逻辑解耦
}
// ./udpServer port
int main(int argc,char* argv[])
{
    if (argc!=2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    } 
    uint16_t port = atoi(argv[1]);
    unique_ptr<udpServer> usvr(new udpServer(handerMessage,port));
    usvr->InitServer();
    usvr->start();
    return 0;
}

A continuación, usamos map para establecer una relación de mapeo entre inglés y chino:

 Primero escribamos un diccionario, que es para guardar la correspondencia entre inglés y chino en el archivo:

 Solo escribimos cinco palabras porque era solo para probar. El siguiente paso es leer los datos en el archivo en el mapa. Ya que necesitamos usar la interfaz del archivo C++ para leer el archivo, primero definimos una variable estática para guarde el archivo que acabamos de crear. La ruta al diccionario:

 Luego escribimos el código para inicializar la interfaz del diccionario:

static void initDict()
{
    ifstream in(dictText,ios::binary);
    if (!in.is_open())
    {
        cerr<<" open file "<<dictText<<" error "<<endl;
        exit(OPEN_ERR);
    }
    string line;
    while (getline(in,line))
    {
         cout<<line<<endl;
    }
    in.close();
}

En primer lugar, establecemos el método de apertura del archivo como binario, y luego juzgamos si el archivo se abre con éxito. Si no tiene éxito, se imprimirá un error y se devolverá un código de error. Aquí, un código de error de también se agrega un archivo:

    enum 
    {
       USAGE_ERR = 1
       ,SOCKET_ERROR
       ,BIND_ERR
       ,OPEN_ERR
    };

Luego usamos un objeto de cadena para poner los datos leídos en el archivo en el objeto de cadena, y usamos getline para obtener cada línea de datos en el archivo.Tenga en cuenta que getline es una función para obtener una línea. Ahora experimentemos para ver si podemos leer el archivo correctamente, así que lo imprimimos primero y recordamos cerrar el archivo al final:

 En la función principal, no iniciamos el servidor primero, sino que solo probamos si el archivo se puede abrir normalmente. Puede ver que no hay problema. A continuación, insertamos la cadena obtenida por getline en el mapa:

static bool cutString(string& line, string* s1, string* s2,const string& flag)
{
    size_t sz = flag.size();
    size_t pos = line.find(flag);
    if (pos!=string::npos)
    {
        *s1 = line.substr(0,pos);
        *s2 = line.substr(pos+sz);
        return true;
    }
    return false;
}
static void initDict()
{
    ifstream in(dictText,ios::binary);
    if (!in.is_open())
    {
        cerr<<" open file "<<dictText<<" error "<<endl;
        exit(OPEN_ERR);
    }
    string line;
    string key,value;
    while (getline(in,line))
    {
         //cout<<line<<endl;
        if (cutString(line,&key,&value,":"))
        {
            _dict.insert(make_pair(key,value));
        }
    }
    in.close();
}

Si queremos insertar, debemos dividir la cadena de lectura en inglés y chino. Usamos ":" para dividir al completar el diccionario, por lo que podemos diseñar una función para cortar la cadena y pasar los parámetros, la clave y el valor es un parámetro de salida. Cuando pasamos una clave y un valor vacíos, la interfaz nos devolverá la clave y el valor procesados. Al mismo tiempo, también podemos pasar el separador, de modo que podamos modificar directamente los parámetros cuando modifiquemos más tarde. A continuación, escribimos una interfaz de impresión para probar si el mapa es normal:

static void debugDict()
{
    for (auto& dt:_dict)
    {
        cout<<dt.first<<" # "<<dt.second<<endl;
    }
}

 Después de la prueba, podemos ver que no hay ningún problema. A continuación, comenzamos a escribir el código para que el servidor ejecute la función de devolución de llamada:

void handerMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
    //就可以对message进行特定的业务处理,而不关心message怎么来的---------server通信和业务逻辑解耦
    auto it = _dict.find(message);
    string response_message;
    if (it == _dict.end())
    {
        cout << "Server notfind message" << endl;
        response_message = "字典内无此单词的中文";
    }
    else
    {
        response_message = it->second;
    }
    // 构建结构体,把处理过的数据再返回
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    bzero(&client, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());
    // 构建好结构体后,我们要把处理的数据发给谁呢?当然是客户端了,客户端给我们发数据我们再将处理后的数据发回给客户端
    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr *)&client, len);
}

En primer lugar, después de que el servidor ingrese a la función de devolución de llamada, primero debemos procesar el mensaje enviado por el usuario. Si no se puede encontrar el mensaje, imprimiremos el mensaje no encontrado (aquí, el servidor y el cliente se imprimen al mismo tiempo). mismo tiempo, y el propósito de imprimir en el servidor es permitir que el programador agregue una palabra que no se ha ingresado), si la encontramos, obtendremos el valor del mensaje en el mapa, y luego debemos regresar el mensaje procesado al cliente (nota: no importa si puede encontrar la palabra china correspondiente a la palabra, debe devolver los datos al cliente), ya que la ip y el número de puerto del cliente y el descriptor del archivo necesitamos usar el Se requiere que la función sendto regrese, por lo que nuestra función de devolución de llamada debe agregar un parámetro adicional para recibir el descriptor del archivo:

 Luego, podemos construir la estructura como antes, y luego llenar la estructura con la IP y el número de puerto del cliente, y luego devolver la estructura al cliente a través de la interfaz sendto, porque el mensaje procesado se recibe por cadena, por lo que puede usar directamente el interfaz c_str() y el tamaño de la cadena como el segundo y tercer parámetro de sendto.

Ahora que hemos escrito el código para que el servidor envíe mensajes al cliente, por supuesto que necesitamos escribir el código para que el cliente reciba mensajes del servidor, así que modificamos el código del cliente:

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;
           char buffer[1024];
           while (!_quit)
           {
              cout<<"Please Enter# ";
              cin>>message;
              sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
              struct sockaddr_in temp;
              socklen_t len = sizeof(temp); 
              int n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
              if (n > 0)
              {
                  buffer[n] = 0;
              }
              cout<<"服务器的翻译结果# "<<buffer<<endl;
           }
       }

El código anterior permanece sin cambios, principalmente la parte de recibir datos. La función recvfrom es necesaria para recibir datos. Esta función requiere un descriptor de archivo, en qué búfer deben existir los datos recibidos, el tipo de datos recibidos y la estructura vacía que preparamos. ., la función de esta función es la siguiente: el servidor nos envía un mensaje, luego esta interfaz llenará la información del socket del servidor en la nueva estructura que creamos, así que no necesitamos llenar la estructura, ejecutemos stand up:

 A través de los resultados de la ejecución, podemos ver que no hay ningún problema con el programa. Por supuesto, también podemos admitir la carga en caliente de archivos de diccionario. ¿Qué es la carga en caliente? Significa que el diccionario recién agregado se puede cargar sin que finalice el programa, lo que es similar a la actualización continua en nuestro juego. El principio de diseño también es muy simple. Capturamos directamente la señal de terminación. Una vez terminado, ya no ejecutamos la lógica de terminación, sino que ejecutamos la lógica de inicialización del diccionario. Vamos a probarlo:

 Primero, captura la señal No. 2 en la función principal, luego ejecuta el método de recarga y luego escribimos recargar nuevamente:

void reload(int signo)
{
     (void)signal;
     initDict();
}

 Primero volvamos a ejecutar el código anterior y luego agreguemos palabras al diccionario para ver si se puede traducir con éxito:

 Agreguemos goodman directamente:

Luego enviamos la señal No. 2 al servidor, y la señal No. 2 es nuestro ctrl + c de uso común:

A partir de los resultados de la ejecución, podemos ver que hemos implementado con éxito la carga en caliente simple.

Dos, el servidor UDP realiza la operación remota de la red

A continuación, estamos implementando una pequeña función, es decir, enviamos el comando bajo Linux al servidor, y luego el servidor ejecuta el comando por nosotros y nos devuelve el resultado de la ejecución, y no es difícil para nosotros implementar, nosotros solo necesitamos modificar nuestra función de devolución de llamada Muy bien, probemos:

Primero reconocemos una interfaz popen:

 La función popen es en realidad una versión combinada de las tres interfaces que usamos antes, pipe + fork + exec*. ¿Todavía recuerdas el análisis de línea de comando que implementamos antes, que se completó con estas tres interfaces? Hablemos de popen a continuación:

El primer parámetro es la cadena de comando que queremos ejecutar en el futuro, como: pwd, lo mismo que el comando ls, ¿cómo ejecutar el comando? De hecho, la capa inferior de popen crea un subproceso para nosotros fork, y luego reemplaza el archivo con el programa exec al final a través de la forma de canalización para devolvernos el resultado. De qué tipo es el segundo parámetro a abrir, como los parámetros r y w de nuestro archivo.

void execCommand(int sockfd,string clientip,uint16_t clientport,string cmd)
{
     string response;
     FILE* fp = popen(cmd.c_str(),"r");
     if (fp==nullptr) response = cmd + " exec failed";
     char line[1024];
     while (fgets(line,sizeof(line)-1,fp))
     {
        response+=line;
     }
     pclose(fp);
}

 Primero crea una clase de cadena para regresar, y luego llamamos a la interfaz popen de una manera de solo lectura. Si la llamada falla, agregamos la información de falla de llamada a la cadena devuelta. Si tiene éxito, definimos un búfer y luego hacemos un bucle Lea el resultado de la operación de comando que nos devuelve la función popen, y podemos cerrar el archivo después de obtener el resultado. Lo siguiente es lo mismo que antes, debemos devolver el resultado al cliente, por lo que debemos crear una estructura:

void execCommand(int sockfd,string clientip,uint16_t clientport,string cmd)
{
     string response;
     FILE* fp = popen(cmd.c_str(),"r");
     if (fp==nullptr) response = cmd + " exec failed";
     char line[1024];
     while (fgets(line,sizeof(line)-1,fp))
     {
        response+=line;
     }
     pclose(fp);
     struct sockaddr_in client;
     bzero(&client,sizeof(client));
     socklen_t len = sizeof(client);
     client.sin_family = AF_INET;
     client.sin_port = htons(clientport);
     client.sin_addr.s_addr = inet_addr(clientip.c_str());
     sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,len);
}

Por supuesto, en aras de la seguridad, debemos juzgar y evitar comandos peligrosos como rm en el frente, porque el servidor que escribimos puede tener múltiples clientes para operar nuestro propio Linux, por lo que debemos evitar que los malos nos den rm y otros comandos. :

 Por supuesto, después de la prueba, todavía tenemos un pequeño problema, es decir, usamos cin cuando hacemos una entrada en el cliente, y cin se detiene cuando encuentra un espacio, y no hay forma de ingresar comandos como ls -a -l , por lo que modificamos la operación Input original:

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;
           char buffer[1024];
           while (!_quit)
           {
              /* cout<<"Please Enter# ";
              cin>>message; */
              char commandbuffer[1024];
              fgets(commandbuffer,sizeof(commandbuffer)-1,stdin);
              message = commandbuffer;
              sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
              struct sockaddr_in temp;
              socklen_t len = sizeof(temp);
              int n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
              if (n > 0)
              {
                  buffer[n] = 0;
              }
              //cout<<"服务器的翻译结果# "<<buffer<<endl;
              cout<<"服务器的执行结果# "<<buffer<<endl;
           }
       }

No necesitamos considerar el problema de los espacios cuando usamos fgets para ingresar. Vamos a ejecutarlo a continuación:

 Después de ejecutar, encontramos que hay un problema con el búfer. Cuando tocamos para crear un archivo, no debería devolvernos información, pero nos devolvió la información del último búfer, para que podamos mejorarlo. Cuando el El servidor no nos envía Cuando se muestra el resultado del comando, significa que el comando no tiene resultado. Al igual que el toque, debe verificarlo usted mismo después de crearlo. Por lo tanto, solo necesitamos juzgar si la cantidad de bytes devuelto es 0 cuando finaliza el mensaje enviado por el servidor.Si es 0, entonces borre el búfer:

 En la figura anterior, nuestro llenado de la estructura temporal es incorrecto, porque la interfaz recvfrom lo llenará por nosotros, por lo que no es necesario que lo haga.

Luego recompilamos:

 Se puede ver que nuestros resultados de ejecución no son un problema, por lo que se realizan dos pequeñas funciones simples. En el próximo artículo, usaremos el servidor udp para escribir una sala de chat de red a gran escala. A través de estos tres ejemplos, creo que todos Puede comprender profundamente el servidor UDP.

Supongo que te gusta

Origin blog.csdn.net/Sxy_wspsby/article/details/131389701
Recomendado
Clasificación