1.流程图
可以看出 相比单线程的reactor模式来说,多线程的模式使用了线程池来管理多个线程来处理各个socket连接的读写请求,而比较简单的连接请求还是交给一个主线程来处理。只有发生读写时间的时候才会分发给各个线程来处理读写逻辑。
2.什么情况下socket是可读和可写的
引用的是https://blog.youkuaiyun.com/nosix/article/details/77484562这篇文章的介绍
可读的条件
1.socket的接收缓冲区中的数据字节大于等于该socket的接收缓冲区低水位标记的当前大小。对这样的socket的读操作将不阻塞并返回一个大于0的值(也就是返回准备好读入的数据)。对于TCP和UDP的socket而言,其缺省值为1.
2.该连接的读这一半关闭(也就是接收了FIN的TCP连接),也就是四次握手里面的服务端接收到服务端的FIN之后并且给客户端返回ack之后,对这样的socket的读操作将不阻塞并返回0
3.给监听套接字准备好新连接。就是说这个套接字属于一个监听套接字。
4.有一个socket有异常错误条件待处理.对于这样的socket的读操作将不会阻塞,并且返回一个错误(-1),errno则设置成明确的错误条件。
可写的条件
1.该套接字的发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记的大小;
2.该套接字的写半部关闭,继续写会产生SIGPIPE信号;(https://blog.youkuaiyun.com/u010982765/article/details/79038062)
3.非阻塞模式下,connect返回之后,该套接字连接成功或失败;
4.该套接字有错误待处理,对这样的套接字的写操作将返回-1。
3.简单的代码实现
相比于上一个单线程模式下的Reactor模式,我只是把TcpHandler类进行了一下改变,主要就是使用多线程来处理多个socket的读写事件 下面是代码实现
public class TcpHanler implements Procrss {
private SelectionKey selectionKey;
private SocketChannel socketChannel;
//当前处于的状态 初始化就是还没有被处理 使用valotile关键字保持可见性和顺序性
public volatile boolean statu_now ;
//所有的类共同使用这个线程池
public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(20, 1000, 5, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),new ThreadPoolExecutor.CallerRunsPolicy());
public TcpHanler(SelectionKey selectionKey, SocketChannel socketChannel) {
this.selectionKey = selectionKey;
this.socketChannel=socketChannel;
statu_now= true;
}
@Override
public void process(){
try {
//如果有别的线程正在操作那么就不创建新的线程去处理它
if (!statu_now){
return;
}else{
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
readAndSend();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
} catch (Exception e) {
e.printStackTrace();
//如果服务端关闭了就会出现java.net.SocketException: Software caused connection abort: recv failed异常
IOUtils.closeQuietly(socketChannel);
}
}
private synchronized void readAndSend() throws IOException {
if (!statu_now){
return;
}
statu_now=false;
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int read = 0;
try {
read = socketChannel.read(byteBuffer);
System.out.println("处理线程名字是"+Thread.currentThread().getName());
} catch (IOException e) {
System.out.println("发生异常的线程名字是"+Thread.currentThread().getName());
}
//即使客户端已经关闭 但是服务端还是会接收到客户端的读状态 但是此时读的值为-1
if (read==-1){
System.out.println(socketChannel.socket().getLocalAddress()+"客户端已经关闭");
selectionKey.cancel();
socketChannel.close();
return ;
}
//这里需要注意 read的返回值问题
//如果对方主动关闭了socket 那么会返回-1
//如果对方没有主动关闭 它不会返回-1
//有三种情况会返回0
// 1.socketChannel里面没有数据可读了
// 2.Buffer里面position和limit相等为0了
// 3. 客户端的程序发送完毕
while (read > 0) {
System.out.println(read);
//切换状态 记得调用flip方法 否则无法读取到数据
byteBuffer.flip();
System.out.println(socketChannel.socket().getRemoteSocketAddress().toString() + ":" + Charset.forName("utf-8").decode(byteBuffer).toString());
byteBuffer.compact();
read = socketChannel.read(byteBuffer);
send();
}
//如果是1 代表socket客户端已经关闭 就不发送消息了
statu_now=true;
}
private void send() throws IOException {
String str = "Your message has sent to "
+ socketChannel.socket().getLocalSocketAddress().toString() + "\r\n";
System.out.println(" start sending back");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(str.getBytes("utf-8"));
while (byteBuffer.hasRemaining()){
byteBuffer.flip();
socketChannel.write(byteBuffer);
}
System.out.println(" finish sending back");
/* state=0;*/
}