Reference since bi li bi li: www.bilibili.com/video/BV1M7…

One, foreword

In the last article, I showed you what a socket is, what a file descriptor is, and how TCP transfers data. In this article, I’ll focus on BIO, NIO, and IO multiplexing for Linux.

Two, the basic use of Netcat software

Detailed usage: www.tecmint.com/netcat-nc-c…

Netcat (NC) is a powerful network command tool that can perform TCP and UDP related operations in Linux, such as port scanning, port redirection, port listening, and even remote connections

Here, we use NC to simulate a message-receiving server and a message-sending client

1. Install the NC software

sudo yum install -y nc
Copy the code

2. Create a server that listens to port 9999 using nc

Nc-l -p 9999 # -lCopy the code

3. Create a bash and use NC to create a client to send message

nc localhost 9999
Copy the code

Enter the message to be sent on the console and check whether the server receives the message. 4. View the file descriptor in the NC process

Ps - ef | grep nc # # check nc process, it is assumed that, 2603 ls/proc / 2603 / fd 2603 # to check the process of file descriptorsCopy the code



You can see that there is a socket under this process. This is the socket created between the CLIENT and server of the NC



After a series of operations, I believe we have a basic understanding of Netcat software, next to introduce BIO





Strace tracks system calls

Strace software description: it is a software that can track system calls and signals, through which we learn BIO

Environment note: This demo is based on the old version of Linux, because the new version of Linux do not use the BIO, the demo can not be shown


Sudo yum install -y strace # yum install -y strace # Strace -ff -o out nc -l -p 8080 -o indicates that the traced information is output to the named file, in this case outCopy the code


In the directory entered in the previous step, there is an out.pid file, the content is the system call process after the command is executed nc -L -p 9999, use vim command to check

Vim out.92459 # nc The process ID is 92459Copy the code

hereaccept()The method is carried outblocking, it waits for other sockets to connect to it


3. Connect to the client and view the system call

Exit vim and use tail to view

tail -f out.92459
Copy the code

-f parameter: When the file has appended content, it can be printed to the console in real time, which makes it easy to see the system call made after the client connects

nc localhost 8080
Copy the code

Viewing system calls

  • After the client connects, the Accept () method retrieves the client connection and returns file descriptor 4, which is the socket created by the server to communicate with the client
  • After that, the multiplexer poll is used to listen for file descriptors 4 and 0 on the server. 0 is the standard input file descriptor, which file descriptor is read if an event occurs, and if no event occurs, the file descriptor is blocked





4. The client sends a message to view the system call

When the client sends data to the server, the server can listen to the occurrence of an event from the socket and process it accordingly. After processing, the server continues to block and wait for the next event to occur


5. The server sends data to the client to view the system call

The server sends data, which must be input from the keyboard (standard input 0), and reads data from 0 and sends it to Socket 4





4. BIO (Blocking IO)

In the third section, we used strace tool to check the system call in the process of using NC software. In fact, BIO was reflected in the previous section. We summarized a series of system calls above, according to the intuitive understanding of BIO

1. Single-threaded mode

1.1. Process demonstration

1. Start the server



Start server, wait for socket connection, accept() method blocked







2. The client is connected, but no data is sent



Connect to client, accept() executes, no data sent by Client1 is received, read() blocks







3. Connect to another client



The CPU can only process one socket at a time because the read() method is blocked and cannot execute to the Accept () method

1.2. Existing problems

The problem with the above model is that if the client is connected to the server, and the client does not send data, the process is stuck on the read() method, and other clients cannot connect, which means only one client at a time, which is very unfriendly to the client

1.3. How to solve the problem

If a socket is connected, the operating system allocates a thread to handle it, so that the read() method is blocked on each thread without blocking the main thread, and multiple sockets can be accessed


2. Multi-threaded mode

1.1 Process Demonstration

  • The application server only listens for client connections, blocking with Accept ()
  • When client 1 connects to the server, it opens a thread (thread1) to execute the read() method, and the program server continues to listen
  • Client 2 connects to the server, also opens a thread, and executes the read() method
  • Any socket on a thread that has data sent in, read() is immediately read and processed by the CPU


1.2. Existing problems

The above multithreaded model, which seems to be very perfect, actually has a big problem. For every client that comes in, you have to open up a thread. For 10,000 clients, you have to open up 10,000 threads. In the operating system, the user state can not directly open up the thread, need to call the CPU 80 soft interrupt, let the kernel to create a thread, which also involves the user state switch (context switch), very resource consumption.

1.3. How to solve the problem

The first solution is to use a thread pool. This is ok for a small number of clients, but in a large number of clients, you don’t know how big the thread pool should be. If it is too large, it may not have enough memory, and it is not feasible. Because the read() method is blocked, multiple threads need to be opened up. If there is a way to make the read() method not blocked, there is no need to open up multiple threads. This uses another IO model, NIO (non-blocking IO).

NIO (Non-blocking IO)

1. Process demonstration


1. The server has just been created and no client is connected



In NIO, the Accept () method is also non-blocking; it is in a while loop







2. When a client is connected







3. When there is a second client to connect







2, summarize

In NIO mode, everything is non-blocking:

  • The accept() method is non-blocking and returns error if there is no client connection
  • The read() method is non-blocking, returning error if it cannot read data, and blocking only the time it took the read() method to read data





In NIO mode, there is only one thread:

  • When a client connects to a server, the socket is added to an array and iterated over periodically to see if the socket’s read() method can read the data
  • This allows a single thread to handle connections and reads from multiple clients





3. Existing problems

NIO successfully solved the problem that BIO needs to enable multi-threading. In NIO, one thread can solve multiple sockets, which seems perfect, but there are still problems. This model is very useful when there are few clients, but if there are many clients, for example, 10,000 clients are connected, then each loop will traverse 10,000 sockets. If only 10 sockets in 10,000 sockets have data, 10,000 sockets will also be variable, which will do a lot of useless work. The user state determines whether the socket has data or calls the kernel’s read() method. This involves switching between the user state and the kernel state, and switching between the user state and the kernel state is very expensive

Multiplexing (IO Multiplexing)

There are three implementations of IO multiplexing: SELECT, Poll, and epoll. Now let’s take a look at these three implementations

1, select









There are also code examples of the select code implementation

1.1 the advantages

The select is actually the NIO in user mode to traverse the fd array copy to kernel mode, let the kernel state traversal, because the user mode to determine whether a socket has data or to call the kernel mode, after all the copies to kernel mode, without traversing the judgment when switch has been frequent user mode and kernel mode As can be seen from the code, After the select system call, a set of &rset is returned, so that the user state can quickly know which sockets need read data with a very simple binary comparison, effectively improving efficiency

1.2 Existing problems

1, the maximum number of bitmaps can be 1024, and a process can only handle a maximum of 1024 clients. 2, the &rset is not reusable, and the corresponding bit is set every time the socket has data. We still need O(n) traversal


2, poll

2.1 Code Examples



In poll, the file descriptor has a separate data structure pollFD, which is passed in as an array of pollFd. The other implementation logic is the same as select



2.2 the advantages

1, poll pollfd array can be used instead of the bitmap in the select, do not have an array of 1024 limit, can be a management more client 2, when pollfds array has occurred, the corresponding revents setting is 1, traverse the setting back to zero, Pollfd array reuse is implemented

2.3 disadvantages

Pollfds does not tell the user which socket has data. Pollfds does not tell the user which socket has data. Pollfds does not tell the user which socket has data and pollFDS does not tell the user which socket has data

3, epoll


3.1 Code Examples

3.2 Event notification Mechanism

2. The nic initiates an interrupt to the CPU so that the CPU can process the nic first. 3. The callback puts the socket into the ready list

3.3 Detailed Process

First, epoll_create creates the epoll instance. It creates the required red-black tree, the ready list, and the file handle representing the epoll instance. In fact, it creates a memory space in the kernel where all sockets connected to the server are stored. There will also be a space for ready lists; Red-black trees store node data of monitored file descriptors, and ready linked lists store node data of ready file descriptors. Epoll_ctl adds a new descriptor, first checks whether the file descriptor exists in the red-black tree, and returns immediately if so. If not, a new node is inserted into the trunk and the kernel is told to register the callback function. When a file descriptor is received, the kernel inserts that node into the ready list. Epoll_wait will receive the message and copy the data to user space, clearing the list.

3.4 Horizontal trigger and edge trigger

Level_triggered: Epoll_wait () tells the handler to read or write when a read or write event occurs on a monitored file descriptor. Epoll_wait () will also notify you to continue reading and writing from the file descriptor if you do not read or write all the data at once (for example, if the buffer is too small). If the system has a large number of ready file descriptors that you don’t need to read or write, and they return every time, it will greatly reduce the efficiency of the processor to retrieve the ready file descriptors that it cares about!! Edge_triggered: Epoll_wait () notifies the handler to read or write when a read or write event occurs on a monitored file descriptor. Epoll_wait () does not notify you of the next call to the file descriptor if the read/write buffer is too small. It does not notify you until the second read/write event occurs on the file descriptor. This mode is more efficient than horizontal triggering and the system is not flooded with ready file descriptors that you don’t care about!!

3.5 the advantages

Epoll is now the most advanced IO multiplexer, Redis, Nginx and Java NIO in Linux all use epoll. 1. In the life cycle of a socket, there is only one copy from the user state to the kernel state, and the cost is small. Every time there is data in a socket, the kernel is actively notified and added to the ready list, without traversing all sockets