这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战
阻塞
阻塞模式的设计
- 阻塞模式下,相关方法都会导致线程暂停
- ServerSocketChannel.accept 会在没有连接建立时让线程暂停,即使之后有客户端向服务端发送消息,服务端也接受不到,直到有新客户端连接服务端,不再阻塞在accept方法上。
- SocketChannel.read 会在通道中没有数据可读时让线程暂停,即使之后有新客户端向服务端发起连接请求也接受不了,直到读取完毕,不再阻塞在read方法上
- 阻塞的表现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置
- 单线程下,阻塞方法之间相互影响,几乎不能正常工作,需要多线程支持
- 但多线程下,有新的问题,如果连接数过多,必然导致 OOM(out of memory),并且线程太多,反而会因为频繁上下文切换导致性能降低
- 可以采用线程池技术来减少线程数和线程上下文切换,但治标不治本,如果有很多连接建立,但长时间闲置,会阻塞线程池中所有线程,因此不适合长连接,只适合短连接
对于NIO阻塞模式下,单线程服务端的代码要有下面几个步骤:
- 打开服务端的ByteBuffer用来存储数据
- 创建服务器 ServerSocketChannel
- ServerSocketChannel绑定监听端口
- 创建一个连接集合 ,用来装所有用户的连接 SocketChannel
- accept 建立与客户端连接,SocketChannel 用来与客户端通信
- 接收客户端发送的数据,遍历集合,看有没有用户有发来的数据
public class NIOServer {
public static void main(String[] args) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(100);
try(ServerSocketChannel serverSocket = ServerSocketChannel.open())
{
serverSocket.bind(new InetSocketAddress(8080));
List<SocketChannel> channels = new ArrayList<>();
while(true)
{
SocketChannel sc = serverSocket.accept();
channels.add(sc);
for (SocketChannel asc :channels)
{
asc.read(buffer);
buffer.flip();
ByteBufferUtil.debugAll(buffer);
buffer.clear();
}
}
}catch (IOException e)
{
e.printStackTrace();
}
}
}
复制代码
对于客户端比较简单了,首先创建与i个SocketChannel,然后连接服务端的端口,之后使用write方法通过通道发送数据就行了
public class NIOClient {
public static void main(String[] args) {
try(SocketChannel socketChannel = SocketChannel.open())
{
socketChannel.connect(new InetSocketAddress("localhost",8080));
System.out.println("connecting...");
}catch (IOException e)
{
e.printStackTrace();
}
}
}
复制代码
阻塞模式的分析
之前的博客Java网络编程(一)—— 简单的C/S程序 - 掘金 (juejin.cn)介绍的使用Socket也能实现类似的作用,但是使用Socket需要得到输入流和输出流,没有使用通道方便,但是他依然没有改变阻塞模式的严重问题:当没有新的客户端尝试连接服务器时,服务端程序就会阻塞在accept方法上,不会读取其他客户端发来的消息;当没有用户发来消息时,服务端程序就会阻塞在
read方法上,不会接收新客户端发来的消息。
非阻塞
非阻塞模式的设计
上面分析的在阻塞模式下会在两个地方发生阻塞——accept方法、read方法,因此在NIO中提供了非阻塞状态:
- 可以通过ServerSocketChannel的configureBlocking(false)方法将获得连接设置为非阻塞的。此时若没有连接,accept会返回null
- 可以通过SocketChannel的configureBlocking(false)方法将从通道中读取数据设置为非阻塞的。若此时通道中没有数据可读,read会返回-1
public class NIOServer {
public static void main(String[] args) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(100);
try(ServerSocketChannel serverSocket = ServerSocketChannel.open())
{
serverSocket.bind(new InetSocketAddress(8080));
List<SocketChannel> channels = new ArrayList<>();
while(true)
{
// 设置为非阻塞模式,没有连接时返回null,不会阻塞线程
serverSocket.configureBlocking(false);
SocketChannel sc = serverSocket.accept();
if(sc!=null)
{
System.out.println("----new client----");
channels.add(sc);
}
for (SocketChannel asc :channels)
{
// 设置为非阻塞模式,若通道中没有数据,会返回0,不会阻塞线程
asc.configureBlocking(false);
int readsize = asc.read(buffer);
if(readsize>0){
buffer.flip();
ByteBufferUtil.debugAll(buffer);
buffer.clear();
}
}
}
}catch (IOException e)
{
e.printStackTrace();
}
}
}
复制代码
非阻塞模式的分析
使用非阻塞模式确实不会发生阻塞,至少不会对业务造成影响,但是从性能分析,这种非阻塞状态下会发生CPU空转情况,即可能一直while循环,占用CPU资源。
近期评论