java socket编程大体有三个技术阶段,大致区别在于:
- bio(java1) 阻塞同步 流式传输
- nio(java 1.4) 非阻塞同步块传输 适应于轻量级连接 如聊天 项目上基本用框架:Netty、Mina
- aio(java1.7) 异步非阻塞 适用于重量级 如相册服务
这里重点说一下NIO,实现它主要是由三个组件:1.selector(能够进行多路复用,一个线程可处理多个channel,极大减少线程数,读写线程数量推荐使用cpu线程数,充分利用cpu资源,减少线程切换带来的损失) 2.channel 3.buffer(相比较)。这三个组件都值得去查阅一番。NIO使用这三个组件的工作流程大致如下:
接下来直接上代码:
/**
* @author wwd
* @data 2019/10/30
* @project MuiltiDataImport
**/
public class NioTest {
private static Charset charset = Charset.forName("utf-8");
public static void main(String[] args) throws IOException {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
//创建一个selector
Selector selector = Selector.open();
//创建channel 通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定9200端口
serverSocketChannel.bind(new InetSocketAddress(9200));
//接下来需要serverSocketChannel绑定到selector
//指定非阻塞的方式
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//一个线程负责选择就绪的channel 这里的话就是主线程循环
while (true) {
//阻塞选择就绪的事件,select()可中断的
int readyChannelcount = selector.select();
if(readyChannelcount ==0){
continue;
}
Set<SelectionKey> selectedKeys = selector.selectedKeys();
//整个遍历器
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()){
SelectionKey key = keyIterator.next();
//连接进来了
if(key.isAcceptable()){
System.out.println("连接进来了");
SocketChannel socketChannel = serverSocketChannel.accept();
//注册到selector,用它来监测 注意ServerSocketChannel 和 SocketChannel是两个层次
socketChannel.configureBlocking(false);
//监测可读取
socketChannel.register(selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
System.out.println("数据发过来了,可以去读取了");
//交到线程池中去处理吧
newFixedThreadPool.submit(new SocketProcess(key));
//值得注意的是 这里由于使用的是线程池 可能处理的不及时(线程就绪等待等等),需要及时取消掉改
key.cancel();
}else if(key.isWritable()){
System.out.println("数据可以发送出去了");
}else if(key.isConnectable()){
System.out.println("我已经联通了其他的服务器了");
}
//处理完了就去掉
keyIterator.remove();
}
}
}
//处理类
static class SocketProcess implements Runnable{
SelectionKey key;
public SocketProcess(SelectionKey key){
super();
this.key =key;
}
@Override
public void run() {
SocketChannel channel = (SocketChannel)key.channel();
//读数据
//1.创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
//读取到buffer的数量 为-1 读完
int readBuffer = channel.read(buffer);
while (readBuffer!=-1){
//转换buffer模式,从写模式转换到读模式 读取其中的值 使得buffer可向外输出
//具体转换position和limit值
buffer.flip();
//看buffer缓冲还有没有东西了
while(buffer.hasRemaining()){
CharsetDecoder charsetDecoder = charset.newDecoder();
CharBuffer decode = charsetDecoder.decode(buffer);
System.out.println( decode.toString());
/* System.out.println((char)buffer.get());*/
}
//清除缓存 还有一个buffer.compact()整理已读的 未读到的提前
buffer.clear();
readBuffer=channel.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
最后可以使用telnet命令进行简单的测试。结果如下: