先上一个最基本的socket io的例子,首先是server端:
ServerSocket serverSocket = new ServerSocket(PORT);
while (true) {
Socket client = serverSocket.accept();
new HandlerThread(client);
}
DataInputStream input = new DataInputStream(socket.getInputStream());
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
while(true){
String clientInputStr = input.readUTF();
}
首先new一个ServerSocket,之后循环监听,得到一个新的连接之后,交给一个HandlerThread来处理。
而在HandlerThread当中,也是需要阻塞的方式来读取输入,读不着了就等着。
然后是client端:
socket = new Socket(IP_ADDR, PORT);
DataInputStream input = new DataInputStream(socket.getInputStream());
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
while(true){
String str = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.writeUTF(str);
String ret = input.readUTF();
}
而在client当中,大同小异,new一个socket对象,连接到server上,之后向server发信息,然后等着server的响应。
上面3处标红的地方,server监听接入,server和client的读取input stream,都是通过阻塞的方式来完成。如果说,一个server介入了若干个连接,就需要若干个线程来完成和客户端的通信工作。
然后我们再上一个java nio的例子,首先是server端:
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port));
this.selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
public void listen() throws Exception {
while (true) {
selector.select();
Iterator ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
ite.remove();
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.write(ByteBuffer.wrap(new String("hello client").getBytes()));
channel.register(this.selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
read(key);
}
}
}
}
private void read(SelectionKey key) throws Exception {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("server receive from client: " + msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
channel.write(outBuffer);
}
基本流程解释一下:
首先是准备阶段,生成一个server channel的对象,然后绑定端口,再打开一个selector,注册accept事件。
然后是监听,监听到一个连接,accept,并返回一个socket channel,把这个客户端channel的read事件注册到selector,注意,这里的selector跟之前监听server channel的是同一个,也就是说,目前这个selector要听2件事,一个是监听室针对server channel,另一个是监听client channel有没有数据进来。
有数据进来的话,读数据,然后写个返回回去。
然后是client端:
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
this.selector = Selector.open();
channel.connect(new InetSocketAddress(ip, port));
channel.register(selector, SelectionKey.OP_CONNECT);
public void listen() throws Exception { // 轮询访问selector
while (true) {
selector.select(); // 获得selector中选中的项的迭代器
Iterator ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next(); // 删除已选的key,以防重复处理
ite.remove(); // 连接事件发生
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key.channel(); // 如果正在连接,则完成连接
if (channel.isConnectionPending()) {
channel.finishConnect();
} // 设置成非阻塞
channel.configureBlocking(false);
channel.write(ByteBuffer.wrap(new String("hello server!").getBytes()));
channel.register(this.selector, SelectionKey.OP_READ); // 获得了可读的事件
} else if (key.isReadable()) {
read(key);
}
}
}
}
private void read(SelectionKey key) throws Exception {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("client receive msg from server:" + msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
channel.write(outBuffer);
}
这里也需要简单解释一下流程:
准备阶段,创建一个SocketChannel对象,连接到服务器上,然后打开一个selector,注册connect事件到selector上。
然后同样是监听,监听到服务器已经响应了自己的连接,需要调用一个finishConnect方法,这就算是完成连接了,把read事件注册到selector上。
读到服务器响应,读取数据,返回响应。
到此为止,对于io和nio的实现方式,我们都已经有了大体的了解,我们现在来比较一下2种方式的不同点:
1、多路选择器(selector)的使用,使得一个线程可以针对多个channel进行监听,大大的减少线程的数量,提高了系统运行的效率。
2、io是针对流的,而nio针对的是缓冲区,而在缓冲区当中指针可以任意进行移动,对某些复杂的业务场景,可以极大地简化编程的复杂度。