/network communication/udp-based echo server

Purpose: To use the udp protocol and the socket interface to write an echo server on the Linux platform

Function: The client sends a message to the server, the server receives and sends the message back to the client

Tools: VS code, makefile, XShell, Tencent Cloud Server

  1. Preparation

Makefile

.PHONY:all
all:udp_client udp_surver

udp_client: udp_client.cc
    g++ -o $@ $^ -std=c++11
udp_surver:udp_surver.cc
    g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
    rm -rf udp_client udp_surver

log.txt log function

#include<iostream>
#include<string>
#include <ctime>
#include <cstdio>
#include<cstdarg> //Used to parse the variable parameter list

// log level
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

// Log level to string representation
const char* gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

void logMessage(int level, const char* format,...)
{
    char stdBuffer[1024];
    time_t timestamp=time(nullptr);//Get the current timestamp
    snprintf(stdBuffer,sizeof stdBuffer,"[%s][%ld]",gLevelMap[level],timestamp);//format the log information to the standard buffer

    char logBuffer[1024];//custom partial buffer
    va_list args;
    va_start(args, format);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args); //args=nullptr

    printf("%s%s\
", stdBuffer, logBuffer);


}
  1. Writing server

udp_server.hpp

  1. Member variables

 //A server needs an ip address and a port number
    //The ip user input is a string, which needs to be converted to a dotted decimal network sequence
    //sock file descriptor, subsequently allocated by the operating system
    uint16_t _port;
    std::string _ip;
    int _sock;
  1. Initialize server

  1. The socket file descriptor is returned successfully, and -1 is returned on failure, and the error code is set

  1. Domain fills in the macro AF_INET network communication whether you want to perform local communication or network communication

  1. type fills the macro SOCK_DGRAM whether you want to be byte stream or datagram oriented

  1. protocol is generally 0

 //Initialize server, system call, complete network function
        //1. Create socket (ip + port)
        _sock=socket(AF_INET,SOCK_DGRAM,0);
        if(_sock<0)//error handling
        {
            logMessage(FATAL,"%d:%s", errno, strerror(errno));
            exit(2);
        }

Fill in port and ip

inet_addr can convert our IP address from char* to a four-byte sequence of the network

 //ip address, dotted decimal, [0-255]: 1 byte, 4 areas, a total of 4 bytes can represent an ip
        struct sockaddr_in local;//used to fill local port + ip
        bzero( & amp;local,sizeof(local));
        local.sin_family=AF_INET;//Indicates that the user has created a file-related socket for network communication
        local.sin_port=htons(_port);//The IP and port of the server will also be sent to the other host in the future -> the data must be sent to the network first!
        local.sin_addr.s_addr=_ip.empty()?INADDR_ANY:inet_addr(_ip.c_str());//When the server is working, we can let him get data from any IP
        //0.0.0.0 represents any ip, if the user wears it, use the ip, because there may be many network cards

binding plus error handling

 if(bind(_sock,(struct sockaddr*) & amp;local,sizeof(local))<0)
        {
            logMessage(FATAL,"%d:%s", errno, strerror(errno));
            exit(2);
        }

Then initialize the server and it’s done

logMessage(NORMAL,"init udp server done...%s",strerror(errno));

Initialize the server:

bool initServer()
    {
        //Initialize the server, call the system, and complete the network function
        //1. Create socket (ip + port)
        _sock=socket(AF_INET,SOCK_DGRAM,0);
        if(_sock<0)//error handling
        {
            logMessage(FATAL,"%d:%s", errno, strerror(errno));
            exit(2);
        }
        //2.bind: The ip and port set by the user are strongly related to the current process in the kernel
        //ip address, dotted decimal, [0-255]: 1byte, 4 areas, a total of 4bytes can represent an ip
        struct sockaddr_in local;//used to fill local port + ip
        bzero( & amp;local,sizeof(local));
        local.sin_family=AF_INET;//Indicates that the user has created a file-related socket for network communication
        local.sin_port=htons(_port);//The IP and port of the server will also be sent to the other host in the future -> the data must be sent to the network first!
        local.sin_addr.s_addr=_ip.empty()?INADDR_ANY:inet_addr(_ip.c_str());//When the server is working, we can let him get data from any IP
        //0.0.0.0 represents any ip, if the user wears it, use the worn ip, because there may be many network cards
        if(bind(_sock,(struct sockaddr*) &local,sizeof(local))<0)
        {
            logMessage(FATAL,"%d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL,"init udp server done...%s", strerror(errno));
        return true;
    }

Summary: Call the system interface, create a socket (file), and use this file for data transmission in the future, set ip and port

Binding ip + port is strongly related to fd

  1. The server starts working

peer is an output parameter used to receive the client’s ip and port number information

revfrom is used to receive remote information

sendto is used to send data to the remote end, using _sock is equivalent to reading and sending information from the socket

void Start()
    {
        // As a web server, never quit!
        // Server startup -> process -> resident process -> always exists in memory, unless it hangs!
        // echo server: client sends us a message, we return it unchanged
        char buffer[SIZE];//Used to receive messages from clients
        while(1)
        {
            //The message from the client will contain the ip/port related information of the client
            //Define peer output parameters for receiving user information
            //len input and output parameters
            struct sockaddr_in peer;
            bzero( &peer,sizeof(peer));
            socklen_t len=sizeof(peer);
            ssize_t s=recvfrom(_sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*) & amp;peer, & amp;len);
            if(s>0)
            {
                buffer[s]=0;
                //Start parsing client information
                uint16_t cli_port=ntohs(peer.sin_port);//Network to host
                std::string cli_ip=inet_ntoa(peer. sin_addr);
                printf("[%s:%d]# %s\
",cli_ip.c_str(),cli_port,buffer);//Print customer information and messages from customers on the server side
            }
            //Write the message sent by the client back to the client
            sendto(_sock,buffer,strlen(buffer),0,(struct sockaddr*) & amp;peer,len);

            
        
        }
}

udp_server.cc

// ./udp_server port
int main(int argc,char* argv[])
{
    if(argc!=2)
    exit(1);

    uint16_t port=atoi(argv[1]);
    std::unique_ptr<UdpServer> svr(new UdpServer(port));//Manage with smart pointer

    svr->initServer();
    svr->Start();
    return 0;
}

udp_client.cc

Compilation similar to the server side

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <memory>

static void usage(std::string proc)
{
    std::cout << "\
Usage: " << proc << " serverIp serverPort\
"
              << std::endl;
}
// ./udp_client 127.0.0.1 8080 //Who do you want to message
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }


    // Should the client be bound? ? Yes, but generally the client will not display the bind, and the programmer will not bind by himself
    // client is a client -> ordinary people download, install and start using -> if the programmer binds himself ->
    // The client must bind a fixed ip and port. What if other clients occupy this port in advance? ?
    // The client generally does not need to specify the port specified by the bind, but let the OS automatically choose randomly (when did you do it?)

    std::string message;
    struct sockaddr_in server;
    memset( & server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);

    char buffer[1024];
    while(true)
    {
        
        std::cout << "Please enter your information # ";
        std::getline(std::cin, message);
        if(message == "quit") break;
        // When the client sends a message to the server for the first time, the OS will automatically bind the client's IP and PORT
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*) & amp;server, sizeof server);

        //fill placeholders for interface uniformity
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr*) & amp;temp, & amp;len);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
    return 0;
}
  1. Effect demonstration

  1. Start the server, bind port number 8080

  1. Start the client and send a message to the execution server (mark the server’s ip and port number)

  1. It’s time to start sending messages

4. Expansion (next update)

At the same time, we can expand the function to the original XShell, that is, the client remotely controls the server, and can also add multi-threading to realize the group chat function, that is, the client reads the message sent by the server and sends a message to the server at the same time, and the server is responsible for sending the client The message sent by the terminal is sent to every client that has sent a message to itself

Note: 127.0.0.1 is used as a local test, which means that the data does not enter the LAN, and the port number can also be opened to achieve real remote communication. It is necessary for the other party’s computer to download the client (udp_client) and send a message to my public network ip. The server can receive the message and realize simple network communication, but there is only one computer, so the experiment cannot be done~