Parallel Patterns and Algorithms (2)

Keywords: socket network Programming Java

1. Matrix algorithm

In matrix multiplication, the number of columns of the first matrix and the number of rows of the second matrix must be the same. If parallel computing is needed, a simple strategy is to divide A matrix horizontally, get sub-matrices A 1 and A 2, B matrix vertically, and get sub-matrices B 1 and B 2. At this time, we can get the product of the original matrix A and B only by calculating the product of these sub-matrices and splicing the results. Of course, this process can be repeated. In order to calculate A1*A2, we can further decompose A1 and B1 until we believe that the size of the submatrices is within acceptable range.

Here we use the FockJoin framework to implement the idea of parallel matrix multiplication. In order to facilitate matrix calculation, we use jMatrces open source software as a matrix calculation tool. The main API s used are as follows:

  • Matrix: Represents a matrix
  • Matrix Operator. multiply (Matrix, Matrix): Matrix multiplication
  • Matrix.row(): Get the number of rows of a matrix
  • Matrix.getSubMatrix(): Get the submatrix of the matrix
  • Matrix Operator. horizontal Concatenation (Matrix, Matrix): Horizontal connection of two matrices
  • Matrix Operator. Vertical Concatenation (Matrix, Matrix): Vertical connection of two matrices

Define a task class to compute matrix multiplication. If the input matrix is too granular, task decomposition will be performed again:

public class MatrixMulTask extends RecursiveTask<Matrix> {
	Matrix m1;
	Matrix m2;
	String pos;
	/**
	 * Constructor
	 * @parm m1 Matrix 1
	 * @parm m2 Matrix 2
	 * @parm pos The Position of Product Results in Parent Matrix Multiplication Results
	 */
	public MatrixMulTask(Matrix m1,Matrix m2,String pos) {
		this.m1 = m1;
		this.m2 = m2;
		this.pos = pos;
	}

	@Override
	protected Matrix compute() {
		if(m1.rows() <= MatrixMulTask.granularity || m2.cols()<=MatrixMulTask.granularity) {
			Matrix mRe = MatrixOperator.multiply(m1, m2);
			return mRe;
		} else {
			//Continuous partition matrix
			int rows;
			rows = m1.rows();
			//Lateral Partition of Left Multiplication Matrix
			Matrix m11 = m1.getSubMatrix(1, 1, rows/2, m1.cols());
			Matrix m12 = m1.getSubMatrix(rows/2+1, 1, m1.rows(), m1.cols());
			//Longitudinal Partition of Right Multiplier Matrix
			Matrix m21 = m2.getSubMatrix(1, 1, m2.rows(), m12.cols()/2);
			Matrix m22 = m2.getSubMatrix(1, m2.cols()/2+1, m2.rows(), m2.cols());

			ArrayList<MatrixMulTask> subTasks = new ArrayList<MatrixMulTask>();
			MatrixMulTask tmp = null;
			tmp = new MatrixMulTask(m11, m21, "m1");
			subTasks.add(tmp);
			tmp = new MatrixMulTask(m11, m22, "m2");
			subTasks.add(tmp);
			tmp = new MatrixMulTask(m12, m21, "m3");
			subTasks.add(tmp);
			tmp = new MatrixMulTask(m12, m22, "m4");
			subTasks.add(tmp);
			for(MatrixMulTask t : subTasks) {
				t.fork();
			}
			Map<String, Matrix> matrixMap = new HashMap<String,Matrix>();
			for(MatrixMulTask t :subTasks) {
				matrixMap.put(t.pos, t.join());
			}
			Matrix tmp1 = MatrixOperator.horizontalConcatenation(matrixMap.get("m1"), matrixMap.get("m2"));
			Matrix tmp2 = MatrixOperator.horizontalConcatenation(matrixMap.get("m3"), matrixMap.get("m4"));
			Matrix reM = MatrixOperator.verticalConcatenation(tmp1, tmp2);
			return reM;
		}
	}
}

The member variables M1 and M2 in Matrix MulTask denote the two matrices to be multiplied, and pos denotes the position of the product results in the parent matrix multiplication results, including m1, m2, m3, and m4. Firstly, the matrix is divided into four tasks: m11, m12, m21 and m22, and then the sub-tasks are created. Then these subtasks are computed. Finally, m1, m2, m3, and M4 are joined together to form a new matrix as the final result.

Main function:

public static final int granularity = 3;
public static void main(String[] args) throws InterruptedException, ExecutionException {
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		//Create two 300*300 random matrices
		Matrix m1 = MatrixFactory.getRandomMatrix(100, 100, null);
		Matrix m2 = MatrixFactory.getRandomMatrix(100, 100, null);
		MatrixMulTask task = new MatrixMulTask(m1, m2, null);
		ForkJoinTask<Matrix> result = forkJoinPool.submit(task);
		Matrix pr = result.get();
		System.out.println(pr);
	}

II. Network NIO

Java NIO is short for New IO. The basic contents involved are Channel and Buffer, File IO and Network IO.

2.1 Multithread Mode of Socket-based Server

Here, take the Echo server as an example. For the Echo server, it reads an input from the client and returns it to the client intact. The server will enable a thread for each client connection, and the new thread will serve the client wholeheartedly. In order to accept client connections, the server will also use an additional dispatch thread. The following is the server-side code:

public class MultiThreadEchoServer {
	// Processing each client connection using thread pool
	private static ExecutorService tp = Executors.newCachedThreadPool();
	//HandleMsg thread is defined, which consists of a client Socket. Its task is to read the content of the Socket and
	//Return it back. When the task is completed, the client Socket is shut down normally.
	static class HandleMsg implements Runnable {
		Socket clientSocket;
		public HandleMsg(Socket clientSocket) {
			this.clientSocket = clientSocket;
		}

		@Override
		public void run() {
			BufferedReader is = null;
			PrintWriter os = null;
			try {
				is = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
				os = new PrintWriter(clientSocket.getOutputStream(),true);
				String inputLine = null;
				long b = System.currentTimeMillis();
				while((inputLine = is.readLine()) != null) {
					os.println(inputLine);
				}
				// Statistics and outputs the time spent by the server thread in processing a client request (including the time spent reading and writing back data)
				long e = System.currentTimeMillis();
				System.out.println("spend:" + (e-b)+"ms");

			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if(is!=null) is.close();
					if(os!=null) os.close();
					clientSocket.close();
				} catch (Exception e2) {
					e2.printStackTrace();
				}
			}
		}
	}

	public static void main(String[] args) {
		ServerSocket echoServer = null;
		Socket clientSocket = null;
		try {
			echoServer = new ServerSocket(8000);
		} catch (Exception e) {
			System.out.println(e);
		}

		while(true) {
			try {
				//Client Connection, Create HandleMsg Thread for Processing
				clientSocket = echoServer.accept();
				System.out.println(clientSocket.getRemoteSocketAddress() + " connect!");
				tp.execute(new HandleMsg(clientSocket));
			} catch (Exception e) {
				System.out.println(e);
			}
		}
	}
}

Define a heavyweight client:


public class HeavySocketClient {
	private static ExecutorService tp = Executors.newCachedThreadPool();
	private static final int sleep_time = 1000*1000*1000;
	public static class EchoClient implements Runnable {
		@Override
		public void run() {
			Socket client = null;
			PrintWriter writer = null;
			BufferedReader reader = null;
			try {
				client = new Socket();
				client.connect(new InetSocketAddress("localhost",8000));
				writer = new PrintWriter(client.getOutputStream(),true);
				writer.write("H");
				LockSupport.parkNanos(sleep_time);
				writer.write("e");
				LockSupport.parkNanos(sleep_time);
				writer.write("l");
				LockSupport.parkNanos(sleep_time);
				writer.write("l");
				LockSupport.parkNanos(sleep_time);
				writer.write("o");
				LockSupport.parkNanos(sleep_time);
				writer.write("!");
				LockSupport.parkNanos(sleep_time);
				writer.println();
				writer.flush();

				reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
				System.out.println("from server:" + reader.readLine());
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if(writer!=null)writer.close();
					if(reader!=null)reader.close();
					if(client!=null)client.close();
				} catch (Exception e2) {
				}
			}
		}
	}
	public static void main(String[] args) {
		EchoClient ec = new EchoClient();
		for(int i=0; i<10; i++) {
			tp.execute(ec);
		}
	}
}

The code defines the client, which makes 10 requests. Each request will access port 8000. When the connection is successful, it will output "Hello!" to the server. String, but in this interaction, the client delays output, only one character at a time, and then waits for one second. So the whole process lasts six seconds.

For the server, the processing time of each request is about 6 seconds. This is easy to understand, because the server reads in the input of the client first, and the slow processing speed of the client (or possibly a congested network environment) makes the server spend a lot of waiting time. In this case, the server's slow processing of requests is not due to heavy tasks on the server side, but simply because the service thread is waiting for IO.

2.2 Network programming using NIO

Channel is a key component in NIO. Channel is a bit like a stream. A Channel can correspond to a file or a network Socket. If Channel corresponds to a Socket, writing data to the Channel is equivalent to writing data to the Socket.

Another important component used with Channel is Buffer. Buffer can be simply understood as a memory area or byte array. Data needs to be packaged as a Buffer to interact with Channel (write or read).

Another closely related to Channel is the Selector. Among Channel's many implementations, there is a Selectable Channel implementation that represents the channel that can be selected. Any Selectable Channel can register itself in a Selector. In this way, the Channel can be managed by the Selector. A Selector can manage multiple Selectable Channels. When the Selectable Channel's data is ready, the Selector is notified of the data that is ready. Socket Channel is a kind of Selectable Channel.

A Selector can be managed by a thread, and a Selectable Channel can represent a client connection, so this constitutes a structure with one or very few threads to handle a large number of client connections. When the data connected to the client is not ready, the Selector will be in a waiting state, and once any Selectable Channel has the data ready, the Selector will be notified immediately to get the data for processing.

Here is the core code of the NIO server:

//Handling all network connections
private Selector selector;
//Thread pool handles each client accordingly
private ExecutorService tp = Executors.newCachedThreadPool();
//Used to count how much time server threads spend on a client
public static Map<Socket, Long> time_stat = new HashMap<Socket, Long>(10240);
// Start NIO Server
private void startServer() throws Exception {
	// Get an instance of the Selector object through the factory method
	selector = SelectorProvider.provider().openSelector();
	// Get an instance of SocketChannel representing the server side
	ServerSocketChannel ssc = ServerSocketChannel.open();
	// Set this Socket Channel to non-blocking mode
	// In this mode, we can register interested events with Channel and get the necessary notification when the data is ready.
	ssc.configureBlocking(false);

	InetSocketAddress isa = new InetSocketAddress("localhost", 8000);
	// Bind this CHannel to port 8000
	ssc.socket().bind(isa);
	// Bind this Server Socket Channel to Selector and register the time it's interested as Accept
	// When the Selector finds a new client connection to ServerSocket Channel, it notifies the ServerSocket Channel for processing.
	// The return value of the method register() is a SelectionKey, which represents a pair of Selector s and Channel s.
	// When Channel is registered with Selector, it is equivalent to determining the service relationship between them, and SelectionKey is the contract.
	// When the Selector or or Channel is closed, their corresponding SelectionKey fails.
	SelectionKey acceptKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
	//Infinite loop, whose main task is to wait-distribute network messages
	for(;;) {
		// The select() method is a blocking method. If no data is currently available, it will wait. Once there is data to read,
		// It will return. Its return value is the number of SelectionKey ready. It's simply ignored here.
		selector.select();
		// Get the selected keys that are ready. Because Selector serves multiple Channel s at the same time, channels that are ready may be multiple.
		Set readyKeys = selector.selectedKeys();
		Iterator i = readyKeys.iterator();
		long e = 0;
		// Use iterators to traverse the entire collection
		while(i.hasNext()) {
			// Get an instance of SelectionKey in a collection based on the iterator
			SelectionKey sk = (SelectionKey)i.next();
			// Remove this element! Note that this is very important, otherwise the same Selection Key will be processed repeatedly.
			i.remove();
			//Is the Channel currently represented by SelectionKey in the Acceptable state
			if(sk.isAcceptable()) {
				// Receiving by Client
				doAccept(sk);
			}
			//Determine if Channel is readable
			else if(sk.isValid() && sk.isReadable()) {
				// In order to count the time the system processes each connection, a timestamp is recorded before the data is read.
				if(!time_stat.containsKey(((SocketChannel)sk.channel()).socket()))
					time_stat.put(((SocketChannel)sk.channel()).socket(), System.currentTimeMillis());
					// read
				doRead(sk);
			}
			// Determine if the channel is ready for writing
			else if(sk.isValid() && sk.isWritable()) {
				// write
				doWrite(sk);
				e = System.currentTimeMillis();
				long b = time_stat.remove(((SocketChannel)sk.channel()).socket());
				System.out.println("spend:" + (e-b) +"ms");
			}
		}
	}
}

The doAccept() method, which establishes a connection with the client:

private void doAccept(SelectionKey sk) {
	ServerSocketChannel server = (ServerSocketChannel)sk.channel();
	SocketChannel clientChannel;
	try {
		// The generated client Channel represents the channel to communicate with the client.
		clientChannel = server.accept();
		// Configuring this Channel in non-blocking mode requires the system to notify our threads to read or write after IO is ready.
		clientChannel.configureBlocking(false);
		//Register the newly generated Channel on the selector selector selector and tell the selector that I am now interested in the OP_READ operation. In this way,
		//When the Selector finds that the Channel is ready to read, it can give the thread a notification.
		SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ);
		//Create a new object instance, an EchoClient instance representing a client
    EchoClient echoClient = new EchoClient();
		// We attach this client instance as an attachment to the SelectionKey representing the connection. So throughout the processing of the connection,
		// We can all share this EchoClient instance
		clientKey.attach(echoClient);

		InetAddress clientAddress = clientChannel.socket().getInetAddress();
		System.out.println("Accepted connection from " + clientAddress.getHostAddress() + ".");
	} catch (Exception e) {
		System.out.println("False to accept new client.");
		e.printStackTrace();
	}
}

The definition of EchoClient is simple. It encapsulates a queue and stores all the information that needs to be replied to the client, so that when replying again, only the elements pop up in the outq object.

public class EchoClient {
	private LinkedList<ByteBuffer> outq;
	EchoClient() {
		outq = new LinkedList<ByteBuffer>();
	}

	public LinkedList<ByteBuffer> getOutQueue() {
		return outq;
	}

	public void enqueue(ByteBuffer bb) {
		outq.addFirst(bb);
	}
}

When Channel is readable, the doRead() method is called:

private void doRead(SelectionKey sk) {
	// Get the current client Channel
	SocketChannel channel = (SocketChannel)sk.channel();
	// Prepare 8K buffer to read data
	ByteBuffer bb = ByteBuffer.allocate(8192);
	int len;

	try {
		// All read data is stored in variable bb
		len = channel.read(bb);
		if(len < 0) {
			disconnect(sk);
			return;
		}
	} catch (Exception e) {
		System.out.println("Failed to read from client.");
		e.printStackTrace();
		disconnect(sk);
		return;
	}
	// Reset the buffer to prepare for data processing
	bb.flip();
	tp.execute(new HandleMsg(sk, bb, selector));
}

In order to simulate complex scenarios, thread pool is used for data processing. In this way, if the data processing is complex, it can be done in a separate thread without blocking the task dispatch thread. The implementation of HandleMsg is also simple:

public class HandleMsg implements Runnable {
	SelectionKey sk;
	ByteBuffer bb;
	Selector selector;

	public HandleMsg(SelectionKey sk, ByteBuffer bb, Selector selector) {
		this.sk = sk;
		this.bb = bb;
		this.selector = selector;
	}

	@Override
	public void run() {
		EchoClient echoClient = (EchoClient)sk.attachment();
		echoClient.enqueue(bb);
		sk.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
		// Force selector to return immediately
		selector.wakeup();
	}
}

In the above code, the received data is simply pushed into the EchoClient queue. If business logic needs to be processed, it can be processed here. After the data processing is completed, you can prepare to write the results back to the client. Therefore, you can re-register the message event of interest and submit the write operation (OP_WRITE) as the event of interest. This allows the thread to be notified when the channel is ready to write.

Write operations are implemented using the doWrite() function:

private void doWrite(SelectionKey sk) {
	// This SelectionKey is the same as the SelectionKey that doRead() gets.
	SocketChannel channel = (SocketChannel)sk.channel();
	// Get shared EchoClient
	EchoClient echoClient = (EchoClient)sk.attachment();
	// Get a list of sent content
	LinkedList<ByteBuffer> outq = echoClient.getOutQueue();
	// Get the top element of the list
	ByteBuffer bb = outq.getLast();
	try {
		// Write
		int len = channel.write(bb);
		if(len == -1) {
			disconnect(sk);
			return;
		}

		if(bb.remaining() == 0) {
			// Remove the buffer object when all the sending is complete
			outq.removeLast();
		}
	} catch (Exception e) {
		System.out.println("Failed to write to client");
		e.printStackTrace();
		disconnect(sk);
	}

	if(outq.size()==0) {
		// If not removed, the doWrite() method is executed every time the Channel is ready to write (it seems unreasonable that there is no data to write at this time).
		sk.interestOps(SelectionKey.OP_READ);
	}
}

With NIO technology, even if the client is slow or the network is delayed, it will not bring too much problems to the server.

2.3 Implementing Client with NIO

We used NIO to implement the server and Socket programming to build our client. Next, we use NIO to implement the client. Similar to NIO servers, the core elements are Selector,Channel and Selection Key.

Initialize Selector and Channel:

private Selector selector;
public void init(String ip, int port) throws IOException {
	SocketChannel channel = SocketChannel.open();
	channel.configureBlocking(false);
	this.selector = SelectorProvider.provider().openSelector();
	// Because Channel is currently non-blocking, the connection does not necessarily succeed when the connect() method returns.
	// finishConnect() is also used to confirm the connection later.
	channel.connect(new InetSocketAddress(ip, port));
	channel.register(selector, SelectionKey.OP_CONNECT);
}

Client main execution logic:

public void working() throws Exception {
	while(true) {
		if(!selector.isOpen()) {
			break;
		}
		selector.select();
		Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
		while(ite.hasNext()) {
			SelectionKey key = ite.next();
			ite.remove();
			// Connection event occurrence
			if(key.isConnectable()) {
				connect(key);
			} else if(key.isReadable()) {
				read(key);
			}
		}
	}
}

It mainly deals with two events. First, it represents Connect event (handled by connect() function) ready for connection and Read event (handled by read() function) which represents channel readable.

The function connect() is implemented as follows:


public void connect(SelectionKey key) throws IOException {
	SocketChannel channel = (SocketChannel)key.channel();
	//If you are connecting, complete the connection
	if(channel.isConnectionPending()) {
		channel.finishConnect();
	}
	channel.configureBlocking(false);
	// Write data to Channel and register read events as interesting events at the same time
	channel.write(ByteBuffer.wrap(new String("hello server!\r\n").getBytes()));
	channel.register(this.selector, SelectionKey.OP_READ);
}

When Channel is readable, the read() method is executed to read the data:


public void read(SelectionKey key) throws IOException {
	SocketChannel channel = (SocketChannel)key.channel();
	//Create a read buffer
	ByteBuffer buffer = ByteBuffer.allocate(100);
	channel.read(buffer);
	byte[] data = buffer.array();
	String msg = new String(data).trim();
	System.out.println("The client receives information:" + msg);
	channel.close();
	key.selector().close();
}

The read() function first creates a 100 byte buffer (line 4), then reads data from Channel and prints it to the console. Finally, close Channel and Selector.

Three, AIO

AIO is the abbreviation of asynchronous IO. Although NIO provides a non-blocking method in network operation, NIO's IO behavior is synchronous. For NIO, our business thread is notified when the IO operation is ready, and then the IO operation is performed by the thread itself. The IO operation itself is synchronous.
For AIO, it does not notify threads when IO is ready, but notifies threads after IO operations have been completed. Therefore, AIO is completely non-blocking. At this point, our business logic will become a callback function, waiting for the IO operation to complete, triggered automatically by the system. Next, we implement a simple EchoServer and its corresponding client through AIO.

3.1 Implementation of AIO EchoServer

Asynchronous IO requires asynchronous Server Socket Channel:

	public final static int PORT = 8000;
	// Use Asynchronous Server Socket Channel asynchronous Channel as the server with a variable named server
	private AsynchronousServerSocketChannel server;
	public AIOEchoServer() throws IOException {
		server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(PORT));
}

Use this server to receive and process the client:

public void start() throws InterruptedException, ExecutionException, TimeoutException {
	System.out.println("Server listen on " + PORT);
	// Register events and processors after event completion	
	server.accept(null,new CompletionHandler<AsynchronousSocketChannel, Object>() {
		final ByteBuffer buffer = ByteBuffer.allocate(1024);
		@Override
		public void completed(AsynchronousSocketChannel result,
				Object attachment) {
			System.out.println(Thread.currentThread().getName());
			Future<Integer> writeResult = null;
			try {
				buffer.clear();
				result.read(buffer).get(100,TimeUnit.SECONDS);
				buffer.flip();
				writeResult = result.write(buffer);
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			} catch (TimeoutException e) {
				e.printStackTrace();
			} finally {
				try {
					server.accept(null,this);
					writeResult.get();
					result.close();
				} catch(Exception e) {
					System.out.println(e.toString());
				}
			}
		}
		@Override
		public void failed(Throwable exc, Object attachment) {
			System.out.println("Failed: " + exc);
		}
	});
}

The AsynchronousServerSocketChannel.accept() method returns immediately. It doesn't really wait for the client to come. The signature of the accept() method used here is:

public final <A> void accept(A attachment,
                         CompletionHandler<AsynchronousSocketChannel,? super A> handler)

Its first parameter is an attachment, which can be of any type, to allow the current thread and subsequent callback methods to share information, which will be passed to handler in subsequent calls. Its second parameter is the CompletionHandler interface. This interface has two methods:

//Successful callback
void completed(V result, A attachment);
//Failure callback
void failed(Throwable exc, A attachment);

Asynchronous Server SocketChannel. accept () actually does two things. The first is to initiate an accept request, telling the system that it can start listening on the port. Second, register the CompletionHandler instance, telling the system that once a client comes to connect, if it succeeds, the CompletionHandler.completed() method will be executed; if the connection fails, the CompletionHandler.failed() method will be executed.
So, the server.accept() method won't block, and it will return immediately.

Next, let's analyze the implementation of CompletionHandler.completed(). When completed() is executed, it means that the client has successfully connected. read() method is used to read customer data. The Asynchronous Server SocketChannel. read() method is also asynchronous. In other words, it does not wait for the completion of the reading to return, but returns immediately. The result is a Future, so this is a typical application of Future mode. For programming convenience, we call Future.get() method directly, wait, and turn this asynchronous method into a synchronous method. Therefore, after its execution is completed, the data reading is completed.

After that, the data is written back to the client. The AsynchronousServerSocketChannel.write() method is called here. This method does not wait for all the data to be written, but also returns immediately. Similarly, it returns a Future object.

Then, the server prepares for the next client connection. Close the client connection currently being processed at the same time. But before closing, you have to make sure that the previous write() operation has been completed, so use the Future.get() method to wait.

Next, just call the start() method in the main function to open the server:

public static void main(String[] args) throws Exception {
	new AIOEchoServer().start();
	while (true) {
		Thread.sleep(1000);
	}
}

Line 2 of the above code calls the start() method to open the server. But because the start() method uses asynchronous methods, it will return immediately, not wait like the blocking method. Therefore, wait statements in lines 4 to 6 are necessary if you want the program to reside in execution. Otherwise, after the start() method, before the client arrives, the program has been run and the main thread will exit.

3.2 AIO Echo Client Implementation

All clients use asynchronous callbacks to achieve.

public class AIOClient {
	public static void main(String[] args) throws Exception {
		// Open the Asynchronous Socket Channel channel
		final AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
		// Let the client connect to the specified server and register a series of events
		client.connect(new InetSocketAddress("localhost", 8000),null,new CompletionHandler<Void, Object>() {
			@Override
			public void completed(Void result, Object attachment) {
				// Write data, send data to the server, this process is also asynchronous, will return soon
				client.write(ByteBuffer.wrap("Hello!".getBytes()), null, new CompletionHandler<Integer, Object>() {
					@Override
					public void completed(Integer result, Object attachment) {
						try {
							// Ready for data reading, read and write back data from the server
							ByteBuffer buffer = ByteBuffer.allocate(1024);
							client.read(buffer,buffer,new CompletionHandler<Integer, ByteBuffer>() {
 
								@Override
								public void completed(Integer result,
										ByteBuffer buffer) {
									buffer.flip();
									// Print the received data
									System.out.println(new String(buffer.array()));
									try {
										client.close();
									} catch (Exception e) {
										e.printStackTrace();
									}
								}
								@Override
								public void failed(Throwable exc,
										ByteBuffer attachment) {
								}
							});
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
					@Override
					public void failed(Throwable exc, Object attachment) {
					}
				});
			}
			@Override
			public void failed(Throwable exc, Object attachment) {
			}
		});
		// Since the main thread ends immediately, there is waiting for the processing to complete.
		Thread.sleep(1000);
	}
}

Posted by ohaus on Fri, 10 May 2019 14:27:01 -0700