Java source code analysis and interview questions-practical work: Socket combined with the use of thread pool

This series of related blog, Mu class reference column Java source code and system manufacturers interviewer succinctly Zhenti
below this column is GitHub address:
Source resolved: https://github.com/luanqiu/java8
article Demo: HTTPS: // GitHub. com / luanqiu / java8_demo
classmates can look at it if necessary)

Java source code analysis and interview questions-practical work: Socket combined with the use of thread pool

The
final question of the Socket interview is generally to let you write a simple example of client and server communication. This article will take you to write this demo together.

1 Requirements

  1. You can use Socket and ServiceSocket and other APIs;
  2. Write an example of TCP communication between client and server;
  3. Server-side processing tasks require asynchronous processing;
  4. Because the processing capacity of the server is very weak, only 5 requests can be processed at the same time. When the sixth request reaches the server, the server needs to return a clear error message: The server is too busy. Please try again later ~

The requirements are relatively simple. The only complicated point is the fourth point. We need to control the client ’s request volume. First, we need to confirm that we cannot control the number of requests sent by the client, so we can only do it from the server Transformation, such as limiting current from the server.

Some students may soon think that we should use the Backlog property of ServerSocket, set it to 5, but we said in the last chapter that backlog does not accurately represent the limit of the number of client connections, and we also require the server Return specific error information, even if the backlog is effective, it will only return a fixed error message, not our customized error message.

Let's think about it. The thread pool seems to be able to do this. We can set the coreSize and maxSize of the thread pool to 4 and the queue size to 1, so that the server will judge the thread pool every time it receives a request. There is no data in the queue in, if there is, it means that the current server is about to process the fifth request. The current request is the sixth request and should be rejected.

Just joining the thread pool can also meet the third point, and the tasks on the server side can be executed asynchronously.

2 Client code

The client code is relatively simple, just request data from the server, the code is as follows:

public class SocketClient {
  	private static final Integer SIZE = 1024;
  	private static final ThreadPoolExecutor socketPoll = new ThreadPoolExecutor(50, 50,
                                               365L, TimeUnit.DAYS, new LinkedBlockingQueue<>(400));
 
  	@Test
  	public void test() throws InterruptedException {
    	// 模拟客户端同时向服务端发送 6 条消息
    	for (int i = 0; i < 6; i++) {
      		socketPoll.submit(() -> {
        		send("localhost", 7007, "nihao");
      		});
    	}
    	Thread.sleep(1000000000);
  	}
  	
  	/**
   	* 发送tcp
   	*
   	* @param domainName 域名
   	* @param port       端口
   	* @param content    发送内容
   	*/
  	public static String send(String domainName, int port, String content) {
    	log.info("客户端开始运行");
    	Socket socket = null;
    	OutputStream outputStream = null;
    	InputStreamReader isr = null;
    	BufferedReader br = null;
    	InputStream is = null;
    	StringBuffer response = null;
    	try {
      		if (StringUtils.isBlank(domainName)) {
        		return null;
      		}
      		// 无参构造器初始化 Socket,默认底层协议是 TCP
      		socket = new Socket();
      		socket.setReuseAddress(true);
      		// 客户端准备连接服务端,设置超时时间 10 秒
      		socket.connect(new InetSocketAddress(domainName, port), 10000);
      		log.info("TCPClient 成功和服务端建立连接");
      		// 准备发送消息给服务端
      		outputStream = socket.getOutputStream();
      		// 设置 UTF 编码,防止乱码
      		byte[] bytes = content.getBytes(Charset.forName("UTF-8"));
      		// 输出字节码
      		segmentWrite(bytes, outputStream);
      		// 关闭输出
      		socket.shutdownOutput();
      		log.info("TCPClient 发送内容为 {}",content);
 
      		// 等待服务端的返回
      		socket.setSoTimeout(50000);//50秒还没有得到数据,直接断开连接
      		// 得到服务端的返回流
      		is = socket.getInputStream();
      		isr = new InputStreamReader(is);
      		br = new BufferedReader(isr);
      		// 从流中读取返回值
      		response = segmentRead(br);
      		// 关闭输入流
      		socket.shutdownInput();
 
      		//关闭各种流和套接字
      		close(socket, outputStream, isr, br, is);
      		log.info("TCPClient 接受到服务端返回的内容为 {}",response);
      		return response.toString();
    	} catch (ConnectException e) {
      		log.error("TCPClient-send socket连接失败", e);
      		throw new RuntimeException("socket连接失败");
    	} catch (Exception e) {
      		log.error("TCPClient-send unkown errror", e);
      		throw new RuntimeException("socket连接失败");
    	} finally {
      		try {
        		close(socket, outputStream, isr, br, is);
      		} catch (Exception e) {
        		// do nothing
      		}
    	}
  	}
 
  	/**
   	* 关闭各种流
   	*
   	* @param socket
   	* @param outputStream
   	* @param isr
   	* @param br
   	* @param is
   	* @throws IOException
   	*/
  	public static void close(Socket socket, OutputStream outputStream, InputStreamReader isr,
                           BufferedReader br, InputStream is) throws IOException {
    	if (null != socket && !socket.isClosed()) {
      		try {
        		socket.shutdownOutput();
      		} catch (Exception e) {
      		
      		}
      		try {
        		socket.shutdownInput();
      		} catch (Exception e) {
      		}
      		try {
        		socket.close();
      		} catch (Exception e) {
      		}
    	}
    	if (null != outputStream) {
      		outputStream.close();
    	}
    	if (null != br) {
      		br.close();
    	}
    	if (null != isr) {
     		isr.close();
    	}
    	if (null != is) {
      		is.close();
    	}
  	}
 
  	/**
   	* 分段读
   	*
   	* @param br
   	* @throws IOException
   	*/
  	public static StringBuffer segmentRead(BufferedReader br) throws IOException {
    	StringBuffer sb = new StringBuffer();
    	String line;
    	while ((line = br.readLine()) != null) {
      		sb.append(line);
    	}
    	return sb;
  	}
 
  	/**
   	* 分段写
   	*
   	* @param bytes
   	* @param outputStream
   	* @throws IOException
  	*/
  	public static void segmentWrite(byte[] bytes, OutputStream outputStream) throws IOException {
    	int length = bytes.length;
    	int start, end = 0;
    	for (int i = 0; end != bytes.length; i++) {
      		start = i == 0 ? 0 : i * SIZE;
      		end = length > SIZE ? start + SIZE : bytes.length;
      		length -= SIZE;
      		outputStream.write(bytes, start, end - start);
      		outputStream.flush();
    	}
  	}
 
}

We also use the thread pool in the client code, mainly to simulate the client to send 6 requests at a time, and the server will return a specific error message to the client when processing the sixth request as expected.

The main method of the above code is the send method, which mainly processes the data sent by the server and processes the response of the server.

3 Server code

The logic of the server is divided into two parts. The first part is to control the number of client requests. When the capabilities of the server are exceeded, new requests are rejected. When the capabilities of the server can respond, new requests are placed. The second part is the server. The execution logic of the task.

3.1 Controlling client requests

public class SocketServiceStart {
 
  	/**
   	* 服务端的线程池,两个作用
   	* 1:让服务端的任务可以异步执行
   	* 2:管理可同时处理的服务端的请求数
   	*/
  	private static final ThreadPoolExecutor collectPoll = new ThreadPoolExecutor(4, 4,
                                       365L, TimeUnit.DAYS, new LinkedBlockingQueue<>(1));
 
  	@Test
  	public void test(){
    	start();
  	}
 
  	/**
   	* 启动服务端
   	*/
  	public static final void start() {
    	log.info("SocketServiceStart 服务端开始启动");
    	try {
      		// backlog  serviceSocket处理阻塞时,客户端最大的可创建连接数,超过客户端连接不上
      		// 当线程池能力处理满了之后,我们希望尽量阻塞客户端的连接
//      ServerSocket serverSocket = new ServerSocket(7007,1,null);
      		// 初始化服务端
      		ServerSocket serverSocket = new ServerSocket();
      		serverSocket.setReuseAddress(true);
//      serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost().getHostAddress(), 80));
      		serverSocket.bind(new InetSocketAddress("localhost", 7007));
      		log.info("SocketServiceStart 服务端启动成功");
      		// 自旋,让客户端一直在取客户端的请求,如果客户端暂时没有请求,会一直阻塞
      		while (true) {
        		// 接受客户端的请求
        		Socket socket = serverSocket.accept();
 
        		// 如果队列中有数据了,说明服务端已经到了并发处理的极限了,此时需要返回客户端有意义的信息
        		if (collectPoll.getQueue().size() >= 1) {
          			log.info("SocketServiceStart 服务端处理能力到顶,需要控制客户端的请求");
          			//返回处理结果给客户端
          			rejectRequest(socket);
          			continue;
        		}
        		try {
          			// 异步处理客户端提交上来的任务
          			collectPoll.submit(new SocketService(socket));
        		} catch (Exception e) {
          			socket.close();
        		}
      		}
    	} catch (Exception e) {
      		log.error("SocketServiceStart - start error", e);
      		throw new RuntimeException(e);
    	} catch (Throwable e) {
      		log.error("SocketServiceStart - start error", e);
      		throw new RuntimeException(e);
    	}
 	}
	
	// 返回特定的错误码给客户端
  	public static void rejectRequest(Socket socket) throws IOException {
    	OutputStream outputStream = null;
    	try{
      		outputStream = socket.getOutputStream();
      		byte[] bytes = "服务器太忙了,请稍后重试~".getBytes(Charset.forName("UTF-8"));
      		SocketClient.segmentWrite(bytes, outputStream);
      		socket.shutdownOutput();
    	}finally {
      		//关闭流
      		SocketClient.close(socket,outputStream,null,null,null);
    	}
  	}
  	
}

We use collectPoll.getQueue (). Size ()> = 1 to determine whether the current server has reached the processing limit. If a task is queued in the queue, it means that the current server has been overloaded. New requests should be Rejected, if there is no data in the queue, it means that the server can also accept new requests.

The above code comments are detailed, so I won't go into details.

3.2 Processing logic of server tasks

The processing logic of the server is relatively simple. The main steps are: read input from the client's socket, process it, and return the response to the client.

We use a thread to sleep for 2 seconds to simulate the processing logic of the server. The code is as follows:

public class SocketService implements Runnable {
  	private Socket socket;
 
  	public SocketService() {
  	}
 
  	public SocketService(Socket socket) {
    	this.socket = socket;
  	}
 
  	@Override
  	public void run() {
    	log.info("SocketService 服务端任务开始执行");
    	OutputStream outputStream = null;
    	InputStream is = null;
    	InputStreamReader isr = null;
    	BufferedReader br = null;
    	try {
      		//接受消息
      		socket.setSoTimeout(10000);// 10秒还没有得到数据,直接断开连接
      		is = socket.getInputStream();
      		isr = new InputStreamReader(is,"UTF-8");
      		br = new BufferedReader(isr);
      		StringBuffer sb = SocketClient.segmentRead(br);
      		socket.shutdownInput();
      		log.info("SocketService accept info is {}", sb.toString());
 
      		//服务端处理 模拟服务端处理耗时
      		Thread.sleep(2000);
      		String response  = sb.toString();
 
      		//返回处理结果给客户端
		    outputStream = socket.getOutputStream();
      		byte[] bytes = response.getBytes(Charset.forName("UTF-8"));
      		SocketClient.segmentWrite(bytes, outputStream);
      		socket.shutdownOutput();
 
      		//关闭流
      		SocketClient.close(socket,outputStream,isr,br,is);
      		log.info("SocketService 服务端任务执行完成");
    	} catch (IOException e) {
      		log.error("SocketService IOException", e);
    	} catch (Exception e) {
      		log.error("SocketService Exception", e);
    	} finally {
      		try {
        		SocketClient.close(socket,outputStream,isr,br,is);
      		} catch (IOException e) {
	        	log.error("SocketService IOException", e);
      		}
    	}
  	}
}

4 Test

When testing, we must start the server first, and then start the client, first we start the server, print the log as follows:
Insert picture description here
Then we start the client, print the log as follows:
Insert picture description here
we finally look at the server operation log:
Insert picture description here
from the above operation results In, we can see that the result is in line with our expectations. The server can process 5 requests concurrently at the peak of the request, and the remaining requests can be rejected with the correct prompt.

5 Summary

So the code is concentrated in SocketClient, SocketServiceStart, SocketService, the starting sequence is to start SocketServiceStart first, then run SocketClient, interested students can debug under their own to deepen the impression.

Published 40 original articles · won praise 1 · views 5352

Guess you like

Origin blog.csdn.net/aha_jasper/article/details/105609566