[Linux] udp server realizes English-Chinese translation and remote operation

All fun little features~

Article Directory

  • foreword
  • 1. The udp server realizes online English-Chinese translation on the Internet
  • Two, udp server realizes network remote operation
  • Summarize


foreword

In the previous article, we explained the implementation steps of the udp server in detail, and explained each interface used in detail. In the previous article, we only had a simple network communication function, and many places were not perfect. So in this article we will modify the code used in the previous article to write an online version of English-Chinese translation and a large online chat room.


1. The udp server realizes online English-Chinese translation on the Internet

We are missing the part of data processing in an article, so let's first design how to process files:

We start by defining a function type with a wrapper:

 The meaning of this code is to rename the wrapper whose return value is void, whose parameters are string, uint16_t, and string, and rename it to func_t. The purpose of this is to use the port number, ip, etc. when processing data. parameter. Then we define an object of type function inside the class:

 Then we need to pass a function method when the user creates the server. The purpose is to let the server do this method in the future, so we initialize it in the constructor:

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

        }

Then we let the server call our callback method at the end of starting the server. At present, we simply let the server pass the client's ip and port number and the sent data to the callback method:

 Next, we will add a callback method to the server.cc file, and pass this method to the server when creating the server:

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

Below we use map to establish a mapping relationship between English and Chinese:

 Let's write a dictionary first, which is to save the correspondence between English and Chinese in the file:

 We only wrote five words because it was just for testing. The next step is to read the data in the file into the map. Since we need to use the interface of the c++ file to read the file, we first define a static variable to save the file we just created. The path to the dictionary:

 Then we write the code to initialize the dictionary interface:

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

First of all, we set the method of opening the file as binary, and then judge whether the file is opened successfully. If it is not successful, an error will be printed and an error code will be returned. Here, an error code of a file is also added:

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

Then we use a string object to put the data read in the file into the string object, and we use getline to get each line of data in the file. Note that getline is a function to get a line. Now let's experiment to see if we can read the file correctly, so we print it first, and remember to close the file at the end:

 In the main function, we do not start the server first, but only test whether the file can be opened normally. You can see that there is no problem. Next, we insert the string obtained by getline into the map:

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

If we want to insert, we must split the read string into English and Chinese. We used ":" to split when filling in the dictionary, so we can design a function to cut the string and pass in the parameters, key and Value is an output parameter. When we pass in an empty key and value, the interface will return us the processed key and value. At the same time, we can also pass in the separator, so that we can directly modify the parameters when modifying later. Next, we write a printing interface to test whether the map is normal:

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

 After testing, we can see that there is no problem. Next, we start to write the code for the server to execute the callback function:

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

First of all, after the server enters the callback function, we need to process the message sent by the user first. If the message cannot be found, we will print the message not found (here, the server and the client are printed at the same time, and the purpose of printing to the server is to let the The programmer adds a word that has not been entered), if we find it, we will get the value of the message in the map, and then we need to return the processed message to the client (note: no matter whether you can find the Chinese word corresponding to the word, must return the data to the client), since the ip and port number of the client and the file descriptor we need to use the sendto function are required to return, so our callback function should add an additional parameter to receive the file descriptor:

 Then we can build the structure as before, and then fill the structure with the client's IP and port number, and then return the structure to the client through the sendto interface, because the processed message is received by string, So you can directly use the c_str() interface and size of string as the second and third parameters of sendto.

Now that we have written the code for the server to send messages back to the client, of course we need to write the code for the client to receive messages from the server, so we modify the client code:

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

The above code remains unchanged, mainly the part of receiving data. The recvfrom function is needed to receive data. This function requires a file descriptor, which buffer the received data should exist in, the type of received data, and the empty structure we prepared. , the function of this function is as follows: the server sends us a message, then this interface will fill the server's socket information into the new structure we created, so we don't need to fill the structure, let's run stand up:

 Through the running results, we can see that there is no problem with the program. Of course, we can also support hot loading of dictionary files. What is hot loading? It means that the newly added dictionary can be loaded without the end of the program, which is similar to the non-stop update in our game. The design principle is also very simple. We directly capture the termination signal. Once terminated, we no longer execute the logic of termination but execute the logic of initializing the dictionary. Let's try it out:

 First, capture the No. 2 signal in the main function, then execute the reload method, and then we write reload again:

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

 Let's rerun the previous code first, and then add words to the dictionary to see if it can be successfully translated:

 Let's add goodman directly:

Then we send the No. 2 signal to the server, and the No. 2 signal is our commonly used ctrl + c:

From the running results, we can see that we have successfully implemented simple hot loading.

Two, udp server realizes network remote operation

Next, we are implementing a small function, that is, we send the command under linux to the server, and then the server runs the command for us and returns the running result to us, and it is not difficult for us to implement, we only need to modify our callback function Alright, let's try it out:

We first recognize an interface popen:

 The popen function is actually a combined version of the three interfaces we used before, pipe + fork + exec*. Do you still remember the command line parsing we implemented before, which was completed with these three interfaces. Let’s talk about popen below:

The first parameter is the command string we want to execute in the future, such as: pwd, the same as the ls command, how to execute the command? In fact, the bottom layer of popen creates a subprocess for us fork, and then replaces the file with the exec program at the end through the pipeline. way to return the result to us. What type is the second parameter to open, such as the r and w parameters of our file.

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

 First create a string class for returning, and then we call the popen interface in a read-only manner. If the call fails, we add the call failure information to the returned string. If it succeeds, we define a buffer, and then loop Read the result of the command operation returned by the popen function to us, and we can close the file after getting the result. The following is the same as before, we need to return the result to the client, so we need to create a structure:

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

Of course, for the sake of safety, we should judge and avoid dangerous commands such as rm in the front, because the server we wrote can have multiple clients to operate our own linux, so we must avoid bad guys giving us rm and other commands:

 Of course, after testing, we still have a small problem, that is, we use cin when we make input on the client, and cin stops when it encounters a space, and there is no way to input commands like ls -a -l, so we modify the original Input operation:

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

We don't need to consider the problem of spaces when we use fgets to input. Let's run it below:

 After running, we found that there is a problem with the buffer. When we touch to create a file, it should not return information to us, but it returned the information of the last buffer to us, so we can improve it. When the server does not send us When the result of the command is displayed, it means that the command has no result. Just like touch, you need to check it yourself after it is created. Therefore, we only need to judge whether the number of bytes returned is 0 when the message sent by the server ends. If it is 0 then clear the buffer:

 In the figure above, our filling of the temp structure is incorrect, because the recvfrom interface will fill it for us, so there is no need to fill it.

Then we recompile:

 It can be seen that our execution results are no problem, so that two simple small functions are realized. In the next article, we will use the udp server to write a large-scale network chat room. Through these three examples, I believe everyone You can deeply grasp the udp server.

Guess you like

Origin blog.csdn.net/Sxy_wspsby/article/details/131389701