BIO
BIO(blocking I/O): Synchronization is blocked, and the server implementation mode is to connect a thread, that is, when a client has a connection request, the server side needs to start a thread to process it. If this connection does nothing and causes unnecessary thread overhead, it can be improved through the thread pool mechanism (to enable multiple clients to connect to the server)
Combing BIO Programming Process
- The server starts a ServerSocket, registers the port, and calls the accpet method to listen for client Socket connections.
- The client starts the Socket to communicate with the server, and by default the server needs to set up a thread to communicate with each client
Based on communication in BIO mode, client-server is fully synchronized and fully coupled
bio basic network programming
- Single Client: Single Receipt and Issuance
// Client public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1",9999); OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream(os); ps.println("hello world! Hello, server!"); } } // Server public class Server { public static void main(String[] args){ System.out.println("=====Server 1 Start====="); ServerSocket ss; { try { ss = new ServerSocket(9999); // Listening Client Socket socket = ss.accept(); InputStream is = socket.getInputStream(); BufferedReader bis = new BufferedReader(new InputStreamReader(is)); String msg; if ((msg = bis.readLine()) != null ){ System.out.println("Server received:" + msg); } } catch (IOException e) { e.printStackTrace(); } } } }
- Single client: multiple and multiple messages
// Client public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1",9999); OutputStream os = socket.getOutputStream(); Scanner sc = new Scanner(System.in); while (true){ System.out.print("Client 2:"); String msg = sc.nextLine(); PrintStream ps = new PrintStream(os); ps.println(msg); ps.flush(); } } } // Server public class Server { public static void main(String[] args){ System.out.println("=====Server 2 Start====="); ServerSocket ss; { try { ss = new ServerSocket(9999); // Listening Client Socket socket = ss.accept(); InputStream is = socket.getInputStream(); BufferedReader bis = new BufferedReader(new InputStreamReader(is)); String msg; while ((msg = bis.readLine()) != null ){ System.out.println("Server received:" + msg); } } catch (IOException e) { e.printStackTrace(); } } } }
- Multi-client mode
// Thread Tasks public class serverThreadReader extends Thread{ private Socket socket; public serverThreadReader(Socket socket) { this.socket = socket; } @Override public void run() { try { InputStream is = socket.getInputStream(); BufferedReader bis = new BufferedReader(new InputStreamReader(is)); String msg; while ((msg = bis.readLine()) != null ){ System.out.println("Server Received["+socket.toString()+"]: " + msg); } }catch (Exception e){ e.printStackTrace(); } super.run(); } } // Server public class Server { public static void main(String[] args){ System.out.println("=====Server 3 Start====="); ServerSocket ss; { try { ss = new ServerSocket(9999); // Listening Client while (true){ Socket socket = ss.accept(); // Create a separate thread to process client requests new serverThreadReader(socket).start(); } } catch (IOException e) { e.printStackTrace(); } } } } // Client public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1",9999); OutputStream os = socket.getOutputStream(); Scanner sc = new Scanner(System.in); while (true){ System.out.print("Client 3:"); String msg = sc.nextLine(); PrintStream ps = new PrintStream(os); ps.println(msg); ps.flush(); } } }
Pseudo-Asynchronous I/O Programming
-
When the concurrent access of the client increases, the server side will incur 1:1 thread overhead. The larger the number of accesses, the system will overflow the thread stack, and the thread creation will fail, which will eventually cause the process to crash or die, thus making it impossible to provide services to the outside world.
-
A pseudo-asynchronous I/O communication framework, implemented using thread pools and task queues, encapsulates the client's Socket as a Task (which implements the java.lang.Runnable thread task interface) and handles it to the back-end thread pool when the client accesses.
-
JDK's thread pool maintains a message queue and N active threads to process Socket tasks in the message queue. Since the thread pool can set the size of the message queue and the maximum number of threads, its resource usage is controlled and it will not cause resource depletion or downtime regardless of how many concurrent clients access it has.
// Thread pool execution task public class HandlerSockerServerPool { private ExecutorService executorService; // Initialize thread pool object when creating class object public HandlerSockerServerPool(int maxThreadNum, int queueSize){ executorService = new ThreadPoolExecutor(3,maxThreadNum,120, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize)); } public void execute(Runnable target){ executorService.execute(target); } } // Thread Tasks public class ServerRunnableTarget implements Runnable{ private Socket socket; public ServerRunnableTarget(Socket socket){ this.socket = socket; } @Override public void run() { try { InputStream is = socket.getInputStream(); BufferedReader bis = new BufferedReader(new InputStreamReader(is)); String msg; while ((msg = bis.readLine()) != null ){ System.out.println("Server Received:"+"["+Thread.currentThread().getName()+"]["+socket.toString()+"]: " + msg); } }catch (Exception e){ e.printStackTrace(); } } } // Server public class Server { public static void main(String[] args){ System.out.println("=====Server 4 Start-Pseudo-asynchronous====="); try { ServerSocket ss = new ServerSocket(9999); HandlerSockerServerPool pool = new HandlerSockerServerPool(3,10); while (true){ Socket socket = ss.accept(); Runnable target = new ServerRunnableTarget(socket); pool.execute(target); } }catch (Exception e){ e.printStackTrace(); } } } // Client public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1",9999); OutputStream os = socket.getOutputStream(); Scanner sc = new Scanner(System.in); while (true){ System.out.print("Client 4:"); String msg = sc.nextLine(); PrintStream ps = new PrintStream(os); ps.println(msg); ps.flush(); } } }
File Upload
// Thread Tasks public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { // Get data input stream to read data sent by client DataInputStream dis = new DataInputStream(socket.getInputStream()); // Read file types sent by clients String suffix = dis.readUTF(); System.out.println("The server receives the file type:" + suffix); // Define a byte input pipeline String fileName = UUID.randomUUID().toString() + suffix; OutputStream os = new FileOutputStream(fileName); byte[] buffer = new byte[1024]; int len; while ((len = dis.read(buffer))> 0){ os.write(buffer, 0, len); } os.close(); System.out.println(String.format("Server received file saved successfully! file: [%s]",fileName)); } catch (IOException e) { e.printStackTrace(); } } } // Server public class Server { public static void main(String[] args){ System.out.println("=====Server Start: file====="); ServerSocket ss = null; try { ss = new ServerSocket(8888); while (true){ Socket socket = ss.accept(); new ServerReaderThread(socket).start(); } } catch (IOException e) { e.printStackTrace(); } } } // Client public class Client { public static void main(String[] args){ try { Socket socket = new Socket("127.0.0.1", 8888); DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF(".png"); FileInputStream is = new FileInputStream("mv.png"); byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer))>0){ dos.write(buffer,0,len); } dos.flush(); socket.shutdownOutput(); } catch (IOException e) { e.printStackTrace(); } } }
Port Forwarding
// Thread Tasks public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { // Get data input stream to read data sent by client BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String msg; while ((msg = br.readLine()) != null){ System.out.println("Server Received["+socket.toString()+"]msg: "+msg); sendMsgToAllClient(msg); } } catch (IOException e) { System.out.println(String.format("Someone is currently offline:[%s]", socket.toString())); Server.allSocketOnLine.remove(socket); } } private void sendMsgToAllClient(String msg) throws IOException { for (Socket sk : Server.allSocketOnLine) { PrintStream ps = new PrintStream(sk.getOutputStream()); ps.println(socket.toString() + ":" + msg); ps.flush(); } } } // Server public class Server { public static List<Socket> allSocketOnLine = new ArrayList<>(); public static void main(String[] args){ System.out.println("=====Server Start: Port Forwarding====="); ServerSocket ss = null; try { ss = new ServerSocket(8888); while (true){ Socket socket = ss.accept(); // Logon Client Add to List System.out.println("["+socket.toString()+"]Go online..."); allSocketOnLine.add(socket); new ServerReaderThread(socket).start(); } } catch (IOException e) { e.printStackTrace(); } } } // Client // --Receiving message client public class Client { public static void main(String[] args){ try { Socket socket = new Socket("127.0.0.1", 8888); while (true){ BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String msg; while ((msg = br.readLine()) != null){ System.out.println("Client receives msg: \n" + msg); } } } catch (IOException e) { e.printStackTrace(); } } } // - Sending message client public class SendMsgClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1",8888); OutputStream os = socket.getOutputStream(); Scanner sc = new Scanner(System.in); while (true){ System.out.print("Client:"); String msg = sc.nextLine(); PrintStream ps = new PrintStream(os); ps.println(msg); ps.flush(); } } }
NIO
- Java NIO (New IO)Also known as java non-blocking IO is a new IO API introduced from Java version 1.4 that can replace standard Java IOAPI. NIO has the same function and purpose as the original IO, but it is used in a completely different way. NIO supports buffer-oriented, channel-based IO operations. NIO will read and write files in a more efficient way. NIO can be understood as non-blocking IO.Traditional IO reads and writes can only block execution. Threads cannot do anything else during IO reads and writes, such as calling socket.read(), if the server has no data to transmit, the thread will always block, and sockets can be configured in NIO as non-blocking mode.
- NIO-related classes are placed under java.nio packages and subpackages, and many of the classes in the original java.io package are overwritten.
- NIO has three core parts: Channel, Buffer, Selector.
- Java NIO's non-blocking mode allows a thread to send requests or read data from a channel, but it only gets data that is currently available. If no data is available, it will get nothing, not keep the thread blocked, so the thread can continue to do other things until the data becomes readable.The same is true for non-blocking writes, where a thread requests to write some data to a channel without waiting for it to write completely, and the thread can do something else at the same time.
- Common sense: NIO can handle multiple operations with one thread. Assuming there are 1000 requests coming, 20 or 80 threads can be allocated to handle them as appropriate. Unlike previous blocking IOs, 1000 are not allocated for non-scoring purposes.
bio versus nio
NIO | BIO |
---|---|
Buffer Oriented | Stream Oriented |
Non Blocking IO | Blocking IO |
Selectors |
NIO Three Cores:
NIO has three core parts: Channel, Buffer, Selector.
- Each channel corresponds to a Buffer
- One thread corresponds to a Selector, and one Selector corresponds to multiple channels (connections)
- Which channel the program switches to is determined by the event
- Selector switches between channels based on different events
- Buffer is a block of memory, the bottom is an array
- Data is read and written by Buffer. In BIO, either input stream or output stream cannot be bidirectional, but NIO's Buffer can be read or written.
- The core of the Java NIO system is Channel and Buffer. Channel means open to IO devices (e.g., files, sockets)Connection. If you need to use a NIO system, you need to get the channels used to connect the IO devices and the buffers used to hold the data. Then you can manipulate the buffers to process the data. In short, Channel is responsible for transmitting and Buffer is responsible for accessing the data.
Buffer
Buffers are essentially a block of memory that can be written to and then read from. This memory is wrapped as a NIO Buffer object and provides a set of methods for accessing the block of memory. The Buffer API is easier to operate and manage than operating directly on arrays.
Buffer class and its subclasses
-
ByteBuffer,CharBuffer ,ShortBuffer ,IntBuffer ,LongBuffer ,FloatBuffer ,DoubleBuffer
-
Get the corresponding buffer object
static xxBuffer.allocate(int capacity) : Create a capacity of capacity Of xxBuffer object
Basic Properties
- Capacity: As a block of memory, a Buffer has a fixed size, also known as "capacity". Buffer capacity cannot be negative and cannot be changed after it is created.
- Limit: Indicates the size of operational data in a buffer (data cannot be read or written after a limit). The limit of a buffer cannot be negative and cannot be larger than its capacity.
- Write mode, limit equal to buffer capacity.
- In read mode, limit equals the amount of data written.
- Position: The index of the next data to be read or written. Buffer position cannot be negative and cannot be greater than its limit
- Mark and reset: A tag is an index that specifies a particular position in a Buffer by the mark() method in the Buffer and can then be restored to that position by calling the reset() method.
Tags, locations, constraints, capacities follow the following invariants: 0 <= mark <= position <= limit <= capacity
Common api
Buffer clear() Empty the buffer and return a reference to it Buffer flip() To set the bounds of the buffer to the current position and charge the current position with 0 int capacity() Return Buffer Of capacity Size boolean hasRemaining() Determine if there are elements in the buffer int limit() Return Buffer Boundaries(limit) Location Buffer limit(int n) Set buffer bounds to n, And returns a new limit Buffer object for Buffer mark() Marking Buffers int position() Returns the current position of the buffer position Buffer position(int n) The current location where the buffer will be set is n , And returns the modified Buffer object int remaining() Return position and limit Number of elements between Buffer reset() Place position Go to previously set mark Location Buffer rewind() Set position to 0, unset mark //---------- Common api for data manipulation Buffer All subclasses provide two methods for data manipulation: get()put() Method Get Buffer Data in get() : Read a single byte get(byte[] dst): Bulk read multiple bytes to dst in get(int index): Read bytes at specified index locations(Will not move position) Put in data to Buffer Middle put(byte b): Writes a given single byte to the current location of the buffer put(byte[] src): take src The current position in which bytes are written to the buffer put(int index, byte b): Writes the specified byte to the index position of the buffer(Will not move position)
Channel
Java NIO's channels are similar to streams, but they differ in that they can read data from and write data to the channel. However, streams (input or output) are usually one-way read and write. Channels can read and write non-blocking channels, channels can support read or write buffers, and channels can support read and write asynchronously.
- NIO's channels are similar to streams, but there are some differences:
- Channels can read and write simultaneously, while streams can read or write only
- Channels can read and write data asynchronously
- Channels can read or write data from a buffer to a buffer:
- stream in BlO is one-way, for example, FileInputStream objects can only read data, whereas channels in NIO are bidirectional and can be read or written.
- Channel is an interface in NIO
public interface channel extends closeable{
Common Channel implementation classes
- FileChannel: A channel for reading, writing, mapping, and manipulating files.
- DatagramChannel: A data channel in a UDP read-write network.
- SocketChannel: Reads and writes data from the network over TCP.
- ServerSocketChannel: You can listen for new incoming TCP connections and create a SocketChannel for each new incoming connection.
[ServerSocketChanne is like ServerSocket, SocketChannel is like Socket]
Common api
int read(ByteBuffer dst) from Channel Read data to ByteBuffer long read(ByteBuffer[] dsts) take Channel The data in is "scattered" to ByteBuffer[] int write(ByteBuffer src) take ByteBuffer Write data from to Channel long write(ByteBuffer[] srcs) take ByteBuffer[] "Aggregate" data from Channel long position() Return the file location for this channel FileChannel position(long p) Set the file location for this channel long size() Returns the current size of the file for this channel FileChannel truncate(long s) Intercept the file for this channel to a given size void force(boolean metaData) Force all file updates to this channel to be written to the storage device
File Operation
- Read and write operations
// Write File @Test public void write(){ try { FileOutputStream fos = new FileOutputStream("data01.txt"); // Get channel FileChannel channel = fos.getChannel(); // Define Buffers ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put("hello,world!".getBytes()); // Change Buffer to Write Mode buffer.flip(); // Write data through channel channel.write(buffer); channel.close(); System.out.println("Write data to file!"); } catch (Exception e) { e.printStackTrace(); } } // read file @Test public void read() { try { FileInputStream fis = new FileInputStream("data01.txt"); // Get channel FileChannel channel = fis.getChannel(); // Define Buffers ByteBuffer buffer = ByteBuffer.allocate(1024); // Read data to buffer channel.read(buffer); buffer.flip(); String rs = new String(buffer.array(), 0, buffer.remaining()); System.out.println("Read file data:"+rs); } catch (Exception e) { e.printStackTrace(); } }
- File Copy
@Test public void copy() { try { File srcFile = new File("1f913498-65f5-4683-98fe-1a9025a2bd87.png"); File destFile = new File("new_1f913498-65f5-4683-98fe-1a9025a2bd87.png"); FileInputStream fis = new FileInputStream(srcFile); FileOutputStream fos = new FileOutputStream(destFile); FileChannel isChannel = fis.getChannel(); FileChannel osChannel = fos.getChannel(); // Allocation Buffer ByteBuffer buffer = ByteBuffer.allocate(1024); while (true) { // Clear the buffer before reading data buffer.clear(); // Start reading data once int flag = isChannel.read(buffer); if (flag == -1) { break; } // Switch buffer to read mode after reading data buffer.flip(); // Data Write Out osChannel.write(buffer); } isChannel.close(); osChannel.close(); System.out.println("Copy complete!"); } catch (Exception e) { e.printStackTrace(); } }
- Distributed Read, Clustered Write
//Dispersion and Aggregation @Test public void test() throws IOException { RandomAccessFile raf1 = new RandomAccessFile("data01.txt", "rw"); //1.Get Channel FileChannel channel1 = raf1.getChannel(); //2.Allocate buffer of specified size ByteBuffer buf1 = ByteBuffer.allocate(10); ByteBuffer buf2 = ByteBuffer.allocate(1024); //3.Distributed Read ByteBuffer[] bufs = {buf1, buf2}; channel1.read(bufs); for (ByteBuffer byteBuffer : bufs) { byteBuffer.flip(); } System.out.println(new String(bufs[0].array(), 0, bufs[0].limit())); System.out.println("-----------------"); System.out.println(new String(bufs[1].array(), 0, bufs[1].limit())); //4.Aggregate Write RandomAccessFile raf2 = new RandomAccessFile("data02.txt", "rw"); FileChannel channel2 = raf2.getChannel(); channel2.write(bufs); } // --- Result hello,worl ----------------- d!
- File copy: nio built-in method
@Test public void transfer() throws Exception { // 1. Byte Input Pipeline FileInputStream is = new FileInputStream("data01.txt"); FileChannel isChannel = is.getChannel(); // 2. Byte Output Stream Pipeline FileOutputStream fos = new FileOutputStream("data03.txt"); FileChannel osChannel = fos.getChannel(); // 3. Replication osChannel.transferFrom(isChannel,isChannel.position(),isChannel.size()); isChannel.close(); osChannel.close(); } @Test public void transferTo() throws Exception { // 1. Byte Input Pipeline FileInputStream is = new FileInputStream("data01.txt"); FileChannel isChannel = is.getChannel(); // 2. Byte Output Stream Pipeline FileOutputStream fos = new FileOutputStream("data04.txt"); FileChannel osChannel = fos.getChannel(); // 3. Replication isChannel.transferTo(isChannel.position() , isChannel.size() , osChannel); isChannel.close(); osChannel.close(); }
Selector
Selector is a Java NIO component that can examine one or more NIO channels and determine which channels are ready for reading or writing. This allows a single thread to manage multiple channels, thereby managing multiple network connections and improving efficiency.
A selector is a multiplexer of the SelectableChannle object, which can monitor the IO status of multiple SelectableChannel s simultaneously, that is, it allows a single thread to manage multiple Channels. Selector is the core of non-blocking lO
- Java NOI, in a non-blocking IO fashion. Selector can be used to handle multiple client connections with one thread
- Selector can detect whether events occur on multiple registered channels (Note: Multiple Channel s can be registered as events to the same Selector), if events occur, get the events and process them accordingly for each event. This allows you to manage multiple channels with a single thread, that is, manage multiple connections and requests.
- Reading and writing occurs only when the connection/channel has a real read/write event, greatly reducing overhead and eliminating the need to create a thread for each connection and maintaining multiple threads
- Avoid overhead caused by context switching between threads
Selector application
Create Selector: Create a Selector by calling the Selector.open() method.
Selector selector = Selector.open();
Register channel with selector: SelectableChannel.register(Selector sel, int ops)
//1. Get Channels ServerSocketChannel ssChannel = ServerSocketChannel.open(); //2. Switch non-blocking mode ssChannel.configureBlocking(false); //3. Binding Connections ssChannel.bind(new InetSocketAddress(9898)); //4. Get the selector Selector selector = Selector.open(); //5. Register the channel with the selector and specify "Listen for Receive Events" ssChannel.register(selector, SelectionKey.OP_ACCEPT);
When register(Selector sel, int ops) is called to register a channel with the selector, the event that the selector listens to for the channel needs to be specified by the second parameter ops. The type of event that can be listened on (represented by four constants that can use SelectionKey):
- Read: SelectionKey.OP_READ(1)
- Write: SelectionKey.OP_WRITE (4)
- Connection: SelectionKey.OP_CONNECT(8)
- Receive: SelectionKey.OP_ACCEPT(16)
- If you are registering to listen for more than one event, you can use the Bit Or operator to connect.
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE
Principle analysis of NIO non-blocking network communication
Selector implements: an I/O thread can concurrently handle N client connections and read-write operations, which fundamentally resolves the traditional one-way synchronous blocking I/O connection model, and greatly improves the performance, resilience and reliability of the architecture.
Service-side process
When the client connects to the server, the server gets the SocketChannel through ServerSocketChannel:
-
1. Getting Channels
ServerSocketChannel ssChannel = ServerSocketChannel.open();
-
2. Switch non-blocking mode
ssChannel.configureBlocking(false);
-
3. Binding Connections
ssChannel.bind(new InetSocketAddress(9999));
-
4. Get Selector
Selector selector = Selector.open();
-
5. Register the channel with the selector and specify "Listen for Receive Events"
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
-
6. Polling Acquire Selector Events "Ready"
//"Ready" events on the polling get selector while (selector.select() > 0) { System.out.println("Round by round"); //7.Gets all registered Selection Keys (Ready Listening Events) in the current selector Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { //8.Getting ready is an event SelectionKey sk = it.next(); //9.Determine exactly what event is ready if (sk.isAcceptable()) { //10.Get Client Connection When Received SocketChannel sChannel = ssChannel.accept(); //11.Switch non-blocking mode sChannel.configureBlocking(false); //12.Register the channel on the selector sChannel.register(selector, SelectionKey.OP_READ); } else if (sk.isReadable()) { //13.Gets the channel in Ready state on the current selector SocketChannel sChannel = (SocketChannel) sk.channel(); //14.Read data ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0; while ((len = sChannel.read(buf)) > 0) { buf.flip(); System.out.println(new String(buf.array(), 0, len)); buf.clear(); } } //15.Unselect Key SelectionKey it.remove(); } } }
Client Process
-
Get Channel
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
-
Switch non-blocking mode
sChannel.configureBlocking(false);
-
Allocate buffer of specified size
ByteBuffer buf = ByteBuffer.allocate(1024);
-
Send data to server
Scanner scan = new Scanner(System.in); while(scan.hasNext()){ String str = scan.nextLine(); buf.put((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(System.currentTimeMillis()) + "\n" + str).getBytes()); buf.flip(); sChannel.write(buf); buf.clear(); } //Close Channel sChannel.close();
Getting Started Cases
Requirements: The server receives connection requests from clients and receives events from multiple clients.
/** * Client */ public class Client { public static void main(String[] args) throws Exception { //1.Get Channel SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999)); //2.Switch non-blocking mode sChannel.configureBlocking(false); //3.Allocate buffer of specified size ByteBuffer buf = ByteBuffer.allocate(1024); //4.Send data to server Scanner scan = new Scanner(System.in); while(scan.hasNext()){ String str = scan.nextLine(); buf.put((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(System.currentTimeMillis()) + "\n" + str).getBytes()); buf.flip(); sChannel.write(buf); buf.clear(); } //5.Close Channel sChannel.close(); } } /** * Server */ public class Server { public static void main(String[] args) throws IOException { //1.Get Channel ServerSocketChannel ssChannel = ServerSocketChannel.open(); //2.Switch non-blocking mode ssChannel.configureBlocking(false); //3.Binding Connection ssChannel.bind(new InetSocketAddress(9999)); //4.Get Selector Selector selector = Selector.open(); //5.Register the channel on the selector and specify "Listen for Receive Events" ssChannel.register(selector, SelectionKey.OP_ACCEPT); //6."Ready" events on the polling get selector while (selector.select() > 0) { System.out.println("Round by round"); //7.Gets all registered Selection Keys (Ready Listening Events) in the current selector Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { //8.Getting ready is an event SelectionKey sk = it.next(); //9.Determine exactly what event is ready if (sk.isAcceptable()) { //10.Get Client Connection When Received SocketChannel sChannel = ssChannel.accept(); //11.Switch non-blocking mode sChannel.configureBlocking(false); //12.Register the channel on the selector sChannel.register(selector, SelectionKey.OP_READ); } else if (sk.isReadable()) { //13.Gets the channel in Ready state on the current selector SocketChannel sChannel = (SocketChannel) sk.channel(); //14.Read data ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0; while ((len = sChannel.read(buf)) > 0) { buf.flip(); System.out.println(new String(buf.array(), 0, len)); buf.clear(); } } //15.Unselect Key SelectionKey it.remove(); } } } }
Group Chat System
- Write a NIO group chat system to achieve client-to-client communication needs (non-blocking)
- Server side: can monitor users online, offline, and realize message forwarding function
- Client: channel allows non-blocking messages to be sent to all other client users, while receiving messages forwarded by other client users through the server
Server
public class Server { //Define Properties private Selector selector; private ServerSocketChannel ssChannel; private static final int PORT = 9999; //constructor //Initialization Work public Server() { try { // 1. Getting Channels ssChannel = ServerSocketChannel.open(); // 2. Switch to non-blocking mode ssChannel.configureBlocking(false); // 3. Bind the port of the connection ssChannel.bind(new InetSocketAddress(PORT)); // 4. Get Selector selector = Selector.open(); // 5. Register channels on selectors and start specifying to listen for receive events ssChannel.register(selector , SelectionKey.OP_ACCEPT); }catch (IOException e) { e.printStackTrace(); } } //Monitor public void listen() { System.out.println("Listen Thread: " + Thread.currentThread().getName()); try { while (selector.select() > 0){ System.out.println("Start a round of event handling~~~"); // 7. Get ready events in all registered channels in the selector Iterator<SelectionKey> it = selector.selectedKeys().iterator(); // 8. Start traversing these prepared events while (it.hasNext()){ // Extract the current event SelectionKey sk = it.next(); // 9. Determine exactly what this event is if(sk.isAcceptable()){ // 10. Direct access to the current client channel SocketChannel schannel = ssChannel.accept(); // 11. Switch to non-blocking mode schannel.configureBlocking(false); // 12. Register this client channel with the selector System.out.println(schannel.getRemoteAddress() + " Go online "); schannel.register(selector , SelectionKey.OP_READ); //Tips }else if(sk.isReadable()){ //Processing Read (Write-only method.) readData(sk); } it.remove(); // The current event needs to be removed after processing is complete } } }catch (Exception e) { e.printStackTrace(); }finally { //Exception handling occurred... } } //Read client messages private void readData(SelectionKey key) { //Get associated channle SocketChannel channel = null; try { //Get channel channel = (SocketChannel) key.channel(); //Create buffer ByteBuffer buffer = ByteBuffer.allocate(1024); int count = channel.read(buffer); //Processing based on count value if(count > 0) { //Convert data from cache to string String msg = new String(buffer.array()); //Output this message System.out.println("form Client: " + msg); //Forward messages to other clients (remove yourself) and write a specific way to handle them sendInfoToOtherClients(msg, channel); } }catch (IOException e) { try { System.out.println(channel.getRemoteAddress() + " Offline.."); e.printStackTrace(); //Unregister key.cancel(); //Close Channel channel.close(); }catch (IOException e2) { e2.printStackTrace();; } } } //Forward messages to other customers (channels) private void sendInfoToOtherClients(String msg, SocketChannel self ) throws IOException{ System.out.println("Server Forwarding Message..."); System.out.println("Server forwards data to client thread: " + Thread.currentThread().getName()); //Traverse all SocketChannel s registered on selector and exclude self for(SelectionKey key: selector.keys()) { //Remove the corresponding SocketChannel by key Channel targetChannel = key.channel(); //Exclude yourself if(targetChannel instanceof SocketChannel && targetChannel != self) { //Transformation SocketChannel dest = (SocketChannel)targetChannel; //Store msg in buffer ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); //Write buffer's data to channel dest.write(buffer); } } } public static void main(String[] args) { //Create Server Object Server groupChatServer = new Server(); groupChatServer.listen(); } }
Client
package com.itheima.chat; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Scanner; public class Client { //Define related properties private final String HOST = "127.0.0.1"; // ip of server private final int PORT = 9999; //Server Port private Selector selector; private SocketChannel socketChannel; private String username; //Constructor, complete initialization public Client() throws IOException { selector = Selector.open(); //Connect to Server socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT)); //Set up non-blocking socketChannel.configureBlocking(false); //Register channel with selector socketChannel.register(selector, SelectionKey.OP_READ); //Get username username = socketChannel.getLocalAddress().toString().substring(1); System.out.println(username + " is ok..."); } //Send message to server public void sendInfo(String info) { info = username + " Say:" + info; try { socketChannel.write(ByteBuffer.wrap(info.getBytes())); }catch (IOException e) { e.printStackTrace(); } } //Read messages replied from the server public void readInfo() { try { int readChannels = selector.select(); if(readChannels > 0) {//Available channels Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if(key.isReadable()) { //Get Related Channels SocketChannel sc = (SocketChannel) key.channel(); //Get a Buffer ByteBuffer buffer = ByteBuffer.allocate(1024); //read sc.read(buffer); //Convert read buffer data to string String msg = new String(buffer.array()); System.out.println(msg.trim()); } } iterator.remove(); //Remove the current selectionKey to prevent duplicate operations } else { //System.out.println("No available channel..."); } }catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws Exception { //Start our client Client chatClient = new Client(); //Start a thread, every 3 seconds, to read and send data from the server new Thread() { public void run() { while (true) { chatClient.readInfo(); try { Thread.currentThread().sleep(3000); }catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); //Send data to server Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { String s = scanner.nextLine(); chatClient.sendInfo(s); } } }
AIO
- Java AIO(NIO.2): Asynchronous, non-blocking, server implementation mode is a valid request for a thread, and client I/O requests are processed by the OS before notifying the server application to start the thread.
AIO Asynchronous non-blocking, based on NIO Can be called NIO2.0 BIO NIO AIO Socket SocketChannel AsynchronousSocketChannel ServerSocket ServerSocketChannel AsynchronousServerSocketChannel
Unlike NIO, when performing read and write operations, only the read or write method of the API needs to be called directly. Both methods are asynchronous. For read operations, the operating system passes readable streams into the buffer of the read method when there is stream readable.For write operations, the operating system actively notifies the application when the stream passed by the write method has been written to
This means that read/write methods are asynchronous and call callback functions on their own initiative when they are finished. In JDK1.7, this section is called NIO.2 and adds the following four asynchronous channels mainly under the Java.nio.channels package:
AsynchronousSocketChannel AsynchronousServerSocketChannel AsynchronousFileChannel AsynchronousDatagramChannel
summary
BIO,NIO,AIO:
- Java BIO: Synchronized and blocked, the server implementation mode is a connection thread, that is, the server side needs to start a thread to process when the client has a connection request. If this connection does nothing, it will cause unnecessary thread overhead, which can of course be improved through the thread pool mechanism.
- Java NIO: Synchronous non-blocking, server implementation mode is a request for a thread, that is, connection requests sent by clients are registered on the multiplexer, which starts a thread to process when the multiplexer polls for I/O requests on the connection.
- Java AIO(NIO.2): Asynchronous, non-blocking, server implementation mode is a valid request for a thread, and client I/O requests are processed by the OS before notifying the server application to start the thread.
BIO, NIO, AIO Scenario Analysis:
- The BIO approach is suitable for architectures with relatively small and fixed number of connections. It requires more server resources and is confined to applications. It was the only option before JDK1.4, but the program is intuitive and easy to understand.
- NIO is suitable for architectures with many connections and short connections (light operation), such as chat servers, which are confined to applications with concurrency, complex programming, and JDK1.4 support.
- AIO is used in architectures with a large number of connections and long connections (re-operation), such as album servers, fully invoking OS to participate in concurrent operations, complex programming, JDK7 support. Netty!