文章目录
io 和nio 之间的那点事
1 传统的io socket
百度网盘下载地址:链接:https://pan.baidu.com/s/1OpOAk_LqvLc_OpfG-3bUug
提取码:dgy6
1.1 编码
这个工具通过实测,这个工具发送数据包是使用 GBK的方式进行编码的
我们编写一个传统的Io 测试类:
package com.liu;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Administrator on 2019/8/28 0028.
*/
public class OioServer {
public static void main(String[] args) throws IOException {
//
ExecutorService service = Executors.newCachedThreadPool();//创建连接池
// 创建socket,监听10010
ServerSocket serverSocket = new ServerSocket(10010);
System.out.println("服务器启动");
while (true){
// 获取一个套接字(阻塞)
Socket socket = serverSocket.accept();// 阻塞消息
service.execute(new Runnable() {
@Override
public void run() {
// 处理业务
handler(socket);
}
});
}
}
private static void handler(Socket socket) {
try {
byte [] bytes = new byte[1024];
InputStream inputStream = socket.getInputStream();
while (true){
int read = inputStream.read(bytes);
if(read != -1){
System.out.print( Thread.currentThread().getName()+"+++");
System.out.println(new String (bytes,0 ,read,Charset.forName("GBK")));
}else {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
System.out.println("socket 关闭");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1.2 工具并发测试
可以通过建立多个client进行定时发送任务;
1.3 并发结果
pool-1-thread-2+++发送
pool-1-thread-1+++范德萨范德萨
pool-1-thread-2+++发送
pool-1-thread-1+++范德萨范德萨
pool-1-thread-2+++发送
pool-1-thread-1+++范德萨范德萨
pool-1-thread-2+++发送
pool-1-thread-1+++范德萨范德萨
pool-1-thread-2+++发送
pool-1-thread-1+++范德萨范德萨
pool-1-thread-2+++发送
pool-1-thread-1+++范德萨范德萨
1.4 结论
通过以上的实例,我们不难看出,我们使用这种传统的io的方式,每当一个客户端连接进来后,我们都需要开销一条线程,去处理这个任务,这样对于我们的系统资源开销很大。
2 nio
2.1 nio 服务类
通过以上的实例,我们可以编制一个nio的server 来测试一下:
package com.liu;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
/**
* Created by Administrator on 2019/8/28 0028.
*/
public class NIOServer {
// / 通道管理器
private Selector selector;
public void initServer(int port) throws IOException {
// 获得一个serversocket 通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置通道为非 阻塞
serverSocketChannel.configureBlocking(false);
// 通道绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(port));
// 通过通道获得一个通道管理器
this.selector=Selector.open();
// 将通道管理器和该通道绑定,并未该通过到注册SelectKey.op_accept事件,注册该事件后
// 当该事件到达时,selector.select()会返回,如果该事件没有到达就会一直阻塞
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
/**
* 轮询的方式监听selector 上是否有需要处理的事件,如果有,则进行处理
* @throws IOException
*/
public void listen () throws IOException {
System.out.println("服务端启动成功!!!");
// 轮询 访问 selector
while (true){
// 当注册事件到达时,方法返回,否则会一直阻塞。
selector.select();
// 获得selector中选中项的迭代器,选中项为注册的事件
Iterator its= this.selector.selectedKeys().iterator();
while (its.hasNext()){
SelectionKey key = (SelectionKey) its.next();
// 删除已经选的key,防治重复处理
its.remove();
handler(key);
}
}
}
/**
* 处理请求
* @param key
*/
private void handler(SelectionKey key) throws IOException {
// 客户端请求连接事件
if (key.isAcceptable()){
handlerAccept(key);
}else if (key.isReadable()){
handlerRead(key);
}
}
/**
* 处理连接事件
* @param key
*/
private void handlerAccept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
// 通过服务 获得客户端连接的通道
SocketChannel channel = serverChannel.accept();
// 将通道设置成非阻塞
channel.configureBlocking(false);
System.out.println("新的客户端连接上来。。。");
// 在和客户端连接成功之后,我们可以接到客户端的信息,需要给通道设置读的权限。
channel.register(this.selector,SelectionKey.OP_READ);
}
/**
* 处理读的事件
* @param key
*/
private void handlerRead(SelectionKey key) throws IOException {
// 服务器可读取的小实习得到事件发生的通道。
SocketChannel channel = (SocketChannel) key.channel();
// 创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
if(read>0){
byte [] data = buffer.array();
String msg = new String(data, Charset.forName("GBK")).trim();
System.out.print(Thread.currentThread().getName()+"======");
System.out.println("服务器接收到的消息:"+msg);
// 回写数据
ByteBuffer out = ByteBuffer.wrap("ok".getBytes());
// 将消息发送给客户端
channel.write(out);
}else {
System.out.println("客户端已经关闭");
key.cancel();
}
}
// 启动服务端测试
public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer();
server.initServer(10020);
server.listen();
}
}
2.2 客户端模拟
客户端模拟,每秒中发送一个暑假给服务端,分别为嘻嘻,哈哈,嘎嘎
2.3 并发结果
服务端启动成功!!!
新的客户端连接上来。。。
新的客户端连接上来。。。
新的客户端连接上来。。。
main======服务器接收到的消息:嘻嘻嘻嘻
main======服务器接收到的消息:嘎嘎嘎
main======服务器接收到的消息:哈哈哈
main======服务器接收到的消息:嘻嘻嘻嘻
main======服务器接收到的消息:嘎嘎嘎
main======服务器接收到的消息:哈哈哈
main======服务器接收到的消息:嘻嘻嘻嘻
main======服务器接收到的消息:嘎嘎嘎
main======服务器接收到的消息:哈哈哈
main======服务器接收到的消息:嘻嘻嘻嘻
main======服务器接收到的消息:嘎嘎嘎
main======服务器接收到的消息:哈哈哈
main======服务器接收到的消息:嘻嘻嘻嘻
main======服务器接收到的消息:嘎嘎嘎
main======服务器接收到的消息:哈哈哈
main======服务器接收到的消息:嘻嘻嘻嘻
main======服务器接收到的消息:嘎嘎嘎
2.4 结论
我们不难看出,在三个客户端时候连上来的时候,我们还是使用同一个线程。这样我们对于服务器的开销就很小。且运行效率要搞的很。