socket通讯常见模式分析(二)
-
非阻塞I/O通信模型(java NIO)
Java NIO是在jdk1.4开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O。下面是java NIO的工作原理:
由一个专门的线程来处理所有的 IO 事件,并负责分发。
事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。
阅读过一些资料之后,下面贴出我理解的java NIO的工作原理图:
(注:每个线程的处理流程大概都是读取数据、解码、计算处理、编码、发送响应。)
Java NIO的服务端只需启动一个专门的线程来处理所有的 IO 事件,这种通信模型是怎么实现的呢?呵呵,我们一起来探究它的奥秘吧。java NIO采用了双向通道(channel)进行数据传输,而不是单向的流(stream),在通道上可以注册我们感兴趣的事件。一共有以下四种事件:
事件名对应值
服务端接收客户端连接事件SelectionKey.OP_ACCEPT(16)
客户端连接服务端事件SelectionKey.OP_CONNECT(8)
读事件SelectionKey.OP_READ(1)
写事件SelectionKey.OP_WRITE(4)
服务端和客户端各自维护一个管理通道的对象,我们称之为selector,该对象能检测一个或多个通道 (channel) 上的事件。我们以服务端为例,如果服务端的selector上注册了读事件,某时刻客户端给服务端发送了一些数据,阻塞I/O这时会调用read()方法阻塞地读取数据,而NIO的服务端会在selector中添加一个读事件。服务端的处理线程会轮询地访问selector,如果访问selector时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止。下面是我理解的java NIO的通信模型示意图:
具体实现代码如下:
Server端:
public class SocketReceiver{
private int port;
// private int poolSize;
private int timeOut;
private Selector selector=null;
// private ExecutorService executor;
// private ServerSocket serverSocket;
private ServerSocketChannel serverChannel;
private String hostAddress;
private DealService dealService;
private MessageDealService messageDealService;
public void setPort(int port) {
this.port = port;
}
// public void setPoolSize(int poolSize) {
// this.poolSize = poolSize;
// }
public void setTimeOut(int timeOut) {
this.timeOut = timeOut;
}
public void setHostAddress(String hostAddress) {
this.hostAddress = hostAddress;
}
public void setMessageDealService(MessageDealService messageDealService) {
this.messageDealService = messageDealService;
}
/**
* spring加载
*/
private void init(){
System.out.println("init");
String uuid = UUID.randomUUID().toString();
try {
//新建selector
selector = Selector.open();
//新建serversocket
serverChannel = ServerSocketChannel.open();
//绑定本地端口
serverChannel.socket().bind(new InetSocketAddress(hostAddress, port));
//设置非阻塞模式
serverChannel.configureBlocking(false);
//注册至soket服务器至selector
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
new SocketServerThread().start();
LogManager.Transfer_Receiver.debug("UUID:"+uuid+"\nSocketReceiver init succ");
} catch (IOException e) {
LogManager.Transfer_Receiver.debug("UUID:"+uuid+"\nSocketReceiver init fail"+e.getStackTrace().toString());
System.exit(0);
}
}
public class SocketServerThread extends Thread{
String uuid = UUID.randomUUID().toString();
SocketReceiverHander hander = null;
public SocketServerThread() {
hander = new SocketReceiverHander(selector, serverChannel, dealService,uuid, messageDealService);
}
@Override
public void run() {
try {
while(selector.select(timeOut)>0){
hander.listener();
}
} catch (IOException e) {
LogManager.Transfer_Receiver.debug("UUID:"+uuid+"\nSocketServerThread executing fail"+e.getStackTrace().toString());
}
}
}
}
Hander类:
public class SocketReceiverHander{
private Selector selector;
private ServerSocketChannel serverChannel;
private Charset charset=Charset.forName("UTF-8");
private ByteBuffer buffer=ByteBuffer.allocate(MopfConfig.DATA_LENGTH);
private DealService dealService;
private MessageDealService messageDealService;
private String uuid;
public SocketReceiverHander(Selector selector,
ServerSocketChannel serverChannel,
DealService dealService,String uuid,MessageDealService messageDealService) {
super();
this.selector = selector;
this.serverChannel = serverChannel;
this.dealService = dealService;
this.uuid = uuid;
this.messageDealService = messageDealService;
}
public void listener() throws IOException{
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
while(it.hasNext()) {
SelectionKey sk=it.next();
messageDealService.getRequest(sk, uuid, serverChannel, null, buffer, charset, selector, dealService);
messageDealService.getResponse(sk, uuid, charset, dealService);
//去除已经处理过的key
it.remove();
}
}
}
Client类:public class SocketSender {
private InetSocketAddress address;
private String host;
private int port;
private SocketChannel socketChannel;
private ServerSocketChannel serverChannel;
private Selector selector;
private DealService dealService;
private MessageDealService messageDealService;
public void setMessageDealService(MessageDealService messageDealService) {
this.messageDealService = messageDealService;
}
public SocketSender(String host, int port) {
address = new InetSocketAddress(host,port);
}
public SocketSender(){
}
public void send(String req){
String uuid = UUID.randomUUID().toString();
try {
//打开socket通道并将其连接到远程地址。
SocketChannel socketChannel = SocketChannel.open(address);
//设置非阻塞模式
socketChannel.configureBlocking(false);
//设置缓冲区
ByteBuffer byteBufer = ByteBuffer.allocate(MopfConfig.DATA_LENGTH);
//将byte数组包装进缓冲区中
socketChannel.write(ByteBuffer.wrap(req.getBytes()));
dealService = new DealSenderService();
LogManager.SendMessage_Info.debug("UUID:"+uuid+"\n请求报文"+req.toString());
while(true){
/**
* clear() 使缓冲区为一系列新的通道读取或相对放置 操作做好准备:它将限制设置为容量大小,将位置设置为 0。
* flip() 使缓冲区为一系列新的通道写入或相对获取 操作做好准备:它将限制设置为当前位置,然后将位置设置为 0。
* rewind() 使缓冲区为重新读取已包含的数据做好准备:它使限制保持不变,将位置设置为 0。
*/
byteBufer.clear();
int readBytes = socketChannel.read(byteBufer);
if(readBytes>0){
byteBufer.flip();
LogManager.SendMessage_Info.debug("UUID:"+uuid+"\n响应报文"+new String(byteBufer.array(),0,readBytes));
dealService.deal(new String(byteBufer.array(),0,readBytes));
socketChannel.close();
break;
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
String hostname = "192.168.0.101";
int port =10222;
String req ="client=2342342342342";
new SocketSender(hostname,port).send(req);
}
}