“This is the 25th day of my participation in the November Gwen Challenge. See details of the event: The Last Gwen Challenge 2021”.
Like again, form a habit 👏👏
IO model
IO model simply means what kind of channel is used to send and receive data. Java supports three NETWORK programming IO modes: BIO, NIO and AIO.
BIO (Blocking IO)
BIO is a synchronous blocking model, where each client connection corresponds to one processing thread. The typical model is as follows:
BIO server example code
public class SocketServer {
static ExecutorService threadPool =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
while(true){
System.out.println("Waiting for the client to connect...");
// Block method, if there is no client connection will always block here
Socket clientSocket = serverSocket.accept();
System.out.println("A client is connected...");
// Single thread mode
handler(clientSocket);
// Multithreaded
// threadPool.execute(() -> {
// try {
// handler(clientSocket);
// } catch (IOException e) {
// e.printStackTrace();
/ /}
/ /});}}private static void handler(Socket clientSocket) throws IOException {
byte[] bytes = new byte[12];
System.out.println("Ready to read...");
// Receive data from client, block method, block when there is no data to read
int read = clientSocket.getInputStream().read(bytes);
System.out.println("Read complete...");
if(read ! = -1){
System.out.println("Received data from client:" + new String(bytes,0,read));
}
clientSocket.getOutputStream().write("HelloClient".getBytes()); clientSocket.getOutputStream().flush(); }}Copy the code
BIO client sample code
public class SocketClient {
public static void main(String[] args) throws IOException {
// Address and port to connect to the server
Socket socket = new Socket("localhost".8888);
// Send data to the server
socket.getOutputStream().write("Bio Client Tests".getBytes());
socket.getOutputStream().flush();
System.out.println("Sending data to the server end!!!!!");
byte[] bytes = new byte[1024];
// Receives data from the server
socket.getInputStream().read(bytes);
System.out.println("Data received from server:" + newString(bytes)); socket.close(); }}Copy the code
disadvantages
Due to the accept () method is a blocking, if there is no client connection, will have been blocked, and in the case of a single thread due to read is a blocking operation, if the connection is not do data read and write operations will lead to thread blocking, waste of resources, but also in other client don’t come, multi-threaded can solve this problem, However, many clients will waste a lot of resources if they are just connected without reading or writing. If there are too many threads, the server will have too many threads and the pressure will be too great, such as THE C10K problem.
Application scenarios
The BIO mode applies to scenarios where the number of connections is small and a large amount of data is sent at a time. This mode has high requirements on server resources and the concurrency is limited to applications. But the program is simple and easy to understand.
NIO (Non Blocking IO)
Synchronous non-blocking, the server implementation mode is that a thread can process multiple requests (connections), the connection request sent by the client will be registered with the multiplexer selector, multiplexer polling to the connection has IO requests to process, JDK1.4 began to introduce.
Application Scenarios:
NIO is suitable for architectures with large number of connections and relatively short connections (light operation), such as chat server, bullet screen system, communication between servers, and programming is complicated.
NIO server code example
public class NioServer {
// Save the client connection
static List<SocketChannel> channelList = new ArrayList<>();
public static void main(String[] args) throws IOException {
// Create a NIO ServerSocketChannel, similar to the BIO serverSocket
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(8888));
// Set ServerSocketChannel to non-blocking
serverSocket.configureBlocking(false);
System.out.println("Service started successfully");
while(true) {// The accept method in non-blocking mode does not block, otherwise it would
//NIO's non-blocking is implemented internally by the Linux kernel's accept function
SocketChannel socketChannel = serverSocket.accept();
if(socketChannel ! =null){
System.out.println("Connection successful");
// Set SocketChannel to non-blocking
socketChannel.configureBlocking(false);
// Save client connections in List
channelList.add(socketChannel);
}
// Traverses the connection for data reading
Iterator<SocketChannel> iterator = channelList.iterator();
while(iterator.hasNext()){
SocketChannel sc = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
// Non-blocking mode The read method does not block, otherwise it would
int len = sc.read(byteBuffer);
// If there is data, print it out
if (len > 0){
System.out.println("Received message:" + new String(byteBuffer.array()));
}else if (len == -1) {// If the client is disconnected, remove the socket from the set
iterator.remove();
System.out.println("Client disconnects");
}
}
}
}
}
Copy the code
Conclusion: if the number of connections too much, there will be a lot of invalid traversal, if there are 10000 links, of which only 1000 connections have write data, but because of the other 9000 connection didn’t disconnect, every time we’re going to the polling traversal ten thousand times, with nine over ten of the traversal is invalid, this is obviously not a very satisfactory condition.
NIO introduces examples of multiplexer code
public class NioSelectorServer {
public static void main(String[] args) throws IOException {
// Create NIO ServerSocketChannel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
// Set ServerSocketChannel to non-blocking
serverSocket.configureBlocking(false);
// Turn on Selector to handle channels, which creates epoll
Selector selector = Selector.open();
// Register a ServerSocketChannel with a Selector that is interested in the client accept connection
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Service started successfully");
while(true) {// Block to wait for an event to occur
selector.select();
// Get SelectionKey instances of all events registered in selector
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// Process the event by iterating through SelectionKey
while(iterator.hasNext()){
SelectionKey key = iterator.next();
// If the event is OP_ACCEPT, the connection is made to obtain the congratulations event registration
if (key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel)key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
// Only read events are registered. If you need to send data to the client, you can register write events
socketChannel.register(selector,SelectionKey.OP_READ);
System.out.println("Client connected successfully");
}else if (key.isReadable()){ // If the event is OP_READ, the read is printed
SocketChannel socketChannel = (SocketChannel)key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = socketChannel.read(byteBuffer);
// If there is data, print it out
if (len > 0){
System.out.println("Received message:" + new String(byteBuffer.array()));
}else if(len == -1) {// If the client disconnects, close the Socket
System.out.println("Client disconnects"); socketChannel.close(); }}// Delete the key from the event set to prevent repeat processing of the next selectiterator.remove(); }}}}Copy the code
NIO has three core components: channels, buffers, and selectors.
1) A channel is similar to a stream. Each channel corresponds to a buffer buffer, and the underlying buffer is an array.
2) A channel is registered with a selector, which sends it to an idle thread for processing according to the occurrence of a channel read or write event.
NIO Buffer and channel are both readable and writable.
NIO in JDK1.4 is implemented using the Linux kernel function select() or poll(). Similar to NioServer code, selector polls all sockchannels each time to see which channel has read or write events. Epoll was introduced in JDK1.5 to optimize NIO based on event response.
The underlying implementation
The following methods are important in the NioSelectorServer code and are understood at the level of Hotspot and Linux kernel functions
Selector.open() // Create a multiplexer
socketChannel.register(selector, SelectionKey.OP_READ) // Register a channel with the multiplexer
selector.select() // Block to wait for an event to occur
Copy the code
Conclusion: The whole call process of NIO is that Java calls the kernel function of the operating system to create the Socket, obtains the file descriptor of the Socket, and then creates a Selector object corresponding to the Epoll descriptor of the operating system. Takes on the Socket connection file descriptor event is bound to the Selector corresponding Epoll file descriptor, for asynchronous notifications of events, this is achieved using a thread, and do not need too many invalid traversal, event handling to the operating system kernel interruption program implementation (operating system), greatly improving the efficiency.
Epoll function details
(1) int epoll_create(int size);
Creates an epoll instance and returns a non-negative number as a file descriptor for all subsequent calls to the epoll interface.
The size parameter may hold size descriptors, but size is not a maximum, just an indication to the operating system of its order of magnitude, and is now largely deprecated.
So I call Selector Selector = Selector. Open (); Executing this statement essentially creates an epoll object, created by calling the operating system function epoll_create.
(2) int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
The op operation is performed on the target file descriptor fd using the epoll instance referenced by the file descriptor EPfd.
The epfd parameter indicates the file descriptor corresponding to epoll, and the fd parameter indicates the file descriptor corresponding to socket.
The op argument has the following values:
- EPOLL_CTL_ADD: Registers a new FD to an EPFD and associates the event with it.
- EPOLL_CTL_MOD: Modifies the listening event of a registered FD.
- EPOLL_CTL_DEL: Removes the fd from the EPFD and ignores the bound event, which can be null.
The event parameter is a structure
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
Copy the code
There are many possible values for events, but here are just a few of the most common:
- EPOLLIN: indicates that the corresponding file descriptor is readable.
- EPOLLOUT: indicates that the corresponding file descriptor is writable.
- EPOLLERR: Indicates that an error occurs in the file descriptor.
Returns 0 on success and -1 on failure
(3) int epoll_wait(int epfd, struct epoll_event *events, int maxEvents, int timeout);
Wait for events on the file descriptor EPfd.
Epfd is the file descriptor corresponding to Epoll, events is the set of all available events for the caller, maxEvents is the maximum number of events to wait before returning, and timeout is the timeout time.
Linux kernel functions (SELECT, poll, epoll) are used to implement THE I/O multiplexing layer. Windows does not support epoll implementation. Windows layer is based on winsock2 select function implementation (not open source).
select | poll | Epoll (JDK 1.5 and above) | |
---|---|---|---|
Mode of operation | traverse | traverse | The callback |
The underlying implementation | An array of | The list | Hash table |
IO efficiency | Each call is linear traversal, order n time. | Each call is linear traversal, order n time. | Whenever an IO event is ready, the system registered callback function will be called, time complexity O(1) |
Maximum connections | Have a cap | There is no upper limit | There is no upper limit |
AIO (NIO 2.0)
Asynchronous non-blocking, the operating system after the completion of the callback notified the server program to start the thread to process, generally applicable to a large number of connections and a long connection time applications.
Application scenarios
AIO mode applies to architectures with a large number of connections and long connections (heavy operations), and is supported in JDK7.
AIO server code example
public class AIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8888));
serverChannel.accept(null.new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
try{
System.out.println("2 --"+Thread.currentThread().getName());
// The client cannot connect to the server if you do not write this line of code
serverChannel.accept(attachment,this);
System.out.println(socketChannel.getRemoteAddress());
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("3--" + Thread.currentThread().getName());
buffer.flip();
System.out.println(new String(buffer.array(),0,result));
socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) { exc.printStackTrace(); }}); }catch(IOException e){ e.printStackTrace(); }}@Override
public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); }}); System.out.println("1--"+Thread.currentThread().getName()); Thread.sleep(Integer.MAX_VALUE); }}Copy the code
AIO client code example
public class AIOClient {
public static void main(String... args) throws Exception {
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1".9000)).get();
socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
ByteBuffer buffer = ByteBuffer.allocate(512);
Integer len = socketChannel.read(buffer).get();
if(len ! = -1) {
System.out.println("Client receives message:" + new String(buffer.array(), 0, len)); }}}Copy the code