传统Socket
先用最简单的方式实现Socket连接,
public class SocketServerMain {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666);
System.out.println("accept");
Socket socket = serverSocket.accept();
System.out.println("接收到client...");
byte[] bytes = new byte[1024];
System.out.println("read");
InputStream inputStream = socket.getInputStream();
while (inputStream.read(bytes) != -1){
String out = new String(bytes);
System.out.println(out);
}
}
}
通过telnet 127.0.0.1 6666
accept()在没有接到client连接会一直阻塞,等待client连接上。这也导致必须开启多线程支持多个client连接。且当client空闲时也会一直占用该连接,并不会释放资源,因为获取流数据方法是阻塞的。
测试当我们开启两个telnet窗口,窗口1能成功发送消息。
由于单SocketServer并不能支持并发。于是乎,演变成必须开启多线程操作。
public class SocketServerMain {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666);
while(true) {
System.out.println("ready accept...");
Socket socket = serverSocket.accept();
System.out.println("接收到client...");
SocketThread socketThread = new SocketThread(socket);
new Thread(socketThread).start();
}
}
}
class SocketThread implements Runnable {
private Socket socket;
public SocketThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try{
byte[] bytes = new byte[1024];
System.out.println("read");
InputStream inputStream = socket.getInputStream();
while (inputStream.read(bytes) != -1){
String out = new String(bytes);
System.out.println(out);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
测试:开启两个telnet窗口,两个窗口发送的消息都能被server收到
但是在实际项目运用中,当client数量上升,服务Server线程增多,资源瞬间被消耗殆尽。很显然,这是不可取的。
以上便是传统的BIO实现。
BIO:blocking IO,阻塞式IO
对于阻塞式IO,它所具备的缺点是很明显的。
于是有另一种模型对其进行了改进,NIO模型
NIO模型,也就是no-blocking-io【非阻塞式IO】
核心概念:Selector多路复用器,或者称为选择器,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。也就是说可以单线程处理多个网络连接。
先实现测试一下:
public static void main(String[] args) throws IOException {
System.out.println("服务端开启");
//开启Channel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(6666));
//开启selector选择器
Selector selector = Selector.open();
//注册Selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (selector.select() > 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
// 删除已选的key,以防重复处理
it.remove();
if (!key.isValid()) {
System.out.println("连接不可用");
} else if (key.isAcceptable()) {
// 新连接
System.out.println("服务端接受到新连接");
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false); // 非阻塞
socketChannel.register(selector, SelectionKey.OP_READ); // 注册可读事件
} else if (key.isReadable()) {
// 有客户端发送数据
SocketChannel socketChannel = (SocketChannel) key.channel();
// 开始读取数据
if (socketChannel.read(byteBuffer) == -1) {
// -1 说明客户端关闭了写或者直接close
System.out.println("客户端关闭连接");
socketChannel.close();
key.cancel(); // 取消注册
} else {
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, byteBuffer.limit()));
byteBuffer.clear();
}
}
}
}
}
SelectionKey的注册事件:
OP_ACCEPT:连接可接受操作,仅ServerSocketChannel支持
OP_CONNECT:连接操作,Client端支持的一种操作
OP_READ/OP_WRITE 读就绪/写就绪
通道注册流程,就是把Selector、SelectionKey 和channel的关系建立起来,SelectionKey 对象的有效期间,Selector 会一直监控与 SelectionKey 对象相关的事件,如果事件发生,就会把 SelectionKey 对象加入到 selected-keys 集合中。所以在遍历selectionKey时,需要remove操作。