Java通信的几种IO设计
[size=large]阻塞IO
同步阻塞最常用的一种用法,使用也是最简单的,但是 I/O 性能一般很差,CPU 大部分在空闲状态。下面是一个简单的基于TCP的同步阻塞的Socket服务端例子:
@Test
public void testBlockIoSocket() throws Exception
{
ServerSocket serverSocket = new ServerSocket(10002);
Socket socket = null;
try
{
while (true)
{
socket = serverSocket.accept();
System.out.println("socket连接:" + socket.getRemoteSocketAddress().toString());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while(true)
{
String readLine = in.readLine();
System.out.println("收到消息" + readLine);
if("end".equals(readLine))
{
break;
}
//客户端断开连接
socket.sendUrgentData(0xFF);
}
}
}
catch (SocketException se)
{
System.out.println("客户端断开连接");
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
System.out.println("socket关闭:" + socket.getRemoteSocketAddress().toString());
socket.close();
}
}
设计分析:
由于服务器端是单线程的,在第一个连接的客户端阻塞了线程后,第二个客户端必须等待第一个断开后才能连接。
所有的客户端连接在请求服务端时都会阻塞住,等待前面的完成。即使是使用短连接,数据在写入 OutputStream 或者从 InputStream 读取时都有可能会阻塞。这在大规模的访问量或者系统对性能有要求的时候是不能接受的。
阻塞IO + 每个请求创建线程/线程池
通常解决这个问题的方法是使用多线程技术,一个客户端一个处理线程,出现阻塞时只是一个线程阻塞而不会影响其它线程工作;为了减少系统线程的开销,采用线程池的办法来减少线程创建和回收的成本。模式如下图:
每当来一个客户端的连接的时候,我们服务器就new 一个线程来处理它。在服务器的主程序是不阻塞的,阻塞的只是这个线程。这样也是我目前最常用的模式。
在单个线程处理中,我人为的使单个线程read后阻塞5秒,就像前面说的,出现阻塞也只是在单个线程中,没有影响到另一个客户端的处理。
这种阻塞IO的解决方案在大部分情况下是适用的,在出现NIO之前是最通常的解决方案,Tomcat里阻塞IO的实现就是这种方式。但是如果是大量的长连接请求呢?不可能创建几百万个线程保持连接。再退一步,就算线程数不是问题,如果这些线程都需要访问服务端的某些竞争资源,势必需要进行同步操作,这本身就是得不偿失的。
非阻塞IO + IO multiplexing Java从1.4开始提供了NIO工具包,这是一种不同于传统流IO的新的IO方式,使得Java开始对非阻塞IO支持;NIO并不等同于非阻塞IO,只要设置Blocking属性就可以控制阻塞非阻塞。至于NIO的工作方式特点原理这里一概不说,以后会写。模式如下图:
public class NioNonBlockingSelectorTest
{
Selector selector;
private ByteBuffer receivebuffer = ByteBuffer.allocate(1024);
@Test
public void testNioNonBlockingSelector()
throws Exception
{
selector = Selector.open();
SocketAddress address = new InetSocketAddress(10002);
ServerSocketChannel channel = ServerSocketChannel.open();
channel.socket().bind(address);
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
while(true)
{
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleKey(selectionKey);
}
}
}
private void handleKey(SelectionKey selectionKey) throws IOException
{
ServerSocketChannel server = null;
SocketChannel client = null;
if(selectionKey.isAcceptable())
{
server = (ServerSocketChannel)selectionKey.channel();
client = server.accept();
System.out.println("客户端: " + client.socket().getRemoteSocketAddress().toString());
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
}
if(selectionKey.isReadable())
{
client = (SocketChannel)selectionKey.channel();
receivebuffer.clear();
int count = client.read(receivebuffer);
if (count > 0) {
String receiveText = new String( receivebuffer.array(),0,count);
System.out.println("服务器端接受客户端数据--:" + receiveText);
client.register(selector, SelectionKey.OP_READ);
}
}
}
}
Java NIO提供的非阻塞IO并不是单纯的非阻塞IO模式,而是建立在Reactor模式上的IO复用模型;在IO multiplexing Model中,对于每一个socket,一般都设置成为non-blocking,但是整个用户进程其实是一直被阻塞的。只不过进程是被select这个函数阻塞,而不是被socket IO给阻塞,所以还是属于非阻塞的IO。
网络IO优化
对于网络IO有一些基本的处理规则如下:
1。减少交互的次数。比如增加缓存,合并请求。
2。减少传输数据大小。比如压缩后传输、约定合理的数据协议。
3。减少编码。比如提前将字符转化为字节再传输。
4。根据应用场景选择合适的交互方式,同步阻塞,同步非阻塞,异步阻塞,异步非阻塞。
[/size]