Análisis del código fuente de Java y preguntas de la entrevista: trabajo práctico: Socket combinado con el uso del grupo de subprocesos

Esta serie de blog relacionado, Mu columna de referencia de clase Java de código fuente y del sistema fabricantes entrevistador sucinta Zhenti
por debajo de esta columna es la dirección de GitHub:
Fuente resolvió: https://github.com/luanqiu/java8
artículo Demostración: HTTPS: // GitHub. com / luanqiu / java8_demo los
compañeros de clase pueden verlo si es necesario)

Análisis del código fuente de Java y preguntas de la entrevista: trabajo práctico: Socket combinado con el uso del grupo de subprocesos

La
pregunta final de la entrevista de Socket es generalmente permitirle escribir un ejemplo simple de comunicación con el cliente y el servidor. Este artículo lo llevará a escribir esta demostración juntos.

1 requisitos

  1. Puede usar Socket y ServiceSocket y otras API;
  2. Escriba un ejemplo de comunicación TCP entre cliente y servidor;
  3. Las tareas de procesamiento del lado del servidor requieren procesamiento asincrónico;
  4. Debido a que la capacidad de procesamiento del servidor es muy débil, solo se pueden procesar 5 solicitudes al mismo tiempo. Cuando la sexta solicitud llega al servidor, el servidor debe devolver un mensaje de error claro: El servidor está demasiado ocupado. Vuelva a intentarlo más tarde ~

Los requisitos son relativamente simples. El único punto complicado es el cuarto punto. Necesitamos controlar el volumen de solicitudes del cliente. Primero, debemos confirmar que no podemos controlar el número de solicitudes enviadas por el cliente, por lo que solo podemos hacerlo desde el servidor Transformación, como limitar la corriente del servidor.

Algunos estudiantes pronto pensarán que deberíamos usar la propiedad Backlog de ServerSocket, establecerla en 5, pero dijimos en el último capítulo que el backlog no representa con precisión el límite del número de conexiones de clientes, y también requerimos el servidor Devuelve información de error específica, incluso si el trabajo atrasado es efectivo, solo devolverá un mensaje de error fijo, no nuestro mensaje de error personalizado.

Pensemos en ello. El grupo de subprocesos parece ser capaz de hacer esto. Podemos establecer coreSize y maxSize del grupo de subprocesos en 4 y el tamaño de la cola en 1, para que el servidor juzgue el grupo de subprocesos cada vez que reciba una solicitud. No hay datos en la cola, si los hay, significa que el servidor actual está a punto de procesar la quinta solicitud. La solicitud actual es la sexta solicitud y debe rechazarse.

Solo unirse al grupo de subprocesos también puede cumplir con el tercer punto, y las tareas en el lado del servidor se pueden ejecutar de forma asincrónica.

2 código de cliente

El código del cliente es relativamente simple, solo solicite datos del servidor, el código es el siguiente:

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();
    	}
  	}
 
}

En el código del cliente, también utilizamos el grupo de subprocesos, principalmente para la simulación simultánea del cliente para enviar 6 solicitudes a la vez, como se esperaba cuando el servidor está procesando la sexta solicitud, devolverá un mensaje de error específico al cliente.

El método principal del código anterior es el método de envío, que procesa principalmente los datos enviados por el servidor y procesa la respuesta del servidor.

3 Código del servidor

La lógica del servidor se divide en dos partes. La primera parte es controlar el número de solicitudes de clientes. Cuando se exceden las capacidades del servidor, se rechazan nuevas solicitudes. Cuando las capacidades del servidor pueden responder, se colocan nuevas solicitudes. La segunda parte es el servidor. La lógica de ejecución de la tarea.

3.1 Control de solicitudes de clientes

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);
    	}
  	}
  	
}

Usamos collectPoll.getQueue (). Size ()> = 1 para determinar si el servidor actual ha alcanzado el límite de procesamiento. Si una tarea se pone en cola en la cola, significa que el servidor actual se ha sobrecargado. Rechazado, si no hay datos en la cola, significa que el servidor también puede aceptar nuevas solicitudes.

Los comentarios del código anterior son detallados, por lo que no entraré en detalles.

3.2 Lógica de procesamiento de tareas del servidor

La lógica de procesamiento del servidor es relativamente simple: los pasos principales son: leer la entrada del socket del cliente, procesarla y devolver la respuesta al cliente.

Usamos un hilo para dormir durante 2 segundos para simular la lógica de procesamiento del servidor. El código es el siguiente:

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 prueba

Al realizar la prueba, primero debemos iniciar el servidor, y luego iniciar el cliente, primero iniciamos el servidor, imprimimos el registro de la siguiente manera:
Inserte la descripción de la imagen aquí
Luego iniciamos el cliente, imprimimos el registro de la siguiente manera:
Inserte la descripción de la imagen aquí
finalmente miramos el registro de operación del servidor: a
Inserte la descripción de la imagen aquí
partir de los resultados de la operación anterior En, podemos ver que el resultado está en línea con nuestras expectativas. El servidor puede procesar 5 solicitudes simultáneamente en el pico de la solicitud, y las solicitudes restantes se pueden rechazar con la solicitud correcta.

5 Resumen

Entonces, el código se concentra en SocketClient, SocketServiceStart, SocketService, la secuencia inicial es iniciar SocketServiceStart primero, luego ejecutar SocketClient, los estudiantes interesados ​​pueden depurar bajo su propia cuenta para profundizar la impresión.

Publicado 40 artículos originales · ganado elogios 1 · vistas 5352

Supongo que te gusta

Origin blog.csdn.net/aha_jasper/article/details/105609566
Recomendado
Clasificación