IO、
就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:BIO,NIO,AIO, 我这里主要讲解BIO与NIO;
BIO、
BIO 同步阻塞模型,一个客户端连接处理对应一个线程;
代码如下:
package com.tuling.xueyuan.io;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
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);
new Thread(new Runnable() {
@Override
public void run() {
try {
handler(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
private static void handler(Socket clienSocket) throws IOException{
byte[] bytes = new byte[1024];
System.out.println("准备read....");
int read = clienSocket.getInputStream().read(bytes);
System.out.println("read完毕。。。。");
if (read != -1) {
System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
}
clienSocket.getOutputStream().write("HelloClient".getBytes());
clienSocket.getOutputStream().flush();
}
}
启动以上服务端代码,然后通过cmd打开黑窗口, 执行telnet localhost 9000 连接服务端 通过Ctrl+] 进入到客户端 通过send 发送数据
经过测试,等到如下结果
1、当没有客户端进行连接时, 代码到Socket clientSocket = serverSocket.accept();这里就会阻塞,不会在向下执行;
2、一个客户端连接处理对应一个线程,服务端只能接受客户端一次数据;
3、一个客户端连接处理对应一个线程,线程的开销太大了
4、假如A客户端连接启动了a线程,但是A客户端长时间没有进行发送数据,那么a线程就一直在阻塞,占用cpu空间,如果是10000个客户端,只有1000个连接并且发送数据,
但是9000个客户端只是连接没有发送数据,这将是一件崩溃的事件;
根据以上BIO的缺点,就衍生出了NIO
NIO、
同步非阻塞的模型,运用了多路复用器,实现了一个线程可以处理多个客户端的响应;
总结: 创建了一个Selector(多路复用器),服务端向Selector注册(连接事件),客户端也向Selector注册(读事件),当客户端向服务端连接时,触发了连接事件,这时底层操作系统就会把有事件的存入到就绪列表,然后判断当时连接事件,就遍历只有连接事件的客户端,进行连接,如果是读事件,那么就只读取有读事件的数据。
代码如下:
package com.tuling.xueyuan.io;
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, InterruptedException {
// 创建NIO ServerSocketChannel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
// 设置ServerSocketChannel为非阻塞
serverSocket.configureBlocking(false);
// 打开Selector处理Channel,即创建epoll
Selector selector = Selector.open();
// 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
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();
// 遍历SelectionKey对事件进行处理
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 如果是OP_ACCEPT事件,则进行连接获取和事件注册
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
// 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
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("接收到消息:" + new String(byteBuffer.array()));
} else if (len == -1) { // 如果客户端断开连接,关闭Socket
System.out.println("客户端断开连接");
socketChannel.close();
}
}
//从事件集合里删除本次处理的key,防止下次select重复处理
iterator.remove();
}
}
}
}
启动以上代码,然后开启多个客户端,我们证明了:
1、一个线程就可以处理多个客户端;
2、只处理有连接事件的客户端,不会有多余的客户端进行阻塞
3、只处理有数据的客户端,提高性能,防止全部遍历,只把有数据的客户端存入到就绪列表中
本人工作3年中级菜鸟程序员, 最近想回顾一下知识,做了一些简单总结同时也为了自己今后复习方便,如果有逻辑错误,大家体谅,同时也希望大牛们能给出正确答案让我改正,谢谢!