bio和nio?
bio是阻塞io,accept接受连接是阻塞方法,read读取数据也是阻塞方法。
nio是非阻塞io,可以通过channel.configureBlocking(false);来设置为非阻塞。
bio的例子及其思考
bio写的服务器代码如下:
public static void main(String[] args) throws IOException {
final ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(9999));
System.out.println("server start...");
System.out.println("wait for connect...");
while(true){
final Socket socket = serverSocket.accept();//阻塞,直到有客户端链接
System.out.println("connect success,客户端ip:"+socket.getInetAddress()+" port:"+socket.getPort());
InputStream ins = socket.getInputStream();
byte[] bytes = new byte[1024];
while(true){
System.out.println("wait for client send data...");
ins.read(bytes);//阻塞,直到这个客户端发数据
System.out.println(new String(bytes));
}
}
}
客户端如下
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("localhost",9999));
Scanner scanner = new Scanner(System.in);
while(true){
String next = scanner.next();
socket.getOutputStream().write(next.getBytes());
}
}
单线程的服务器,两个while true保证不断有人链接服务器和不断可以接受客户端数据。
可是这段代码其实有很严重的bug,设想A客户链接服务器,发送hello给服务器,
但是此时B客户想来链接服务器,却怎么也连不上。因为服务器一直在第二个while true里等待A发数据。
所以使用bio+单线程是不可能实现多客户端的链接的。常规bio做法就是一个连接一个线程,代码如下。
public static void main(String[] args) throws IOException {
final ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(9999));
System.out.println("server start...");
System.out.println("wait for connect...");
while(true){
final Socket socket = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("connect success,客户端ip:"+socket.getInetAddress()+" port:"+socket.getPort());
InputStream ins = socket.getInputStream();
byte[] bytes = new byte[1024];
while(true){
System.out.println("wait for client send data...");
ins.read(bytes);
System.out.println(new String(bytes));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
如果我们设想accept和read不是阻塞方法,例如
accept方法不阻塞,有人连接时返回Socket 对象,无人链接时返回null。
read方法不阻塞,有数据时返回值>0,无数据时返回值==0
这时候我们再去思考单线程实现多链接就不是没有可能的了。
基于设想的服务器代码如下:
假设accept和read是我们假设的非阻塞方法,下面这段代码是可以做到单线程多连接的。
可惜这两方法是阻塞的,于是我们迫切的需要一种非阻塞的方式去支持单线程多连接,而nio支持非阻塞。
public static void main(String[] args) throws IOException, InterruptedException {
List<Socket> socketList = new ArrayList<>();
final ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(9999));
System.out.println("server start...");
System.out.println("wait for connect...");
while(true){
final Socket socket = serverSocket.accept();//设想不阻塞
if(socket==null){
Thread.sleep(1000);
System.out.println("no one conn");
}else{
System.out.println("connect success,客户端ip:"+socket.getInetAddress()+" port:"+socket.getPort());
socketList.add(socket);
}
for (Socket s:socketList) {
InputStream ins = socket.getInputStream();
byte[] bytes = new byte[1024];
int total = ins.read(bytes);//设想不阻塞
if(total!=0){
System.out.println(new String(bytes));
}
}
}
}
nio单线程多连接
这段代码和我们上面的设想代码几乎思路是一样的
public static void main(String[] args) throws IOException, InterruptedException {
List<SocketChannel> channels = new ArrayList<>();
ByteBuffer buffer = ByteBuffer.allocate(1024);
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(9999));
System.out.println("server start");
System.out.println("wait for connect");
ssc.configureBlocking(false);
while(true){
SocketChannel sc = ssc.accept();//非阻塞
if(sc==null){
Thread.sleep(1000);
System.out.println("no conn");
}else{
sc.configureBlocking(false);
channels.add(sc);
System.out.println("connect success:"+sc.getRemoteAddress());
}
for (SocketChannel channel:channels) {
int total = channel.read(buffer);//非阻塞
if(total!=0){
buffer.flip();
System.out.println("receive data from "+channel.getRemoteAddress()+" data is:"+new String(buffer.array()));
}
}
}
}