BIO代码
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketSever {
public static void main(String[] args) throws IOException {
//
ServerSocket serverSocket = new ServerSocket(9000);
while(true){
System.out.println("等待连接");
// 阻塞方法
Socket clientSocket = serverSocket.accept();
System.out.println("有客户端连接了...");
handler(clientSocket);
}
}
private static void handler(Socket clientSocket) throws IOException {
byte[] bytes = new byte[1024];
System.out.println("准备read...");
// 接受客户端的数据,阻塞方法
int read = clientSocket.getInputStream().read(bytes);
System.out.println("read 完毕。。。");
if(read !=-1){
System.out.println("接受到客户端请求"+new String(bytes,0,read));
}
}
}
NIO 简单版本
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class NioServer {
//保存客户端连接
static List<SocketChannel> channelList = new ArrayList<>();
public static void main(String[] args) throws IOException {
// 创建NIO serverSocketChannel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
// 设置SeverSocketChannel 非阻塞
serverSocket.configureBlocking(false);
System.out.println("服务启动成功");
while (true){
SocketChannel socketChannel = serverSocket.accept();
if(socketChannel!=null){
System.out.println("连接成功");
// 设置SocketChannel 为非阻塞
socketChannel.configureBlocking(false);
// 保存客户端连接在list 中
channelList.add(socketChannel);
}
// 遍历连接进行数据读取
Iterator<SocketChannel> iterator = channelList.iterator();
while (iterator.hasNext()){
SocketChannel sc = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
// 非阻塞模式read方法不会阻塞,否则会阻塞
int len = sc.read(byteBuffer);
// 如果有数据
if(len>0){
System.out.println("收到消息:"+new String(byteBuffer.array()));
}else if(len ==-1){
iterator.remove();
System.out.println("客户端断开连接");
}
}
}
}
}
NIO 简单版本会有一个问题: 它会把所有连接都遍历,不管连接是否有数据读写进来。
会浪费大量的资源
NIO selector 版本代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NioSelectorServer {
public static void main(String[] args) throws IOException {
// 创建 NIO SeverSocketChannelS
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
// 设置 serverSocket 为非阻塞
serverSocket.configureBlocking(false);
// 打开selector处理channel ,即创建epoll
Selector selector = Selector.open();
// 把ServerSocketChannel 注册到selector 上,并且selector 感知客户端的accept
SelectionKey selectionKey = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务启动成功");
while (true) {
// 阻塞等待要处理的事件发生
selector.select();
// 获取selector 中注册的全部事件的 selectionKey 实列
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 遍历Selection Key 对事件进行处理
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 如果是 OP_ACCEPT事件 则进行连接获取事件和事件注册
if (key.isAcceptable()) {
ServerSocketChannel sever = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = sever.accept();
socketChannel.configureBlocking(false);
// 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
SelectionKey selKey = socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接成功");
} else if (key.isReadable()) {
// 如果是OP_READ 事件,进行读取
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = socketChannel.read(byteBuffer);
// 如果有数据,把数据打印出来
if (len > 0) {
System.out.println("接受到消息:" +key+ new String(byteBuffer.array()));
} else if (len == -1) {
System.out.println(key+"客户端断开连接,关闭socket");
socketChannel.close();
}
}
// 从事件集合里删除本次处理的key ,防止下次select 重复处理
iterator.remove();
}
}
}
}
这里的代码都是创建一个服务端等待客户端连接并接受内容
不同的在于 BIO 是阻塞的,当客户端连接后服务端就会阻塞直到客户端输入或者关闭。
如果是100个客户端要连接就需要启动100个线程去处理。
NIO 可以一个线程处理100个客户端,并且都是非阻塞模式
nio是通过调用系统的epoll 函数要用监听的方式来处理IO
这就要求服务端和客户端都注册到selector
注册后有数据变动就会出发事件,并且把事件放到事件列表
select poll epoll 的区别
JDK1.4开始有NIO,那时候用的是select,JDK把chanel列表哪出来遍历
,在找出那些有数据的进行处理。如果列表里有1W个channel ,其中只有100 有发送数据,那效率很低
JDK1.5后用epoll ,采用事件回调方式,channel列表还是有1W个,但是不再轮询channl,而是selector ,效率变高了
而poll是linux系统的另一个函数和select 类似,只是他没有数量上限
select | poll | epoll | |
---|---|---|---|
操作方式 | 遍历 | 遍历 | 回调 |
底层实现 | 数组 | 链表 | 哈希表 |
IO效率 | 每次遍历都进行线性遍历,时间复杂度O(N) | 每次遍历都进行线性遍历,时间复杂度O(N) | 事件通知方式,每当有IO事件就绪,系统注册的回调函数就会被调用,时间复杂度O(1) |
最大连接 | 有上限 | 无上限 | 无上限 |