我们必定接触过的JDK提供的Socket网络编程,它其实就是比较典型的阻塞模式。接下来就详细讲讲这阻塞二字。
我们服务器端启动后,通过accept()方法来处理客户端的连接,当客户端连接成功后,会通过read()方法来接收客户端发送的数据。这两个方法都是阻塞的,程序运行到accept()方法就会被阻塞住,当有客户端连接成功才会解除阻塞继续运行,然后运行到read()方法又会阻塞,直到客户端发送数据菜户解除阻塞。
那么就会出现这样的一个情况,当一个客户端连接成功后,在read()方法阻塞住,这时候另一个客户端是连接不上的。
如果执行accept()方法等待客户端连接时,这个时候其他已连接的客户端发送数据是接收不到的。这就是阻塞。当然也就解决方法,那就是来一个客户端连接就开一个线程。
服务器端代码:
package com.hs.netty.network;
import com.google.common.base.Charsets;
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.List;
public class ServerSocketChannelTest {
public static void main(String[] args) throws IOException {
// 首先创建一个缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
// 创建服务器
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置端口为8080
serverSocketChannel.bind(new InetSocketAddress(8080));
// 创建一个集合,存放每个客户端的SocketChannel、
List socketChannelList = new ArrayList<>();
// 建立客户端的连接,因为不会连接一个客户端程序就结束了,所以需要放在死循环中
while (true){
// accept() 建立与客户端之间的连接,程序运行到这里会阻塞
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("客户端成功连接服务器!!!");
// 得到一个客户端连接后存入list集合中
socketChannelList.add(socketChannel);
// 然后再遍历所有客户端连接,从SocketChannel中获取客户端传递过来的数据
for (SocketChannel channel : socketChannelList) {
// read()方法也会让程序阻塞
channel.read(byteBuffer);
byteBuffer.flip();
System.out.println("客户端传递的数据为:" + Charsets.UTF_8.decode(byteBuffer).toString());
byteBuffer.clear();
}
}
}
}
客户端代码
package com.hs.netty.network;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
public class SocketChannelTest {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
// 连接服务器
socketChannel.connect(new InetSocketAddress("localhost",8080));
System.out.println("客户端已连接服务器!!!");
}
}
非阻塞模式
因为阻塞模式下,出现的问题很明显,就有了非阻塞模式的概念,它就是让accept() 方法和 read() 方法 在运行时不会阻塞。
具体做法就是让ServerSocketChannel和SocketChannel都调用configureBlocking(false);方法即可设置为非阻塞模式
public class ServerSocketChannelTest {
public static void main(String[] args) throws IOException {
// 首先创建一个缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
// 创建服务器
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置为非阻塞模式,影响的就是serverSocketChannel.accept()方法------------------------
serverSocketChannel.configureBlocking(false);
// 设置端口为8080
serverSocketChannel.bind(new InetSocketAddress(8080));
// 创建一个集合,存放每个客户端的SocketChannel
List socketChannelList = new ArrayList<>();
// 建立客户端的连接,因为不会连接一个客户端程序就结束了,所以需要放在死循环中
while (true) {
// accept() 建立与客户端之间的连接,阻塞模式下程序运行到这里会阻塞,有客户端连接了才会继续往下执行
// 如果是非阻塞状态下,没有客户端连接这里就会返回一个null---------------------------
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
System.out.println("客户端成功连接服务器--->" + socketChannel);
// 客户端SocketChannel也可以设置为非阻塞状态 方法还是一样的,这里影响的就是下面read()方法------------
socketChannel.configureBlocking(false);
// 得到一个客户端连接后存入list集合中
socketChannelList.add(socketChannel);
}
// 然后再遍历所有客户端连接,从SocketChannel中获取客户端传递过来的数据
for (SocketChannel channel : socketChannelList) {
// 阻塞模式下read()方法也会让程序阻塞,不过上面设置了客户端SocketChannel为非阻塞模式
// 非阻塞模式下,如果没有读取到数据,该方法会返回0
int read = channel.read(byteBuffer);
if (read != 0) {
byteBuffer.flip();
System.out.println("客户端传递的数据为:" + Charsets.UTF_8.decode(byteBuffer).toString());
byteBuffer.clear();
}
}
}
}
}
非阻塞模式下也有一个很严重的问题,那就是即使没有连接事件 或者是 客户端没有向服务器发送数据,但是服务器端的程序还是会一直执行死循环,这样会很消耗cpu的资源。
多路复用紧接着就有了多路复用来解决非阻塞模式下不停进行死循环的问题。
单线程可以配合Selector完成对多个Channel可读写事件的监控,这称之为多路复用。它在非阻塞的基础上加了事件概念,只有事件发生了才会让线程继续运行,如果没有事件发生就是阻塞状态。



