Network IO and IO multiplexing (select/poll/epoll) (1)

Tip: After the article is written, the table of contents can be automatically generated. For how to generate it, please refer to the help document on the right.

Network IO and IO multiplexing (select/poll/epoll) (1)

  • Preface
  • 1. Network card
  • 2. Network IO
    • 1.Code
      • Replenish
    • 2. Use tcp/udp net assistant tool
    • 3. Implement communication between the program and assistant
    • 4. Implement multiple cycles of communication between the program and the assistant
    • 4. Implement communication between the program (server) and multiple assistants (clients)
  • Summarize

Foreword

Make a record and summary of the study of network communication. The following operations are all performed in a Linux environment. (There was an error later. The development environment was changed to Windows environment and the virtual machine was enabled for operation)

Tips: The following is the text of this article, the following cases are for reference

1. Network card

As an interface at the TCP/IP layer, the network card can transmit signals at the physical layer and data packets at the network layer. No matter which layer it is on, it acts as an intermediary between the computer or server and the data network. When a user sends a request for a web page, the network card fetches data from the user’s device and sends it to the web server, which then receives the required data to display to the user.
We can view the relevant network configuration of the computer through the ifconfig command. Please add a picture descriptionSince I am connected to a mobile hotspot, what is displayed here is wlo1. Generally, when a computer is connected to a wired network cable, the display may be eth0, or eno, ens or enp. It will be briefly introduced below.

The difference between ens, eno, and enp network ports

Expanded knowledge content:

en identifies ethernet
o: Mainboard onboard network card, integrated device index number
p: independent network card, PCI network card
s: hot-swappable network card, expansion slot index number such as USB
nnn (number): MAC address + motherboard information to calculate a unique sequence

eno1: represents the network card built into the motherboard bios

ens1: represents the PCI-E network card built into the motherboard bios

enp2s0: PCI-E independent network card

eth0: If none of the above are used, return to the default network card name.

It can probably be understood like this: ens37f1np1, ens37f1

ens means hot-swappable network card, 37 means slot, “f1”: may refer to specific functions or identifiers, such as network port type or configuration. “np1”: An abbreviation that may represent a specific network protocol or function. (Plug in the ordinary Ethernet network card: ens37f1, plug in the mellanox rdma network card: ens37f1np1, (CI environment) Intel rdma network card plug in: ens1f1)
(Excerpted from: https://blog.csdn.net/bandaoyu/article/details/116308950)

2. Network IO

1. Code

The multi-io.c code is as follows:

#include <sys/socket.h> // Contains header files for socket series functions
#include <errno.h> // Contains header files for error handling
#include <netinet/in.h> // Header file containing the IPv4 address structure
#include <stdio.h>
#include <string.h>

//Create TCP Socket
int main() {<!-- -->

    int sockfd = socket(AF_INET, SOCK_STREAM, 0); // Create socket descriptor
    struct sockaddr_in serveraddr; // Define server address structure
    memset( & amp;serveraddr, 0, sizeof(struct sockaddr_in)); // Clear address structure

    serveraddr.sin_family = AF_INET; // Specify the address family as IPv4
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // Specify any local address
    serveraddr.sin_port = htons(2048); //The specified port number is 2048

    // Bind the socket to the specified address
    if (-1 == bind(sockfd, (struct sockaddr*) & amp;serveraddr, sizeof(struct sockaddr))) {<!-- -->
        perror("bind"); // Print error message
        return -1; // Return error code
    }

    listen(sockfd, 10); // Listening socket, the maximum number of connections allowed is 10

    getchar(); // Wait for user input

    return 0;
}

If our program wants to communicate over the network, we must first create a socket, which provides an abstraction layer that enables the program to communicate through the network (equivalent to us opening the entrance and exit for the program to communicate through the network). Then we need to provide the relevant address information for network communication, that is, the sockaddr in the code, and then bind the address to our socket through the bind function. At this point we have connected to a certain port at a certain address.
We generate it as an executable file

gcc -o multi-io multi-io.c

After running, you can observe that the program is waiting for our user’s input.
Please add a picture description
So how do we know if the port is connected? You can check the connection status of this port through netstat -anop | grep 2048.
Please add a picture description(If running under non-root permissions, the above prompt will appear , you can add sudo before the command or switch to run with root permissions)
You can see that the port is occupied and in listening state.

Supplement

The following is a supplement to the meaning of some parameters in the code.

1.socket

Please add image description
This is the official description of the socket function. Its return value is an integer. If its value is -1, it means an error has occurred. You can use perror to print out the error. It contains a total of three parameters.
1.__domain: specifies the address type for communication. Common ones include:

  • AF_INET:IPv4 address type
  • AF_INET6: IPv6 address type
  • AF_UNIX:Unix domain (local) tools

2.__type: Specifies the communication method. Common ones include:

  • SOCK_STREAM: Data transmission through data streams, providing reliable, connection-oriented communication, using the TCP protocol
  • SOCK_DGRAM: Transmits data through datagrams, provides connectionless, unreliable communication, and uses the UDP protocol.
  • SOCK_RAW: Raw tool with direct access to simple network protocols

3.__protocol: Indicates the protocol used by the device. If the parameter is 0, the system will automatically select the appropriate protocol based on the address and device type. For IPv4 and IPv6, common protocols are:

  • IPPROTO_TCP: represents TCP protocol
  • IPPROTO_UDP: represents UDP protocol

2.sockaddr_in
This is a structure used to store IPv4 address information. The structure stored in IPv6 is sockaddr_in6, which is specifically defined as follows:

Please add image description
We did not see the sin_family member in the structure. I guess it was inherited from the parent class at that time. We will not delve into it here.
Please add a picture descriptionGenerally I only need to define these three member variables. Normally used, while sin_zero is used to pad the structure size to 16 bytes and is usually not needed. Special note is that INADDR_ANY represents any local address, which is 0.0.0.0, which means that connections to any local IP address are allowed.

3.bind
A function that binds a device (socket here) to a local address (IP address and port number). The specific parameters are defined as follows:
Please add image description
The three parameters are expressed in sequence (binding device, binding address, address length).
(struct sockaddr*) & amp;serveraddr may be confusing to some friends, but it is actually used to obtain the address of the structure and perform pointer forced conversion.

2. Use tcp/udp net assistant tool

Using this tool allows for more intuitive testing of the programs we create.
Download address: https://github.com/busyluo/NetAssistant
Steps for usage:
git clone https://github.com/busyluo/NetAssistant.git
cd NetAssistant
qmake
make
./NetAssistant

The interface after successful permission:
Please add image description
The first time I tried to connect on a Linux system, the connection failed because there was only one network card on my computer. It is recommended to operate on a virtual machine in a Windows environment.

The following is performed on a virtual machine in a Windows environment.
Tcp/udp net assistant tool under windows
Download address: http://www.cmsoft.cn/download/cmsoft/netassist.zip
Run it directly after decompression. (This software may prompt that there is a virus, and the real-time firewall needs to be temporarily turned off before downloading and using it)

Run ifconfig on the virtual machine

It can be observed that the ip address is 192.168.237.134
Set the parameters of assistant
Protocol type: TCP_Client
Remote host address: 192.168.237.134
Remote host port: 2048

Click to connect
You can see the connection information in the data log (the assistant in the picture above is the picture taken after the connection)
At the same time, we can use netstat -anop | grep 2048 to check the port usage.

You can see that the connection has been successful. What our code creates is a server, and what is run by the assistant program is a client. In this way, we realize the TCP connection between the client and the server.
LISTEN and ESTABLISHED displayed in the fifth column are the connection status of TCP. TCP has 11 connection statuses. For details, you can read the article written by this blogger:
https://blog.csdn.net/weixin_44560620/article/details/116449785

3. Implement communication between the program and assistant

The multi-io.c code is as follows:

#include <sys/socket.h> // Contains header files for socket series functions
#include <errno.h> // Contains header files for error handling
#include <netinet/in.h> // Header file containing the IPv4 address structure
#include <stdio.h>
#include <string.h>
#include <unistd.h> //close
#include <fcntl.h> //open (optional)

//Create TCP Socket
int main() {<!-- -->
    //Create socket descriptor
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    //Define server address structure
    struct sockaddr_in serveraddr;
    
    // Clear address structure
    memset( & amp;serveraddr, 0, sizeof(struct sockaddr_in));

    // Specify the address family as IPv4
    serveraddr.sin_family = AF_INET;
    
    //Specify any local address
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    //Specify the port number as 2048
    serveraddr.sin_port = htons(2048);

    // Bind the socket to the specified address
    if (-1 == bind(sockfd, (struct sockaddr*) & amp;serveraddr, sizeof(struct sockaddr))) {<!-- -->
        perror("bind"); // Print error message
        return -1; // Return error code
    }

    // Listening socket, the maximum number of connections allowed is 10
    listen(sockfd, 10);

    //Accept client connection
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    int clientfd = accept(sockfd, (struct sockaddr*) & amp;clientaddr, & amp;len);
    printf("accept\\
");

    // Receive data
    char buffer[128] = {<!-- -->0};
    int count = recv(clientfd, buffer, 128, 0);
    
    // send data
    send(clientfd, buffer, count, 0);

    //Print related information
    printf("sockfd: %d, clientfd: %d, count: %d, buffer: %s\\
", sockfd, clientfd, count, buffer);

    getchar(); // Wait for user input

    // close connection
    close(clientfd);

    return 0;
}

The code structure here is similar to the above, and comments are added after each piece of code, so I won’t go into details here.
We recompile and run the code and use the assistant to connect, and then we send the data on the assistant.

We can observe the data information we sent on the terminal. At the same time, we re-sent the data information back to the assistant. On the assistant, we can see the received data information (the information sent to the assistant here is for verification, proving that we successfully received the information sent from the assistant. In fact, through Printing can also be verified, but here I have added an extra layer).

But at the same time, we also found that when we click send again, the data will not be received. This is because our code currently only accepts one cycle of reception. So how to achieve multiple reception of information? In fact, the simplest way is that we Add a while loop to it.
Supplementary
Why are the values of sockfd and clientfd file descriptors 3 and 4 respectively?
This is because the operating system may have assigned file descriptors 0, 1, and 2 to standard input, standard output, and standard error respectively. When you open a socket with socket(AF_INET, SOCK_STREAM, 0) , the operating system allocates the next available file descriptor, which is 3. Then, when you accept a connection using accept(sockfd, (struct sockaddr*) & clientaddr, & len) the operating system allocates the next available file descriptor for the client socket, which is 4.
We can verify it through the ls /dev/std* -l command.
Please add an image description

4. Implement multiple cyclic communications between the program and the assistant

The multi-io.c code is as follows:

#include <sys/socket.h> // Contains header files for socket series functions
#include <errno.h> // Contains header files for error handling
#include <netinet/in.h> // Header file containing the IPv4 address structure
#include <stdio.h>
#include <string.h>
#include <unistd.h> //close
#include <fcntl.h> //open (optional)

//Create TCP Socket
int main() {<!-- -->
    // Create socket descriptor
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    //Define server address structure
    struct sockaddr_in serveraddr;
    
    // Clear address structure
    memset( & amp;serveraddr, 0, sizeof(struct sockaddr_in));

    // Specify the address family as IPv4
    serveraddr.sin_family = AF_INET;
    
    //Specify any local address
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    //Specify the port number as 2048
    serveraddr.sin_port = htons(2048);

    // Bind the socket to the specified address
    if (-1 == bind(sockfd, (struct sockaddr*) & amp;serveraddr, sizeof(struct sockaddr))) {<!-- -->
        perror("bind"); // Print error message
        return -1; // Return error code
    }

    // Listening socket, the maximum number of connections allowed is 10
    listen(sockfd, 10);

    //Accept client connection
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    int clientfd = accept(sockfd, (struct sockaddr*) & amp;clientaddr, & amp;len);
    printf("accept\\
");

#if 0
    //Receive and send data in a single time
    char buffer[128] = {<!-- -->0};
    int count = recv(clientfd, buffer, 128, 0);
    send(clientfd, buffer, count, 0);
    printf("sockfd: %d, clientfd: %d, count: %d, buffer: %s\\
", sockfd, clientfd, count, buffer);

#else
    //Loop to receive and send data
    while (1) {<!-- -->
        char buffer[128] = {<!-- -->0};
        int count = recv(clientfd, buffer, 128, 0);
        send(clientfd, buffer, count, 0);
        printf("sockfd: %d, clientfd: %d, count: %d, buffer: %s\\
", sockfd, clientfd, count, buffer);
    }
#endif

    getchar(); // Wait for user input
    close(clientfd); // Close the connection

    return 0;
}

With the addition of a loop, when we compile and run again, the data will be sent again. At this time, loop reception and communication of data can be realized.


Question
But when we click disconnect at this time, we will also find a problem. Our program falls into an infinite loop. This is because our loop is an infinite loop, there is no escape operation, and the close() function is not called in it.

In addition, we can observe that when falling into an infinite loop, the length of characters received by our program is 0. So we can make the following changes to our program

while(1){<!-- -->
        char buffer[128]={<!-- -->0};
        int count=recv(clientfd,buffer,128,0);
        if(count==0)
        {<!-- -->
            break;
        }
        send(clientfd,buffer,count,0);
        printf("sockfd: %d,clientfd: %d,count: %d,buffer: %s\\
",sockfd,clientfd,count,buffer);
        
    }

Add a break to the loop to terminate it. In fact, according to the original semantics of our code, it would be better to add close() here, but it is also possible to add break based on our current code structure.

4. Implement communication between the program (server) and multiple assistants (clients)

We can first try what will happen if multiple assistants are enabled to communicate with the code at this time. We can find that we can connect, but our program (server) can only communicate with one assistant (client). communication.
I think a simpler way is to use threads to open multiple accepts to receive our client’s programs. Of course, there must be other better ways. We will use this method as an example in the early stage of learning.
The multi-io.c code is as follows:

#include <sys/socket.h> // Contains header files for socket series functions
#include <errno.h> // Contains header files for error handling
#include <netinet/in.h> // Header file containing the IPv4 address structure
#include <stdio.h>
#include <string.h>
#include <unistd.h> //close
#include <fcntl.h> //open (optional)
#include <pthread.h> //Create the header file of the thread

//Create client thread
void *client_thread(void *arg)
{<!-- -->
    // Get the client socket descriptor from the parameter
    int clientfd = *(int *)arg;

    // Loop through client data
    while (1)
    {<!-- -->
        char buffer[128] = {<!-- -->0};
        int count = recv(clientfd, buffer, 128, 0);

        // If the received data length is 0, it means the client closes the connection and exits the loop
        if (count == 0)
        {<!-- -->
            break;
        }

        //Send the received data back to the client
        send(clientfd, buffer, count, 0);
        printf("clientfd: %d, count: %d, buffer: %s\\
", clientfd, count, buffer);
    }

    //Close client connection
    close(clientfd);

    return NULL;
}

//Create TCP Socket
int main()
{<!-- -->
    //Create socket descriptor
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    //Define server address structure
    struct sockaddr_in serveraddr;

    // Clear address structure
    memset( & amp;serveraddr, 0, sizeof(struct sockaddr_in));

    // Specify the address family as IPv4
    serveraddr.sin_family = AF_INET;

    //Specify any local address
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

    //Specify the port number as 2048
    serveraddr.sin_port = htons(2048);

    // Bind the socket to the specified address
    if (-1 == bind(sockfd, (struct sockaddr *) & amp;serveraddr, sizeof(struct sockaddr)))
    {<!-- -->
        perror("bind"); // Print error message
        return -1; // Return error code
    }

    // Listening socket, the maximum number of connections allowed is 10
    listen(sockfd, 10);

    // Loop waiting for client connection
    while (1)
    {<!-- -->
        //Accept client connection
        struct sockaddr_in clientaddr;
        socklen_t len = sizeof(clientaddr);
        int clientfd = accept(sockfd, (struct sockaddr *) & amp;clientaddr, & amp;len);

        //Create a thread to process client data
        pthread_t thid;
        pthread_create( & amp;thid, NULL, client_thread, & amp;clientfd);
    }

    getchar(); // Wait for user input
    // close(clientfd);

    return 0;
}

When we compile and run, we can realize communication between our multiple clients. I set up two clients here to send data to the server respectively.


Message data can be received and sent successfully.

Summary

This article only briefly introduces the basic knowledge of network IO and implements simple network communication through code and assistant tools. We will share IO multiplexing in the next article. Please give me some advice and encourage me if I am inappropriate.