您的位置:首页 > 其它

使用NIO实现非阻塞Socket通信

2012-03-07 20:39 302 查看
服务器端:

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.SelectableChannel;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.ServerSocketChannel;

import java.nio.channels.SocketChannel;

import java.nio.charset.Charset;

public class ServerNio {

public static void main(String[] args) throws IOException{

//打开一个多路复用器对象,作用是监控多个SelectableChannel的IO状况。

Selector selector = Selector.open();

//使用open()方法打开一个未绑定的服务器插口通道,支持非阻塞操作

ServerSocketChannel serverChannel = ServerSocketChannel.open();

//将服务器插口绑定到指定ip和端口号

serverChannel.socket().bind( new InetSocketAddress("127.0.0.1",55555));

//调整服务器插口通道的阻塞模式为非阻塞模式

serverChannel.configureBlocking(false);

//将服务器插口通道注册到多路复用器(selector选择容器中)

//SelectionKey表示注册通道时产生的选择键

serverChannel.register(selector , SelectionKey.OP_ACCEPT);

//创建一个1字节的缓冲区

ByteBuffer buffer = ByteBuffer.allocate(1024);

//指定编码集

Charset charset = Charset.forName("UTF-8");

//服务器不断地调用select()方法,若大于0表示有多少个Channel通道具有可用的I/O操作

while(selector.select() > 0){

//遍历已经注册到selector容器的通道

for(SelectionKey sk: selector.selectedKeys()){

//把正在处理的sk从集合中移除

selector.selectedKeys().remove(sk);

//如果sk对应的通道包含客户端的连接请求

if(sk.isAcceptable()){

//返回创建此键的通道

SelectableChannel sc1 = sk.channel();

//如果这个通道是服务器插口通道类型的

if(sc1 instanceof ServerSocketChannel){

//将其强制转换成服务器插口通道类型的对象

ServerSocketChannel ssc = (ServerSocketChannel)sc1;

//调用accept()方法接受连接并返回普通插口通道类型的I/O接口

SocketChannel socketChannel1 = ssc.accept();

//设置采用非阻塞模式

socketChannel1.configureBlocking(false);

//将插口通道也注册到selector

socketChannel1.register(selector,SelectionKey.OP_READ);

//将sk对应的Channel设置成准备接受其他请求

sk.interestOps(SelectionKey.OP_ACCEPT);

}

}

//如果sk对应的通道有数据需要读取

if(sk.isReadable()){

//获取该SelectionKey对应的Channel,该Channel中有可读得数据

SelectableChannel sc2 = sk.channel();

if(sc2 instanceof SocketChannel){

SocketChannel socketChannel2 = (SocketChannel)sc2;

String msg = "";

//开始读取数据

while(socketChannel2.read(buffer)>0){

buffer.flip(); //重绕(锁定)缓冲区(pos=0,limit=pos)

msg+=charset.decode(buffer);

System.out.println(msg);

buffer.clear(); //重置缓冲区(pos=0,limit=capacity)

}

//将sk对应的Channel设置成准备下一次读取

sk.interestOps(SelectionKey.OP_READ);

//发送给客户端

//如果msg的长度大于0,即聊天信息不为空

if(msg.length()>0){

//遍历该selector里注册的所有SelectKey

for(SelectionKey key:selector.keys()){

//获取该key对应的Channel

SelectableChannel targetChannel = key.channel();

if(targetChannel instanceof SocketChannel){

//将读到的内容写入该Channel中

SocketChannel dest =(SocketChannel)targetChannel;

dest.write(charset.encode(msg));

}

}

}

}

}

}

}

}

}

复制代码
客户端:

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.SocketChannel;

import java.nio.charset.Charset;

import java.util.Scanner;

public class ClientNio {

public static void main(String[] args) throws IOException{

//定义检测SocketChannel的Selector对象

Selector selector = Selector.open();

InetSocketAddress server = new InetSocketAddress("127.0.0.1",55555);

//调用open静态方法创建连接到指定主机的SocketChannel

SocketChannel clientChannel = SocketChannel.open(server);

//设置该客户端插口通道为非阻塞模式

clientChannel.configureBlocking(false);

//注册插口到selector容器

clientChannel.register(selector, SelectionKey.OP_READ);

//定义编码和解码的字符集

Charset charset = Charset.forName("UTF-8");

//启动一个线程用于读取服务器发送来的数据

new ClientNioThread(selector,charset);

//创建键盘输入流

Scanner scanner = new Scanner(System.in);

//向服务器发送数据

while( scanner.hasNextLine()){

//读取键盘输入

String message = scanner.nextLine();

//将键盘输入的内容输出到SocketChannel

clientChannel.write(charset.encode(message));

}

}

}

复制代码
读取服务器端数据的线程:

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.channels.SelectableChannel;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.SocketChannel;

import java.nio.charset.Charset;

public class ClientNioThread extends Thread{

private Selector selector;

private Charset charset;

public ClientNioThread(Selector selector,Charset charset){

this.selector = selector;

this.charset = charset;

this.start(); //启动线程

}

@Override

public void run() {

try{

ByteBuffer buffer = ByteBuffer.allocate(1024);

while( selector.select() > 0 ){

//遍历每个有可用I/O操作Channel对应的SelectionKey

for( SelectionKey sk : selector.selectedKeys() ){

//删除正在处理的SelectionKey

selector.selectedKeys().remove(sk);

SelectableChannel sc = sk.channel();

if( sc instanceof SocketChannel ){

SocketChannel socketChannel = (SocketChannel) sc;

StringBuilder sb = new StringBuilder();

while( socketChannel.read( buffer ) > 0){

buffer.flip();

sb.append( charset.decode( buffer ));

buffer.clear();

}

//打印输出读取的内容

System.out.println( sb.toString());

}

//为下一次读取做准备

sk.interestOps( SelectionKey.OP_READ);

}

}

}catch(IOException e){

e.printStackTrace();

}

}

}

复制代码
总结:

服务器端:

1-获得Selector对象

2 获得ServerSocketChannel 对象serverChannel

3 把serverChannel跟指定的ip和端口(serverChannel对应的ServerSocket)

4 设置非阻塞模式

5 把serverChannel 注册到selector,指定模式(SelectionKey.OP_ACCEPT)

6 反复执行

1> select()选择可以选择的通道

2> 遍历所有的selectedKeys

isAcceptable:打开监听 SocketChannel channel = serverChannel.accept();

把通过监听获得到的channel设置为无阻塞模式

注册channel到selector上,同时指定模式(OP_READ)

为sk的interest集合插入一个值(为下次操作做准备)

sk.interestOps(SelectionKey)

sk.isReadable:根据sk获得它对应的通道:SelectableChannel sc = sk.channel();

类型检查if(sc.instanceof SocketChannel)

如果满足条件做类型转换:SocketChannel socketChannel = (SocketChannel)sc

读取数据(读取ByteBuffer)socketChannel.read(buffer);

向客户端发送:

if(msg.length()>0){

//遍历keys

for(SelectionKey key:selector keys()){

SelectableChannel selectChannel = key.channel();

if(selectChannel instance of SocketChannel){

SocketChannel sssccc = (SocketChannel)selectChannel

sssccc.write(charset.encode(msg));

}

}

}

客户端:

1 获得一个Selector对象selector;

2 获得SocketChannel同时指定要连接的服务器的InetSocketAddress对象

3 设置为非阻塞模式

4 注册SocketChannel到selector同时指定模式为SelectionKey.OP_READ

5 使用主线程向服务器发送数据

6 使用单独的一个线程读取服务器发送来的数据
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: