BIO大家都比较熟悉,也比较简单,所以写demo时作了一些拓展:
1. 要求每次发请求时所有客户端同时并发请求,共起20个客户端;
2. 要求服务关闭时能尽快的停止任务线程,但不能丢失任务信息而是将被停止或者取消的任务信息和状态保存下来;
3. 要求服务对于不同类型的消息能够自动的识别和处理,且方便扩展,在添加新的消息和处理时不需要修改已有的实现;
4. 设计通信协议和消息的编解码;
5. 对消息内容加密;
这些内容对于线程并发,中断处理、Class反射、泛型、消息编解码以及加解密都有些涉及,下面说下具体的实现
1. 并发请求
模拟并发请求的场景,想法就是在每次请求时都等所有的client准备好再一起请求,可以在发送请求处设置一个阻塞点,就像线程栅栏一样。但是demo中客户端的数量会动态变化,后来直接用wait-notify实现的,设置两个AtomicInteger变量,准备好请求的client的数量clientReadyNum和运行着的client的总数clientRunningNum,当有client到达阻塞点时clientReadyNum就递增,clientRunningNum则随着client的创建和关闭而递增或递减,等待条件就是clientReadyNum % clientRunningNum== 0
要注意的是,在client关闭重置等待条件之后需要再次判断等待条件判断和线程唤醒的操作。在demo中,当某个client出现连接异常时自行关闭,如果这时其他的client都已经阻塞在等待锁上,那它在结束自己之前,应该唤醒别的client,让其他client不要再等待自己,否则已经阻塞的client会无限等待下去。
package com.io.Bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.security.SecureRandom;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Logger;
import com.io.Util;
import com.io.Bio.message.Demo1Request;
import com.io.Bio.message.Demo2Request;
import com.io.Bio.message.Request;
public class BioClient extends Thread {
private static final Logger LOG = Logger.getLogger(BioClient.class);
private static SecureRandom random = new SecureRandom();
/**
* 所有运行着的client的计数
*/
private static volatile AtomicInteger clientRunningNum = new AtomicInteger(0);
/**
* 到达阻塞点准备好发请求的client的计数
*/
private static volatile AtomicInteger clientReadyNum = new AtomicInteger(0);
private static volatile AtomicInteger client_index = new AtomicInteger(0);
private int index = client_index.incrementAndGet();
private static Object lock = new Object();
private Socket socket;
private String host;
private int port;
public BioClient(String host, int port) {
this.host = host;
this.port = port;
this.setName("client-" + index);
}
@Override
public void run() {
clientRunningNum.incrementAndGet();
while (true) {
if (!isConnectionAlive()) {
try {
socket = new Socket(host, port);
} catch (IOException e) {
LOG.error(" 连接服务异常:" + e.getMessage());
shutdown();
return;
}
}
BufferedReader in = null;
OutputStream out = null;
//为了体现不同消息的不同处理
Request requst = null;
if(index % 2 == 0){
requst = new Demo1Request();
requst.init(this.getName());
}else{
requst = new Demo2Request();
requst.init(this.getName());
}
try {
out = socket.getOutputStream();
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//模拟并发场景,设置阻塞点,每次等所有客户端都到达后一起发送消息。
clientReadyNum.incrementAndGet();
synchronized (lock) {
while (clientReadyNum.get() % clientRunningNum.get() != 0) {
lock.wait();
}
clientReadyNum.set(0);
lock.notifyAll();
}
LOG.info("发送请求:" + requst.getMsg());
out.write(requst.getBytes());
String response = in.readLine();
LOG.info("收到响应:" + response);
} catch (InterruptedException e) {
shutdown(); // 在等待锁lock的时候可以响应中断
return;
} catch (IOException e) {
LOG.error("异常中断:" + e.getMessage());
shutdown();
return;
} catch (Exception e) {
LOG.error("消息编码异常:", e);
} finally {
Util.close(socket);
}
try {
sleep(random.nextInt(10));
} catch (InterruptedException e) {
shutdown();
break;
}
}
}
/**
* 客户端关闭,注意要重设阻塞条件,且通知其他client不需要再等待自己了
*/
public void shutdown() {
Util.close(socket);
clientRunningNum.decrementAndGet();
LOG.warn("关闭自己 " + "Ready/Running:" + clientReadyNum.get() + "/" + clientRunningNum.get());
synchronized (lock) {
if (clientRunningNum.get() == 0 || clientReadyNum.get() % clientRunningNum.get() == 0) {
lock.notifyAll();
}
}
}
/**
* 检测socket连接
*/
private boolean isConnectionAlive() {
if (socket == null) {
return false;
}
try {
socket.sendUrgentData(0xFF);
return true;
} catch (Exception e) {
Util.close(socket);
return false;
}
}
/**
* @测试 启动20个client,并发请求
*/
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new BioClient("127.0.0.1", 4040).start();
}
}
}
2. 服务关闭
借用《java并发编程实战》中的一句话:处理好失败,关闭和取消是好的软件和勉强运行的软件之间最大的区别。demo中实现任务线程响应中断的思路也来自于此书的笔记https://blog.csdn.net/shm839218753/article/details/79377069
关闭服务有三件事需要做
1. 停止接收新的请求,即给服务的accepter线程发送一个中断信号,accepter会在每次接收新的请求之前检测中断状态,如果已经被置位,那么就立即结束自己。
2. 关闭任务线程池,尽快停止已接受的请求的处理,线程池会返回还在排队的任务并已经启动的任务handler发送一个中断请求,但是只是请求了一下而已,怎么响应以及何时响应是需要自己判断和实现的。demo中实现如下:
定义CancellableHandler继承Runnable,增加取消方法cancel()和RunnableFuture的工厂方法newTask()
定义ExtendExecutor继承ThreadPoolExecutor,重写RunnableFuture的工厂方法newTaskFor(Runnable runnable,T value),如果参数runnable是CancellableHandler的实例,则调用实例runnable自定义的newTask()方法。
定义抽象类Handler作为所有任务线程的父类,在cancel()的实现中关闭socket,保存任务的内容和进度情况;在newTask()的实现中返回一个匿名的FutureTask实例,并重写这个FutureTask实例的cancel()方法,在调用FutureTask.cancel()之前,先调用Handler.cancel()方法。这样在FutureTask.cancel()之前会先将socket关闭,避免了因为socket的读取写入等操作而阻塞中断响应,并且保存了被中断的任务线程处理的任务。
3. 关闭socket连接。把这个放在最后,是为了让前面没有被中断的任务线程还能把响应给发回去。如果不断开,后续有的的客户端仍然可以成功发送请求,却会被服务器拒绝。如果socket关闭第一步,那好处理结束的任务线程又无法将响应发回去。具体怎么做需就看怎么取舍了。
package com.io.Bio;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import org.apache.log4j.Logger;
import com.io.Util;
import com.io.Bio.handler.ExtendExecutor;
import com.io.Bio.handler.Handler;
import com.io.Bio.message.Request;
import com.io.Bio.message.Demo1Request;
public class BioServer extends Thread {
private static final Logger LOG = Logger.getLogger(BioServer.class);
private final ExtendExecutor exec;
private ServerSocket serverSocket;
private Integer port;
public BioServer(int port) {
super("accepter");
this.port = port;
exec = new ExtendExecutor(5, 5, 1, port);
}
@Override
public void run() {
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
LOG.error("服务启动异常:" + e.getMessage());
e.printStackTrace();
}
LOG.info("服务启动端口:" + port);
Request request = null;
while (!interrupted()) {
try {
Socket socket = serverSocket.accept();
InputStream input = socket.getInputStream();
//读取消息头,长度固定56,0消息代号,1消息版本,2-5消息体长度,6-55消息class类型
byte[] headArray = new byte[56];
input.read(headArray);
//解码消息头信息
int bodyLnegth = Util.decodeInt(headArray, 2);
String requestClzz = Util.decodeString(headArray, 6, 50);
@SuppressWarnings("unchecked")
Class<? extends Request> requestClass = (Class<? extends Request>) Class.forName(requestClzz);
request = requestClass.newInstance();
//读取消息体
byte[] bodyArray = new byte[bodyLnegth];
input.read(bodyArray);
//解密,解码消息体
request.decode(Util.RSADecode(bodyArray));
//获取对应处理器进行处理
Handler<? extends Request> handler = Handler.getHandler(request,socket);
exec.submit(handler);
} catch (IOException e) {
if (serverSocket.isClosed()) {
LOG.warn("socket已经关闭,开始关闭服务");
} else {
LOG.error("服务异常关闭!" + e.getMessage());
}
shutdown();
break;
} catch (RejectedExecutionException e){
// 可以选择调用者执行的饱和策略r.run(); 但这里本就想关闭服务了,所以直接丢弃
LOG.warn("服务已关闭,丢弃"+ request.getName() + "的请求["
+ request.getClass().getSimpleName() + "]:" + request.getMsg());
} catch (Exception e) {
LOG.error("消息处理异常,", e);
}
}
}
@SuppressWarnings("unchecked")
public void shutdown() {
LOG.info("关闭服务,停止接收请求...");
interrupt();// 中断accepter
LOG.info("关闭任务线程池,中断正在处理和取消等待处理的任务");
List<Runnable> taskList = exec.shutdownNow();
if (!taskList.isEmpty()) {
for (Runnable task : taskList) {
FutureTask<Demo1Request> future = (FutureTask<Demo1Request>) task;
future.cancel(true);
}
}
/**
* 关闭的时候将连接放在最后关闭,是为了刚好在这时结束的任务或者不能很好响应中断的任务能够在最后返回响应
* 可以在这边在这里也设置一个闭锁阻塞main线程,
* 等所有需要返回响应的任务线程都结束之后,再放开闭锁,关闭连接。
* 否则任务现在在处理结束后想返回响应时会发现连接已经关闭,无法返回响应
*/
LOG.info("断开连接...");
Util.close(serverSocket);
}
/**
* @测试:启动服务7秒后关闭
*/
public static void main(String[] args) throws InterruptedException {
BioServer server = new BioServer(4040);
server.start();
Thread.sleep(7000);
server.shutdown();
}
}
package com.io.Bio.handler;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 扩展ThreadPoolExecutor,对于CancellableRunnable,在构造RunnableFuture的时候调用它自己的newTask()方法
*/
public class ExtendExecutor extends ThreadPoolExecutor {
public ExtendExecutor(int corePoolSize, int maximumPoolSize, long keepAliveSeconds, int port) {
super(corePoolSize, maximumPoolSize, keepAliveSeconds, TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(100), new HandleThreadFactory());
}
@Override
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable,T value) {
if (runnable instanceof CancellableHandler) {
return ((CancellableHandler) runnable).newTask();
} else {
return super.newTaskFor(runnable,value);
}
}
private static class HandleThreadFactory implements ThreadFactory {
private AtomicInteger index = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "handler-" + index.incrementAndGet());
}
}
}
package com.io.Bio.handler;
import java.util.concurrent.RunnableFuture;
/**
* 扩展Runnable,增加自定义的取消方法cancel()和构造RunnableFuture的工厂方法newTask()
*/
public interface CancellableHandler extends Runnable{
void cancel();
<T> RunnableFuture<T> newTask();
}
3. 消息处理
在实际应用中,服务会收到很多不同类型的消息,而不同类型消息的处理也是不同的,如果简单通过if-else来判断显然是不合适的,可以定义一个消息类型与消息处理器类型的映射器Map<Class<? extends Request>,Class<? extends Handler>>,在系统启动时注册好,这样在收到消息时就可以通过消息类型找到对应的处理器来处理,并且方便以后的程序扩展,当添加了新的消息类型和处理类型时,只需要在映射器中增加一对映射关系就好,不需要再修改已有的代码实现,在实际中,一般会将这种初始化初始化的内容放到配置xml或者数据库中,所以在扩展业务时,只需要添加下新的消息类型和处理器类型文件并改下配置就可以了。这正是设计模式原则中推荐的理念:对扩展开放,对修改封闭。
demo中定义两组消息和消息处理器,共用一个线程池,其实在实际的服务中都会用多个线程池来处理不同类型的任务(每个线程池中提交的任务应该保证独立且尽量同一种类型)。
package com.io.Bio.handler;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import org.apache.log4j.Logger;
import com.io.Util;
import com.io.Bio.message.Demo1Request;
import com.io.Bio.message.Demo2Request;
import com.io.Bio.message.Request;
/**
* Handler实现CancellableRunnable,自定义了Future.cancel(),在cancel()中关闭socket
*/
public abstract class Handler<T extends Request> implements CancellableHandler {
private static final Logger LOG = Logger.getLogger(Handler.class);
@SuppressWarnings("rawtypes")
public static Map<Class<? extends Request>,Class<? extends Handler>> handlerMap = new HashMap<>();
protected Socket socket;
protected T request;
static{
handlerMap.put(Demo1Request.class , Demo1RequestHandler.class);
handlerMap.put(Demo2Request.class , Demo2RequestHandler.class);
//...
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <E extends Request> Handler<? extends Request> getHandler(E request,Socket socket){
Class<? extends Handler> clzz = handlerMap.get(request.getClass());
if(clzz == null){
return null;
}
Handler<E> handler = null;
try {
handler = (Handler<E>)clzz.newInstance();
} catch (Exception e) {
return null;
}
handler.socket = socket;
handler.request = request;
return handler;
}
public abstract void handler(Socket socket,T v);
@Override
public void run() {
handler(socket,request);
}
@Override
public void cancel() {
LOG.warn(" 取消处理并保存" + request.getName() + "的请求["
+ request.getClass().getSimpleName() + "]:" + request.getMsg());
Util.close(socket);
}
/**
* cancel()能关闭socket的关键就在于这个方法,
* 它返回一个匿名的RunnableFuture实例,但是重写了实例的cancel()方法,
* 在cancel()之前调用了外部HandleTask实例的cancel()方法,将socket关闭。
*/
@SuppressWarnings("unchecked")
@Override
public RunnableFuture<T> newTask() {
return new FutureTask<T>(this,request) {
@SuppressWarnings("finally")
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
try {
Handler.this.cancel();
} finally {
return super.cancel(mayInterruptIfRunning);
}
}
};
}
}
package com.io.Bio.handler;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.SecureRandom;
import org.apache.log4j.Logger;
import com.io.Calculator;
import com.io.Util;
import com.io.Bio.message.Demo1Request;
public class Demo1RequestHandler extends Handler<Demo1Request>{
private static final Logger LOG = Logger.getLogger(Demo1RequestHandler.class);
private static SecureRandom random = new SecureRandom();
@Override
public void handler(Socket socket,Demo1Request request) {
PrintWriter out = null;
try {
out = new PrintWriter(socket.getOutputStream(), true);
long beginTime = System.currentTimeMillis();
LOG.info("处理" + request.getName() + "的请求:" + request.getMsg());
String response;
try {
response = String.valueOf(Calculator.conversion(request.getMsg()));
} catch (Exception e) {
response = "server exception:" + e.getMessage();
}
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
Util.close(socket);
LOG.warn("处理被中断");
return;
}
out.println(response);
LOG.info("返回响应:" + response + " ,处理耗时:" + (System.currentTimeMillis() - beginTime));
} catch (IOException e) {
LOG.error("处理异常:" + e.getMessage());
} finally {
Util.close(socket);
}
}
}
package com.io.Bio.handler;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import org.apache.log4j.Logger;
import com.io.Util;
import com.io.Bio.message.Demo2Request;
public class Demo2RequestHandler extends Handler<Demo2Request>{
private static final Logger LOG = Logger.getLogger(Demo2RequestHandler.class);
private static SecureRandom random = new SecureRandom();
@Override
public void handler(Socket socket, Demo2Request request) {
PrintWriter out = null;
try {
out = new PrintWriter(socket.getOutputStream(), true);
long beginTime = System.currentTimeMillis();
LOG.info("处理" + request.getName() + "的请求:" + request.getMsg());
String response;
try {
response = new SimpleDateFormat("yyyyMMdd HH:mm:ss:SSS").format(request.getTime());
} catch (Exception e) {
response = "server exception:" + e.getMessage();
}
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
Util.close(socket);
LOG.warn("处理被中断");
return;
}
out.println(response);
LOG.info("返回响应:" + response + " ,处理耗时:" + (System.currentTimeMillis() - beginTime));
} catch (IOException e) {
LOG.error("处理异常:" + e.getMessage());
} finally {
Util.close(socket);
}
}
}
4. 消息编解码
编解码其实就是指字节与字符之间的相互转换。在收发消息时传送的永远都是字节流,当然你在读取字节流时也可以用装饰过的Reader来直接读取字符。但是通常收发的消息都不会只是一坨字符串,而是会有一定的组织结构。这在发送消息时的编码并没什么问题,因为你肯定知道自己发的是什么东西,但是收消息一端看到的只是一个可以读取字节的输入流通道。所以就需要双方约定好一个消息协议,发送时按照协议格式编码,收到时按照协议格式解码。
demo中的消息约定好,前56位byte位作为消息头(0位:消息代号;1位:消息版本;2-5位:消息长度;6-55位:消息的class类型),在消息头中放入消息class类型只是为了方便,在与其他不是java的系统进行交互时这个信息是没有用的,一般都会用消息代号和消息版本来确定收到的是什么消息。所以收到消息时首先读取byte[56],一般消息协议都不会对消息头进行加密,可以直接解析出消息类型和消息体字节长度信息len,然后再读取byte[len]交给对应的消息解码器进行解码(demo中消息的编解码器都直接定义在消息类型中)。
package com.io.Bio.message;
import org.apache.log4j.Logger;
import com.io.Util;
public abstract class Request {
private static final Logger LOG = Logger.getLogger(Request.class);
public static final int HEADER_LEN = 56;
protected String name;
protected String msg;
private int byteIndex;
private byte[] byteArray;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void init(String name){
this.name = name;
this.msg = buildMsg();
this.byteArray = new byte[60 + getMessageLen()];
}
public abstract byte getMessageId();
public abstract int getMessageLen();
protected String buildMsg(){
return "";
}
protected abstract void encode();
public abstract void decode(byte[] byteArray);
public byte[] getBytes() {
encode();
byte[] arr = new byte[HEADER_LEN + 128]; //56 + 128 简单处理,消息头56位,消息体加密后固定128位
//加密消息体部分
byte[] bodyArr = new byte[getMessageLen()];
System.arraycopy(byteArray, HEADER_LEN, bodyArr, 0, getMessageLen());
try {
byte[] encodeArr = Util.RSAEncode(bodyArr); //128byte
System.arraycopy(byteArray, 0, arr, 0, HEADER_LEN);
System.arraycopy(encodeArr, 0, arr, HEADER_LEN, 128);
} catch (Exception e) {
LOG.error("消息加密异常," + e.getMessage());
}
return arr;
}
protected void encode(byte b){
byteArray[byteIndex] = b;
byteIndex++;
}
protected void encode(int value){
for (int i = 3; i >= 0; i--, value = value >> 8){
byteArray[byteIndex + i] = (byte)(value & 0xFF);
}
byteIndex += 4;
}
protected void encode(long value) {
for (int i = 7; i >= 0; i--, value = value >> 8){
byteArray[byteIndex + i] = (byte)(value & 0xFF);
}
byteIndex += 8;
}
protected void encode(String value, int length){
byte[] arr = value.getBytes();
int len = arr.length;
System.arraycopy(arr, 0, byteArray, byteIndex, len);
byteIndex += length;
}
}
package com.io.Bio.message;
import java.security.SecureRandom;
import com.io.Util;
public class Demo1Request extends Request {
private static final byte ID = 0x1F;
private static final byte VERSION = 0x2F;
private static final int LEN = 30;
private static String operators[] = { "+", "-", "*", "/" };
private static SecureRandom random = new SecureRandom();
@Override
public byte getMessageId(){
return ID;
}
@Override
public int getMessageLen(){
return LEN;
}
@Override
protected String buildMsg(){
return random.nextInt(10) + operators[random.nextInt(4)] + (random.nextInt(10) + 1);
}
@Override
protected void encode() {
encode(ID);
encode(VERSION);
encode(128);//RSA加密报文后长度固定为128byte
encode(this.getClass().getName(),50);
encode(name,20);
encode(msg,10);
}
@Override
public void decode(byte[] byteArray) {
name = Util.decodeString(byteArray, 0, 20);
msg = Util.decodeString(byteArray, 20, 10);
}
}
package com.io.Bio.message;
import com.io.Util;
public class Demo2Request extends Request {
private static final byte ID = 0x1E;
private static final byte VERSION = 0x2E;
private static final int LEN = 58;
private long time = System.currentTimeMillis();
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public byte getMessageId() {
return ID;
}
@Override
public int getMessageLen() {
return LEN;
}
@Override
protected void encode() {
encode(ID);
encode(VERSION);
encode(128);//RSA加密报文后长度固定为128byte
encode(this.getClass().getName(),50);
encode(name,10);
encode(msg,40);
encode(time);
}
@Override
public void decode(byte[] byteArray) {
name = Util.decodeString(byteArray, 0, 10);
msg = Util.decodeString(byteArray, 10, 40);
time = Util.decodeLong(byteArray,50);
}
@Override
protected String buildMsg() {
return "格式化:" + time;
}
}
其实一开始写demo还尝试过按指定分隔符编解码,当时想的是这样能节约字节流的长度,但其实一般都约定好的字段长度不会浪费太多字节的,而且这样会导致解码时很慢,因为需要遍历byte[]从头到位,简单如下
private static final String SP = "@#$%";
private String name;
private String msg;
public byte[] encode() throws UnsupportedEncodingException{
byte[] spbyte = SP.getBytes("utf8");
byte[] namebyte = name.getBytes("utf8");
byte[] msgbyte = msg.getBytes("utf8");
byte[] arr = new byte[spbyte.length * 2 + namebyte.length + msgbyte.length];
System.arraycopy(namebyte, 0, arr, 0, namebyte.length);
System.arraycopy(spbyte, 0, arr, namebyte.length, spbyte.length);
System.arraycopy(msgbyte, 0, arr, namebyte.length + spbyte.length, msgbyte.length);
System.arraycopy(spbyte, 0, arr, namebyte.length + spbyte.length + msgbyte.length, spbyte.length);
return arr;
}
public static void decode(byte[] arr) throws UnsupportedEncodingException{
byte[] spbyte = SP.getBytes("utf8");
String name = "";
String msg = "";
int indexMsg = 0;
for(int i = 0;i < arr.length - 3;i++){
if(arr[i] == spbyte[0] && arr[i + 1] == spbyte[1]
&& arr[i + 2] == spbyte[2] && arr[i + 3] == spbyte[3]){
if(indexMsg == 0){
name = new String(Arrays.copyOfRange(arr, 0, i),"utf8");
i = i + 4;
indexMsg = i;
}else{
msg = new String(Arrays.copyOfRange(arr, indexMsg, i),"utf8");
break;
}
}
}
System.out.println(name);
System.out.println(msg);
}
5. 消息加解密
参考https://blog.csdn.net/hj7jay/article/details/51921522 对demo中的消息体使用了RSA加密。其实对加密解密数字签名证书等并不是很了解,但使用起来并不难,生成一对秘钥,用来加解密就好。后面对着这些再做下梳理。
package com.io;
import java.io.Closeable;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class Util {
public static final String KEY_PUBLIC =
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTDLinNiVmZvDMrX66pEoMCpJZbtQNSTa+Imgz"
+ "4386ITu7Xy7nS7mSOBvIieCQhPEta/FAgiTPL5g4SH/XyFdYAaKaV3/j+9IiITWsaa5t5JBsQ0VV"
+ "aacsqgScKba6RgVRQSZUEJaMX7/YOeO8RnP1H6tiZG3vzkAi/2E5hWbp5wIDAQAB";
public static final String KEY_PRIVATE =
"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJMMuKc2JWZm8MytfrqkSgwKkllu"
+ "1A1JNr4iaDPjfzohO7tfLudLuZI4G8iJ4JCE8S1r8UCCJM8vmDhIf9fIV1gBoppXf+P70iIhNaxp"
+ "rm3kkGxDRVVppyyqBJwptrpGBVFBJlQQloxfv9g547xGc/Ufq2Jkbe/OQCL/YTmFZunnAgMBAAEC"
+ "gYACYyp8THy+9Nzj9c0g6pnpKCLIIOyAarfgzl4yuXbPUsrNd/Yi+y/AF/kbgGGM9xuTSTVZfsNq"
+ "ObOW9lZdAnuog8mT8vqWwdBuNYAzNw6vJZbxaVt3LDzBSGhx3RxPe+zcjBMXjAQj8MJFcE81v7CK"
+ "i03pPtP61kPu2fhnZEEZQQJBANYwYMeFSTTljIqAeePaeIVUVZDYNI2mkXD7Ml6O/hFUg5uMp+Qg"
+ "9EXI4RJ+P1wHtU3rqRm+hHPI9ZYNpUQznesCQQCvwTSdzeqPTNHBb7tc8zADTab8Ug2uyWIpOPyq"
+ "32lh1y+WSeJIccFEvaedncZSxi2SHt6l0apU/K7g2BA/KFj1AkEAo+uqZYgQGreC84yXvFW63u9H"
+ "/O46ah4MORdF2TA+KS0w+56N7v15dN7jwa909g3AJ74vUFCKNcKakRgoXyXRuQJAEwxupnfN51Ad"
+ "H8j7Vpyo5ILDCW/fOcVr1SnvAJoMMuV+q9xAITfrCYdApm2WNBx0jfS4juJFgsaMMaRZRm8aDQJA"
+ "d0/61FiITY+j/1r/O3j4OSwOYwOTiQJf9Y0JQBM9yRTPXQVVDSvG6Xvgb4a99Npmq95HenZYxzQ1"
+ "YWiKUmkAlA==";
public static final String operators[] = { "+", "-", "*", "/" };
public static String buildMsg() {
SecureRandom random = new SecureRandom();
return random.nextInt(10) + operators[random.nextInt(4)] + (random.nextInt(10) + 1);
}
public static String decodeString(byte[] byteArray,int index,int length){
byte[] arr1 = new byte[length];
System.arraycopy(byteArray, index, arr1, 0, length);
int used = getByteLen(arr1);
byte[] arr2 = new byte[used];
System.arraycopy(arr1, 0, arr2, 0, used);
return new String(arr2);
}
public static int decodeInt(byte[] byteArray, int index) {
return (int) ( ((byteArray[index] & 0xFF)<<24)
|((byteArray[index+1] & 0xFF)<<16)
|((byteArray[index+2] & 0xFF)<<8)
|(byteArray[index+3] & 0xFF));
}
public static long decodeLong(byte[] byteArray,int index) {
long value = 0;
for(int i = 0;i < 8;i++){
int shift = (7 - i) << 3;
value |=((long)0xff << shift) & ((long)byteArray[index + i] << shift);
}
return value;
}
private static int getByteLen(byte[] data) {
int i = 0;
for (; i < data.length; i++) {
if (data[i] == '\0')
break;
}
return i;
}
public static void close(Closeable io) {
if (io != null) {
try {
io.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* RSA非对称加密内容长度有限制,1024位key的最多只能加密127位数据,加密后长度固定为128位
* 否则就会报错(javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes)
* 解决办法是用对称加密(AES/DES etc)加密数据,然后用RSA公钥加密对称加密的密钥,
* 用RSA的私钥解密得到对称加密的密钥,然后完成反向操作得到明文。
*/
public static void buildKeyofRSA() throws NoSuchAlgorithmException{
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey)keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey)keyPair.getPrivate();
System.out.println(new BASE64Encoder().encode(rsaPrivateKey.getEncoded()));
System.out.println(new BASE64Encoder().encode(rsaPublicKey.getEncoded()));
}
public static byte[] RSAEncode(byte[] byteArray) throws Exception {
PKCS8EncodedKeySpec encodeKey =
new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(Util.KEY_PRIVATE));
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(encodeKey);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(byteArray);
}
public static byte[] RSADecode(byte[] byteArray) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec =
new X509EncodedKeySpec(new BASE64Decoder().decodeBuffer(Util.KEY_PUBLIC));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(byteArray);
}
}
下面是服务开启7秒后关闭的测试数据:
客户端:
2018-06-28 09:56:52 912 [INFO] client-18 BioClient.java:90 发送请求:3*4
2018-06-28 09:56:52 912 [INFO] client-12 BioClient.java:90 发送请求:3*3
2018-06-28 09:56:52 912 [INFO] client-14 BioClient.java:90 发送请求:9-4
2018-06-28 09:56:52 912 [INFO] client-4 BioClient.java:90 发送请求:5-10
2018-06-28 09:56:52 912 [INFO] client-6 BioClient.java:90 发送请求:1*1
2018-06-28 09:56:52 912 [INFO] client-2 BioClient.java:90 发送请求:7+5
2018-06-28 09:56:52 912 [INFO] client-10 BioClient.java:90 发送请求:5-6
2018-06-28 09:56:52 912 [INFO] client-20 BioClient.java:90 发送请求:2*8
2018-06-28 09:56:52 912 [INFO] client-16 BioClient.java:90 发送请求:9-5
2018-06-28 09:56:52 912 [INFO] client-8 BioClient.java:90 发送请求:0-5
2018-06-28 09:56:52 912 [INFO] client-5 BioClient.java:90 发送请求:格式化:1530151012750
2018-06-28 09:56:52 912 [INFO] client-1 BioClient.java:90 发送请求:格式化:1530151012750
2018-06-28 09:56:52 912 [INFO] client-13 BioClient.java:90 发送请求:格式化:1530151012750
2018-06-28 09:56:52 912 [INFO] client-3 BioClient.java:90 发送请求:格式化:1530151012750
2018-06-28 09:56:52 912 [INFO] client-15 BioClient.java:90 发送请求:格式化:1530151012749
2018-06-28 09:56:52 912 [INFO] client-11 BioClient.java:90 发送请求:格式化:1530151012750
2018-06-28 09:56:52 912 [INFO] client-7 BioClient.java:90 发送请求:格式化:1530151012749
2018-06-28 09:56:52 912 [INFO] client-9 BioClient.java:90 发送请求:格式化:1530151012749
2018-06-28 09:56:52 912 [INFO] client-17 BioClient.java:90 发送请求:格式化:1530151012749
2018-06-28 09:56:52 912 [INFO] client-19 BioClient.java:90 发送请求:格式化:1530151012749
2018-06-28 09:56:54 060 [INFO] client-14 BioClient.java:94 收到响应:5.0
2018-06-28 09:56:54 075 [INFO] client-7 BioClient.java:94 收到响应:20180628 09:56:52:749
2018-06-28 09:56:54 103 [INFO] client-9 BioClient.java:94 收到响应:20180628 09:56:52:749
2018-06-28 09:56:54 110 [INFO] client-8 BioClient.java:94 收到响应:-5.0
2018-06-28 09:56:54 118 [INFO] client-19 BioClient.java:94 收到响应:20180628 09:56:52:749
2018-06-28 09:56:54 156 [INFO] client-11 BioClient.java:94 收到响应:20180628 09:56:52:750
2018-06-28 09:56:54 215 [INFO] client-12 BioClient.java:94 收到响应:9.0
2018-06-28 09:56:54 529 [INFO] client-18 BioClient.java:94 收到响应:12.0
2018-06-28 09:56:54 660 [INFO] client-15 BioClient.java:94 收到响应:20180628 09:56:52:749
2018-06-28 09:56:54 795 [INFO] client-4 BioClient.java:94 收到响应:-5.0
2018-06-28 09:56:54 819 [INFO] client-6 BioClient.java:94 收到响应:1.0
2018-06-28 09:56:55 021 [INFO] client-17 BioClient.java:94 收到响应:20180628 09:56:52:749
2018-06-28 09:56:55 095 [INFO] client-2 BioClient.java:94 收到响应:12.0
2018-06-28 09:56:55 224 [INFO] client-3 BioClient.java:94 收到响应:20180628 09:56:52:750
2018-06-28 09:56:55 360 [INFO] client-1 BioClient.java:94 收到响应:20180628 09:56:52:750
2018-06-28 09:56:55 452 [INFO] client-13 BioClient.java:94 收到响应:20180628 09:56:52:750
2018-06-28 09:56:55 661 [INFO] client-16 BioClient.java:94 收到响应:4.0
2018-06-28 09:56:55 723 [INFO] client-10 BioClient.java:94 收到响应:-1.0
2018-06-28 09:56:55 768 [INFO] client-5 BioClient.java:94 收到响应:20180628 09:56:52:750
2018-06-28 09:56:55 896 [INFO] client-20 BioClient.java:94 收到响应:16.0
2018-06-28 09:56:55 905 [INFO] client-20 BioClient.java:90 发送请求:0+10
2018-06-28 09:56:55 905 [INFO] client-16 BioClient.java:90 发送请求:1/9
2018-06-28 09:56:55 906 [INFO] client-6 BioClient.java:90 发送请求:6/3
2018-06-28 09:56:55 912 [INFO] client-4 BioClient.java:90 发送请求:0/2
2018-06-28 09:56:55 912 [INFO] client-12 BioClient.java:90 发送请求:9/4
2018-06-28 09:56:55 906 [INFO] client-17 BioClient.java:90 发送请求:格式化:1530151015031
2018-06-28 09:56:55 913 [INFO] client-11 BioClient.java:90 发送请求:格式化:1530151014158
2018-06-28 09:56:55 906 [INFO] client-2 BioClient.java:90 发送请求:2+4
2018-06-28 09:56:55 914 [INFO] client-19 BioClient.java:90 发送请求:格式化:1530151014120
2018-06-28 09:56:55 914 [INFO] client-14 BioClient.java:90 发送请求:3-4
2018-06-28 09:56:55 906 [INFO] client-3 BioClient.java:90 发送请求:格式化:1530151015233
2018-06-28 09:56:55 905 [INFO] client-13 BioClient.java:90 发送请求:格式化:1530151015461
2018-06-28 09:56:55 905 [INFO] client-5 BioClient.java:90 发送请求:格式化:1530151015777
2018-06-28 09:56:55 906 [INFO] client-1 BioClient.java:90 发送请求:格式化:1530151015362
2018-06-28 09:56:55 905 [INFO] client-10 BioClient.java:90 发送请求:6*7
2018-06-28 09:56:55 914 [INFO] client-7 BioClient.java:90 发送请求:格式化:1530151014081
2018-06-28 09:56:55 914 [INFO] client-9 BioClient.java:90 发送请求:格式化:1530151014105
2018-06-28 09:56:55 914 [INFO] client-8 BioClient.java:90 发送请求:0*3
2018-06-28 09:56:55 912 [INFO] client-18 BioClient.java:90 发送请求:7-6
2018-06-28 09:56:55 912 [INFO] client-15 BioClient.java:90 发送请求:格式化:1530151014664
2018-06-28 09:56:56 033 [INFO] client-9 BioClient.java:94 收到响应:20180628 09:56:54:105
2018-06-28 09:56:56 063 [INFO] client-8 BioClient.java:94 收到响应:0.0
2018-06-28 09:56:56 152 [INFO] client-12 BioClient.java:94 收到响应:2.25
2018-06-28 09:56:56 395 [INFO] client-19 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 395 [INFO] client-14 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 395 [INFO] client-18 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 396 [INFO] client-7 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 396 [INFO] client-11 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 397 [INFO] client-15 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 397 [INFO] client-4 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 398 [INFO] client-6 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 398 [INFO] client-17 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 398 [INFO] client-2 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 399 [INFO] client-3 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 399 [INFO] client-1 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 399 [INFO] client-13 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 399 [INFO] client-16 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 400 [INFO] client-10 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 400 [INFO] client-5 BioClient.java:94 收到响应:null
2018-06-28 09:56:56 400 [INFO] client-20 BioClient.java:94 收到响应:null
2018-06-28 09:56:57 422 [ERROR] client-15 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 422 [ERROR] client-13 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 423 [WARN] client-13 BioClient.java:124 关闭自己 Ready/Running:6/18
2018-06-28 09:56:57 423 [ERROR] client-17 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 422 [ERROR] client-6 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 422 [ERROR] client-16 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 424 [WARN] client-16 BioClient.java:124 关闭自己 Ready/Running:6/15
2018-06-28 09:56:57 422 [WARN] client-15 BioClient.java:124 关闭自己 Ready/Running:6/19
2018-06-28 09:56:57 422 [ERROR] client-18 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 425 [WARN] client-18 BioClient.java:124 关闭自己 Ready/Running:6/14
2018-06-28 09:56:57 422 [ERROR] client-4 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 426 [WARN] client-4 BioClient.java:124 关闭自己 Ready/Running:6/13
2018-06-28 09:56:57 422 [ERROR] client-10 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 426 [WARN] client-10 BioClient.java:124 关闭自己 Ready/Running:6/12
2018-06-28 09:56:57 422 [ERROR] client-19 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 427 [WARN] client-19 BioClient.java:124 关闭自己 Ready/Running:6/11
2018-06-28 09:56:57 424 [WARN] client-6 BioClient.java:124 关闭自己 Ready/Running:6/16
2018-06-28 09:56:57 423 [WARN] client-17 BioClient.java:124 关闭自己 Ready/Running:6/17
2018-06-28 09:56:57 423 [ERROR] client-3 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 428 [WARN] client-3 BioClient.java:124 关闭自己 Ready/Running:6/10
2018-06-28 09:56:57 423 [ERROR] client-11 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 429 [WARN] client-11 BioClient.java:124 关闭自己 Ready/Running:6/9
2018-06-28 09:56:57 423 [ERROR] client-5 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 430 [WARN] client-5 BioClient.java:124 关闭自己 Ready/Running:6/8
2018-06-28 09:56:57 423 [ERROR] client-20 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 423 [ERROR] client-7 BioClient.java:60 连接服务异常:Connection refused: connect
2018-06-28 09:56:57 431 [WARN] client-7 BioClient.java:124 关闭自己 Ready/Running:6/6
2018-06-28 09:56:57 430 [WARN] client-20 BioClient.java:124 关闭自己 Ready/Running:6/7
2018-06-28 09:56:57 431 [INFO] client-12 BioClient.java:90 发送请求:9-10
2018-06-28 09:56:57 431 [INFO] client-14 BioClient.java:90 发送请求:5-2
2018-06-28 09:56:57 431 [INFO] client-2 BioClient.java:90 发送请求:5-7
2018-06-28 09:56:57 436 [ERROR] client-12 BioClient.java:99 异常中断:Connection reset by peer: socket write error
2018-06-28 09:56:57 436 [WARN] client-12 BioClient.java:124 关闭自己 Ready/Running:0/5
2018-06-28 09:56:57 431 [INFO] client-1 BioClient.java:90 发送请求:格式化:1530151016400
2018-06-28 09:56:57 436 [ERROR] client-2 BioClient.java:99 异常中断:Connection reset by peer: socket write error
2018-06-28 09:56:57 436 [WARN] client-2 BioClient.java:124 关闭自己 Ready/Running:0/4
2018-06-28 09:56:57 432 [INFO] client-9 BioClient.java:90 发送请求:格式化:1530151016042
2018-06-28 09:56:57 437 [ERROR] client-14 BioClient.java:99 异常中断:Connection reset by peer: socket write error
2018-06-28 09:56:57 437 [WARN] client-14 BioClient.java:124 关闭自己 Ready/Running:0/3
2018-06-28 09:56:57 432 [INFO] client-8 BioClient.java:90 发送请求:3-2
2018-06-28 09:56:57 438 [ERROR] client-1 BioClient.java:99 异常中断:Connection reset by peer: socket write error
2018-06-28 09:56:57 438 [WARN] client-1 BioClient.java:124 关闭自己 Ready/Running:0/2
2018-06-28 09:56:57 439 [ERROR] client-8 BioClient.java:99 异常中断:Connection reset by peer: socket write error
2018-06-28 09:56:57 439 [WARN] client-8 BioClient.java:124 关闭自己 Ready/Running:0/1
2018-06-28 09:56:57 442 [ERROR] client-9 BioClient.java:99 异常中断:Connection reset
2018-06-28 09:56:57 443 [WARN] client-9 BioClient.java:124 关闭自己 Ready/Running:0/0
服务端:
2018-06-28 09:56:49 400 [INFO] accepter BioServer.java:43 服务启动端口:4040
2018-06-28 09:56:53 216 [INFO] handler-1 Demo2RequestHandler.java:27 处理client-9的请求:格式化:1530151012749
2018-06-28 09:56:53 217 [INFO] handler-2 Demo1RequestHandler.java:27 处理client-8的请求:0-5
2018-06-28 09:56:53 218 [INFO] handler-3 Demo2RequestHandler.java:27 处理client-19的请求:格式化:1530151012749
2018-06-28 09:56:53 220 [INFO] handler-4 Demo1RequestHandler.java:27 处理client-14的请求:9-4
2018-06-28 09:56:53 220 [INFO] handler-5 Demo1RequestHandler.java:27 处理client-12的请求:3*3
2018-06-28 09:56:54 060 [INFO] handler-4 Demo1RequestHandler.java:44 返回响应:5.0 ,处理耗时:840
2018-06-28 09:56:54 062 [INFO] handler-4 Demo2RequestHandler.java:27 处理client-7的请求:格式化:1530151012749
2018-06-28 09:56:54 075 [INFO] handler-4 Demo2RequestHandler.java:45 返回响应:20180628 09:56:52:749 ,处理耗时:13
2018-06-28 09:56:54 075 [INFO] handler-4 Demo2RequestHandler.java:27 处理client-15的请求:格式化:1530151012749
2018-06-28 09:56:54 103 [INFO] handler-1 Demo2RequestHandler.java:45 返回响应:20180628 09:56:52:749 ,处理耗时:887
2018-06-28 09:56:54 104 [INFO] handler-1 Demo1RequestHandler.java:27 处理client-4的请求:5-10
2018-06-28 09:56:54 110 [INFO] handler-2 Demo1RequestHandler.java:44 返回响应:-5.0 ,处理耗时:893
2018-06-28 09:56:54 111 [INFO] handler-2 Demo2RequestHandler.java:27 处理client-17的请求:格式化:1530151012749
2018-06-28 09:56:54 118 [INFO] handler-3 Demo2RequestHandler.java:45 返回响应:20180628 09:56:52:749 ,处理耗时:900
2018-06-28 09:56:54 119 [INFO] handler-3 Demo2RequestHandler.java:27 处理client-11的请求:格式化:1530151012750
2018-06-28 09:56:54 156 [INFO] handler-3 Demo2RequestHandler.java:45 返回响应:20180628 09:56:52:750 ,处理耗时:37
2018-06-28 09:56:54 157 [INFO] handler-3 Demo1RequestHandler.java:27 处理client-6的请求:1*1
2018-06-28 09:56:54 215 [INFO] handler-5 Demo1RequestHandler.java:44 返回响应:9.0 ,处理耗时:995
2018-06-28 09:56:54 215 [INFO] handler-5 Demo1RequestHandler.java:27 处理client-18的请求:3*4
2018-06-28 09:56:54 529 [INFO] handler-5 Demo1RequestHandler.java:44 返回响应:12.0 ,处理耗时:314
2018-06-28 09:56:54 529 [INFO] handler-5 Demo2RequestHandler.java:27 处理client-3的请求:格式化:1530151012750
2018-06-28 09:56:54 660 [INFO] handler-4 Demo2RequestHandler.java:45 返回响应:20180628 09:56:52:749 ,处理耗时:585
2018-06-28 09:56:54 661 [INFO] handler-4 Demo2RequestHandler.java:27 处理client-13的请求:格式化:1530151012750
2018-06-28 09:56:54 795 [INFO] handler-1 Demo1RequestHandler.java:44 返回响应:-5.0 ,处理耗时:691
2018-06-28 09:56:54 796 [INFO] handler-1 Demo2RequestHandler.java:27 处理client-1的请求:格式化:1530151012750
2018-06-28 09:56:54 819 [INFO] handler-3 Demo1RequestHandler.java:44 返回响应:1.0 ,处理耗时:662
2018-06-28 09:56:54 820 [INFO] handler-3 Demo1RequestHandler.java:27 处理client-2的请求:7+5
2018-06-28 09:56:55 021 [INFO] handler-2 Demo2RequestHandler.java:45 返回响应:20180628 09:56:52:749 ,处理耗时:910
2018-06-28 09:56:55 022 [INFO] handler-2 Demo1RequestHandler.java:27 处理client-10的请求:5-6
2018-06-28 09:56:55 095 [INFO] handler-3 Demo1RequestHandler.java:44 返回响应:12.0 ,处理耗时:275
2018-06-28 09:56:55 096 [INFO] handler-3 Demo2RequestHandler.java:27 处理client-5的请求:格式化:1530151012750
2018-06-28 09:56:55 224 [INFO] handler-5 Demo2RequestHandler.java:45 返回响应:20180628 09:56:52:750 ,处理耗时:695
2018-06-28 09:56:55 224 [INFO] handler-5 Demo1RequestHandler.java:27 处理client-20的请求:2*8
2018-06-28 09:56:55 360 [INFO] handler-1 Demo2RequestHandler.java:45 返回响应:20180628 09:56:52:750 ,处理耗时:564
2018-06-28 09:56:55 361 [INFO] handler-1 Demo1RequestHandler.java:27 处理client-16的请求:9-5
2018-06-28 09:56:55 452 [INFO] handler-4 Demo2RequestHandler.java:45 返回响应:20180628 09:56:52:750 ,处理耗时:791
2018-06-28 09:56:55 661 [INFO] handler-1 Demo1RequestHandler.java:44 返回响应:4.0 ,处理耗时:300
2018-06-28 09:56:55 723 [INFO] handler-2 Demo1RequestHandler.java:44 返回响应:-1.0 ,处理耗时:701
2018-06-28 09:56:55 768 [INFO] handler-3 Demo2RequestHandler.java:45 返回响应:20180628 09:56:52:750 ,处理耗时:672
2018-06-28 09:56:55 896 [INFO] handler-5 Demo1RequestHandler.java:44 返回响应:16.0 ,处理耗时:672
2018-06-28 09:56:55 927 [INFO] handler-4 Demo1RequestHandler.java:27 处理client-14的请求:3-4
2018-06-28 09:56:55 942 [INFO] handler-1 Demo2RequestHandler.java:27 处理client-7的请求:格式化:1530151014081
2018-06-28 09:56:55 948 [INFO] handler-2 Demo2RequestHandler.java:27 处理client-9的请求:格式化:1530151014105
2018-06-28 09:56:55 955 [INFO] handler-3 Demo1RequestHandler.java:27 处理client-8的请求:0*3
2018-06-28 09:56:55 959 [INFO] handler-5 Demo2RequestHandler.java:27 处理client-19的请求:格式化:1530151014120
2018-06-28 09:56:56 032 [INFO] handler-2 Demo2RequestHandler.java:45 返回响应:20180628 09:56:54:105 ,处理耗时:84
2018-06-28 09:56:56 035 [INFO] handler-2 Demo2RequestHandler.java:27 处理client-11的请求:格式化:1530151014158
2018-06-28 09:56:56 063 [INFO] handler-3 Demo1RequestHandler.java:44 返回响应:0.0 ,处理耗时:108
2018-06-28 09:56:56 064 [INFO] handler-3 Demo1RequestHandler.java:27 处理client-12的请求:9/4
2018-06-28 09:56:56 152 [INFO] handler-3 Demo1RequestHandler.java:44 返回响应:2.25 ,处理耗时:88
2018-06-28 09:56:56 153 [INFO] handler-3 Demo1RequestHandler.java:27 处理client-18的请求:7-6
2018-06-28 09:56:56 395 [INFO] main BioServer.java:91 关闭服务,停止接收请求...
2018-06-28 09:56:56 395 [INFO] main BioServer.java:94 关闭任务线程池,中断正在处理和取消等待处理的任务
2018-06-28 09:56:56 395 [WARN] handler-3 Demo1RequestHandler.java:39 处理被中断
2018-06-28 09:56:56 395 [WARN] handler-4 Demo1RequestHandler.java:39 处理被中断
2018-06-28 09:56:56 395 [WARN] handler-5 Demo2RequestHandler.java:40 处理被中断
2018-06-28 09:56:56 396 [WARN] handler-2 Demo2RequestHandler.java:40 处理被中断
2018-06-28 09:56:56 396 [WARN] handler-1 Demo2RequestHandler.java:40 处理被中断
2018-06-28 09:56:56 397 [WARN] main Handler.java:63 取消处理并保存client-15的请求[Demo2Request]:格式化:1530151014664
2018-06-28 09:56:56 397 [WARN] main Handler.java:63 取消处理并保存client-4的请求[Demo1Request]:0/2
2018-06-28 09:56:56 398 [WARN] main Handler.java:63 取消处理并保存client-6的请求[Demo1Request]:6/3
2018-06-28 09:56:56 398 [WARN] main Handler.java:63 取消处理并保存client-17的请求[Demo2Request]:格式化:1530151015031
2018-06-28 09:56:56 398 [WARN] main Handler.java:63 取消处理并保存client-2的请求[Demo1Request]:2+4
2018-06-28 09:56:56 398 [WARN] main Handler.java:63 取消处理并保存client-3的请求[Demo2Request]:格式化:1530151015233
2018-06-28 09:56:56 399 [WARN] main Handler.java:63 取消处理并保存client-1的请求[Demo2Request]:格式化:1530151015362
2018-06-28 09:56:56 399 [WARN] main Handler.java:63 取消处理并保存client-13的请求[Demo2Request]:格式化:1530151015461
2018-06-28 09:56:56 399 [WARN] main Handler.java:63 取消处理并保存client-16的请求[Demo1Request]:1/9
2018-06-28 09:56:56 399 [WARN] main Handler.java:63 取消处理并保存client-10的请求[Demo1Request]:6*7
2018-06-28 09:56:56 400 [WARN] main Handler.java:63 取消处理并保存client-5的请求[Demo2Request]:格式化:1530151015777
2018-06-28 09:56:56 400 [WARN] main Handler.java:63 取消处理并保存client-20的请求[Demo1Request]:0+10
2018-06-28 09:56:56 400 [INFO] main BioServer.java:109 断开连接...
2018-06-28 09:56:57 438 [WARN] accepter BioServer.java:81 服务已关闭,丢弃client-9的请求[Demo2Request]:格式化:1530151016042
参考文章
1. https://blog.csdn.net/shm839218753/article/details/79377069
2. https://blog.csdn.net/hj7jay/article/details/51921522