1.流程图
client 指的是连接的客户端
reactor指的是总的调度类 监听到符合监听条件的selectionkey会调用dispatch分发出去
acceptor指的是管理处理连接状态的组件
黄色的部分分别代表读、解码、处理、编码、发送。这部分可以用handler来代表,处理的是读写的操作,也是整个流程里面最耗时的操作。
单线程模式主要代表的是所有的操作都是在一个线程里面完成。
下面是各个组件的简单代码实现。(都是我自己在IDEA里面跑过的)
2.Reactor代码实现
public class TcpReactor {
private ServerSocketChannel serverSocketChannel;
private Selector selector ;
public TcpReactor(int port) throws IOException {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
selector = Selector.open();
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
selectionKey.attach(new Acceptor(selector,serverSocketChannel));
}
public void run(){
try {
while (true){
System.out.println("监听"+serverSocketChannel.socket().getLocalPort()+"端口的信息");
//selector.select方法时阻塞的 如果不向阻塞可以加上时间参数
if (selector.select()!=0){
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterators = keys.iterator();
System.out.println("keys"+ JSON.toJSONString(keys));
while (iterators.hasNext()){
SelectionKey selectionKey = iterators.next();
System.out.println(selectionKey.isReadable());
dispatch(selectionKey);
iterators.remove();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
IOUtils.closeQuietly(serverSocketChannel);
IOUtils.closeQuietly(selector);
}
}
private void dispatch(SelectionKey next) {
//这里是使用的多态的一种思想 就是会自动根据子类的类型去执行子类或者实现类里面的方法
// 开始看网上的例子就是没想到多态....................
Procrss procrss = (Procrss) next.attachment();
try {
procrss.process();
} catch (Exception e) {
e.printStackTrace();
}
}
3.accptor和handler都实现的接口类
public interface Procrss {
//对当前状态下的selectionKey和socketChannel进行操作
void process () throws Exception;
}
4.acceptor的实现类
public class Acceptor implements Procrss {
private final Selector selector;
private final ServerSocketChannel serverSocketChannel;
public Acceptor(Selector selector, ServerSocketChannel serverSocketChannel) {
this.selector =selector;
this.serverSocketChannel = serverSocketChannel;
}
@Override
public void process() throws IOException {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel!=null){
socketChannel.configureBlocking(false);
SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
selectionKey.attach(new TcpHanler(selectionKey,socketChannel));
}
}
}
5handler的实现类
public class TcpHanler implements Procrss {
private SelectionKey selectionKey;
private SocketChannel socketChannel;
private int state =0;
public TcpHanler(SelectionKey selectionKey, SocketChannel socketChannel) {
this.selectionKey = selectionKey;
this.socketChannel=socketChannel;
}
@Override
public void process(){
try {
readAndSend();
} catch (Exception e) {
e.printStackTrace();
//如果服务端关闭了就会出现java.net.SocketException: Software caused connection abort: recv failed异常
IOUtils.closeQuietly(socketChannel);
}
}
private void readAndSend() throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int read = socketChannel.read(byteBuffer);
//这里需要注意 read的返回值问题
//如果对方主动关闭了socket 那么会返回-1
//如果对方没有主动关闭 它不会返回-1
//有三种情况会返回0
// 1.socketChannel里面没有数据可读了
// 2.Buffer里面position和limit相等为0了
// 3. 客户端的程序发送完毕
while (read!=-1&&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);
}
//如果是1 代表socket客户端已经关闭 就不发送消息了
if (read==-1){
socketChannel.close();
selectionKey.cancel();
}else{
//send只要tcp缓冲区没有满 都是可以写数据的 一般不注册
send();
}
}
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;*/
}
}
6.客户端代码
public static boolean testClient() {
boolean f =false;
try {
System.out.println("===========");
Socket client = new Socket("10.252.69.254", 5063);
System.out.println("Connected to 10.252.69.254");
PrintWriter out = new PrintWriter(client.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String input;
boolean result = true;
while(result) {
out.println("i am sending");
out.flush();
String a=in.readLine();
System.out.println(a);
f=(a!=null);
result=false;
}
client.close();
System.out.println("client stop.");
} catch (UnknownHostException u) {
System.err.println("Don't know about host: " );
} catch (IOException e) {
e.printStackTrace();
}
return f;
}
7.服务端的启动方法
public static void main(String[] args) {
try {
TcpReactor tcpReactor = new TcpReactor(5063);
tcpReactor.run();
} catch (IOException e) {
e.printStackTrace();
}
}
8.写程序时候遇到的几点问题
1.客户端发送的数据。服务端已经接收到,但是无法读出来。
解决:没有调用buffer.flip方法切换到读状态
2.运行的时候报异常
java.net.SocketException:Software caused connection abort: recv failed
原因:客户端已经关闭的情况下,服务端仍然试图发送数据
解决:判断一下是否已经关闭
加上代码
if (read==-1){
socketChannel.close();
selectionKey.cancel();
}
客户端判断服务端是否关闭的方式
Socket client = new Socket("10.252.69.254", 5063);
int available = client.getInputStream().available();
//如果avalible的在值大于0 那么代表代表服务端没有关闭
9.耗时测试
public static void main(String[] args) throws InterruptedException {
Long start = System.currentTimeMillis();
final AtomicInteger atomicInteger = new AtomicInteger(0);
for (int i=0;i<100;i++){
Thread thread= new Thread(new Runnable() {
@Override
public void run() {
boolean result =testClient();
if (result){
atomicInteger.getAndIncrement();
}
}
});
thread.start();
thread.join();
}
System.out.println("一共成功接收到响应的数目"+atomicInteger.get());
System.out.println("消耗的时间为"+(System.currentTimeMillis()-start));
}
100个线程的时候
一共成功接收到响应的数目100
消耗的时间为366
500个线程
一共成功接收到响应的数目500
消耗的时间为1451
1000个线程
一共成功接收到响应的数目1000
消耗的时间为2874
5000个线程
一共成功接收到响应的数目5000
消耗的时间为9070
下节课讲一下多线程模式下Reactor的设计来提高响应的速度。