2020年开年不平凡,自己在家呆着重新学习一下io
io分为bio,nio,aio
其中netty属于nio,aio目前还没有广泛应用,将来有很大的潜力
理解几个概念
同步和异步
阻塞和非阻塞
io多路复用
I/O多路复用,I/O是指网络I/O, 多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。
简单来说:就是使用一个或者几个线程处理多个TCP连接 最大优势是减少系统开销小,不必创建过多的进程/线程,也不必维护这些进程/线程
Reactor反应堆模式:基于事件驱动的,常见的有redis,netty等应用
epoll
1)没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄
2)效率提高,使用回调通知而不是轮询的方式,不会随着FD数目的增加效率下降
3)通过callback机制通知,内核和用户空间mmap同一块内存实现
首先图解io的简单交互流程
从流程图可以看出,外部交互首先是socket通过网卡,通过对应的端口进入,之后通过数据准备阶段进入内核空间,内核空间再经过数据拷贝到用户空间,进而应用程序进行处理。其他和涉及到同步和异步,阻塞和非阻塞的理解,这里就不做介绍了。
java.io模型
BIO:jdk1.4之前主要网络io编程的方式,同步阻塞
bio多线程 请求阻塞和读写阻塞,一个线程服务一个客户端,可以服务多个客户端
BIO聊天室
TalkServerBIO bio服务端
//聊天室服务端bio实现
public class TalkServerBIO {
//客户端列表
public static List userList = new ArrayList<>();
public static void main(String[] args) throws IOException {
//服务端端口设置
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("美女聊天室等待您的连接......");
while (true){
Socket socket = serverSocket.accept();
System.out.println("尊敬的客户欢迎您的到来");
MyChannel myChannel = new MyChannel(socket);
new Thread(myChannel).start();
userList.add(myChannel);
}
}
}
TalkClientBIO bio客户端实现
//聊天室客户端bio实现
@Slf4j
public class TalkClientBIO {
private static File file = new File("Client.propertise");
private static Properties properties = new Properties();
private static String HOST = "";
private static int PORT =0;
//初始化端口启动信息
static {
if(!file.exists()){
try {
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
properties.setProperty("hostname","localhost");
properties.setProperty("port","9999");
properties.store(fos,null);
fos.close();
} catch (IOException e) {
log.error("创建文件失败");
e.printStackTrace();
}
}
try {
properties.load(new FileInputStream(file));
HOST = properties.getProperty("hostname");
PORT = Integer.parseInt(properties.getProperty("port"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Socket socket = new Socket(HOST,PORT);
Scanner input = new Scanner(System.in);
System.out.println("客户端已经启动,请输入您的昵称: ");
String name = input.next();
System.out.println("已经进入聊天室!");
new Thread(new SendUtil(socket),name).start();
new Thread(new ReceiveUtil(socket),name).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
MyChannel 消息通道的实现
//server服务端的管道通讯实现
@Slf4j
public class MyChannel implements Runnable {
//输入流
private DataInputStream dis;
//输出流
private DataOutputStream dos;
//管道关闭标识
private boolean isStop = Boolean.FALSE;
/**
* 构造函数获取连接相关信息
* @param client
*/
public MyChannel(Socket client){
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
log.info("管道消息异常{}",client.getInetAddress());
colse();
}
}
@Override
public void run() {
while (!isStop){
sendOther();
}
}
/**
*
* 获取消息
* @return
*/
public String receiveMsg(){
String msg ="";
try {
msg = dis.readUTF();
System.out.println(Thread.currentThread().getName()+":"+msg);
} catch (IOException e) {
colse();
TalkServerBIO.userList.remove(this);
}
return msg;
}
public void sendMsg(String s){
if(!StringUtils.isEmpty(s)){
try {
dos.writeUTF(s);
dos.flush();
} catch (IOException e) {
e.printStackTrace();
isStop = true;
colse();
}
}
}
/**
* 给其他客户端发送消息
*/
public void sendOther(){
String s = this.receiveMsg();
List userList = TalkServerBIO.userList;
for(MyChannel myChannel:userList){
if(this == myChannel){
continue;
}
//给其他人发送消息
myChannel.sendMsg(s);
}
}
/**
* 关闭连接
*/
public void colse(){
isStop = true;
if(null!=dis){
try {
dis.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
if(null!=dos){
try {
dos.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
//发送信息工具类
public class SendUtil implements Runnable {
private DataOutputStream dos = null;
private boolean isStop = Boolean.FALSE;
private BufferedReader br = null;
public SendUtil(Socket socket){
br = new BufferedReader(new InputStreamReader(System.in));
try {
dos = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
colse();
}
}
/**
* 发送消息
*/
public void sendMessage(){
try {
dos.writeUTF(Thread.currentThread().getName()+"-->说:"+br.readLine());
dos.flush();
} catch (IOException e) {
colse();
e.printStackTrace();
}
}
@Override
public void run() {
while (!isStop){
this.sendMessage();
}
}
/**
* 关闭连接
*/
public void colse(){
isStop = true;
if(null!=dos){
try {
dos.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
//获取消息工具类
public class ReceiveUtil implements Runnable {
private DataInputStream ios = null;
private boolean isStop = Boolean.FALSE;
private BufferedReader br = null;
public ReceiveUtil(Socket socket){
try {
ios = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
e.printStackTrace();
colse();
}
}
public void receiveMessage(){
String msg ="";
try {
msg = ios.readUTF();
System.out.println(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (!isStop){
this.receiveMessage();
}
}
/**
* 关闭连接
*/
public void colse(){
isStop = true;
if(null!=ios){
try {
ios.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
通过这个例子可以看出,每新增一个客户端就会服务端就会新起一个线程,那么在大量客户请求的时候,会出现服务器线程资源不够使用,导致服务不可用.即使使用线程池也改变不了根本问题。如果客户端对应的线程一直没有读写操作,则线程一直是阻塞状态,极大浪费资源。
NIO模型出现了,jdk1.4之后,特点同步非阻塞
nio聊天室实现
//服务端
@Slf4j
public class TalkServerNIO {
//定义端口号
private static int PORT = 8848;
//用于字符集解码
private Charset charset = Charset.forName(“UTF-8”);
//用于接收数据的缓冲区
private ByteBuffer rBuffer = ByteBuffer.allocate(1024);
//用于发送数据的缓冲区
private ByteBuffer sBuffer = ByteBuffer.allocate(1024);
//用于存放客户端socketchannel集合
private Map
//用于监听通道事件
private static Selector selector;
public static void main(String[] args) {
TalkServerNIO talkServerNIO = new TalkServerNIO(8848);
talkServerNIO.listen();
}
public TalkServerNIO(int port) {
PORT = port;
try {
init();
} catch (Exception e) {
e.printStackTrace();
log.error("服务器初始化异常", e);
}
}
//初始化服务器,注册侦听事件
private void init() throws IOException {
//打开一个serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//通道设置为非阻塞
serverSocketChannel.configureBlocking(false);
//获取该通道的serverSocket
ServerSocket serverSocket = serverSocketChannel.socket();
//绑定端口号
serverSocket.bind(new InetSocketAddress(PORT));
//打开侦听选择器
selector = Selector.open();
//注册连接侦听事件到选择器上面
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动,端口号为:" + PORT);
}
//服务器轮询监听,select方法会一直阻塞直到有关事件发送或者超时
public void listen() {
while (true) {
try {
// log.info("等待连接请求.....");
selector.select();//返回值为本次触发的事件数
//获取事件集合
Set selectionKeys = selector.selectedKeys();
//处理各个事件
selectionKeys.forEach(selectionKey -> handle(selectionKey));
selectionKeys.clear();//清除处理过的事情
} catch (IOException e) {
e.printStackTrace();
}
}
}
//处理对应事件
private void handle(SelectionKey selectionKey) {
// log.info("有请求进来,需要处理....");
//有客户端请求连接
if (selectionKey.isAcceptable()) {
//获取连接请求的channel
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
try {
SocketChannel clent = server.accept();
clent.configureBlocking(false);
clent.register(selector, SelectionKey.OP_READ);
clientMap.put(getClientName(clent), clent);
} catch (IOException e) {
e.printStackTrace();
}
}
//客户端法消息了
else if (selectionKey.isReadable()) {
//客户端通道
SocketChannel client = (SocketChannel) selectionKey.channel();
//清空读缓存
rBuffer.clear();
try {
int bytes = client.read(rBuffer);
if (bytes > 0) {
//设置换从区初始位置开始读取数据
rBuffer.flip();
//解码缓冲区数据
String receiveText = String.valueOf(charset.decode(rBuffer));
System.out.println(client.toString() + ":" + receiveText);
dispatch(client, receiveText);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//转发消息给其他客户端,相当于bio重的sendOther
private void dispatch(SocketChannel clinet, String info) {
if (!clientMap.isEmpty()) {
for (Map.Entry entry : clientMap.entrySet()) {
SocketChannel temp = entry.getValue();
if (!clinet.equals(temp)) {
sBuffer.clear();
sBuffer.put(charset.encode(getClientName(clinet) + ":" + info));
sBuffer.flip();
try {
temp.write(sBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//生成客户端名称或者昵称
private String getClientName(SocketChannel client) {
Socket socket = client.socket();
return "[" + socket.getInetAddress().toString().substring(1) + ":" + Integer.toHexString(client.hashCode()) + "]";
}
}
客户端的实现
@Slf4j
public class TalkClientNIO {
//服务器地址
private InetSocketAddress SERVER = null;
//用于接收数据的缓冲区
private ByteBuffer rBuffer = ByteBuffer.allocate(1024);
//用于发送数据的缓冲区
private ByteBuffer sBuffer = ByteBuffer.allocate(1024);
//用于监听通道事件
private static Selector selector;
//用于编码或者解码
private Charset charset = Charset.forName("UTF-8");
public TalkClientNIO(int port){
SERVER = new InetSocketAddress("127.0.0.1",port);
//初始化客户端
init();
}
//初始化客户端
private void init(){
try {
//打开SocketChannel
SocketChannel socketChannel = SocketChannel.open();
//非阻塞
socketChannel.configureBlocking(false);
//打开监听通道
selector = Selector.open();
//将通道注册在选择器上面且侦听连接事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
//进行连接
socketChannel.connect(SERVER);
while (true){
//调用select方法选择有事情调用的通道
selector.select();
Set selectionKeys = selector.selectedKeys();
//进行处理
selectionKeys.forEach(selectionKey -> handle(selectionKey));
selectionKeys.clear();//清理处理过的事件
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void handle(SelectionKey selectionKey){
//连接就绪事件
if(selectionKey.isConnectable()){
//获取有连接事件的channel
SocketChannel client = (SocketChannel) selectionKey.channel();
//如果正在连接状态
if(client.isConnectionPending()){
try {
//表示连接成功
client.finishConnect();
System.out.println("完成和服务端的连接...");
//启动线程侦听客户端的输入
new Thread(new Runnable() {
@Override
public void run() {
while (true){
sBuffer.clear();
Scanner scanner = new Scanner(System.in);
String sendText = scanner.next();
sBuffer.put(charset.encode(sendText));
sBuffer.flip();
try {
client.write(sBuffer);
}catch (Exception e){
log.error("输入消息异常...");
}
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
try {
//注册可读事件
client.register(selector,SelectionKey.OP_READ);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
}
//可读事件有从服务端传过来的消息,读取输入到屏幕上
else if(selectionKey.isReadable()){
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
rBuffer.clear();
try {
int count = socketChannel.read(rBuffer);
if(count>0){
String receiveText = new String(rBuffer.array(),0,count);
System.out.println(receiveText);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TalkClientNIO talkClientNI = new TalkClientNIO(8848);
}
}
nio的Reactor的模型
Reactor可以分为两部分:
一部分是Reactor自身,处理客户端的连接和请求
一部分是io处理
1.单Reactor单线程
所有的请求全部由主线程来响应
客户端发送请求:主线程多路复用,从selector中循环进行侦听连接accept,然后注册获取连接的客户端进行read监听客户端连接上之后,进行写操作,服务端读数据.
2.单Reactor多线程
使用线程池来处理业务io
3.主从Reactor多线程
java nio缺点
1.学习难度大,需要熟练掌握技能socketchannel,buffer等
2.开发复杂很多业务处理需要经验,比如网络中断,网络异常等
3.臭名昭著的epoll空轮询bug导致系统瘫痪
netty是一个高性能的网络通讯框架,有jboss公司开发维护。
线程模型:主从Reactor多线程基础上面的改进
使用了高效的零拷贝技术:
mmp,sendfile两种实现方式
正常流程中读写操作需要4次拷贝和3次上下文切换
mmp改进之后是3次拷贝3次上下文切换
sendfile 在linux 2.4之前 3次拷贝2次上下文切换
sendfile在linux2.4之后 2次拷贝 2次上下切换
做到真正的零拷贝:所谓零拷贝是指cpu参与的内存拷贝
dma拷贝是不可避免的,dma是指directory memory access
netty 聊天室实战
NettyServer
public class NettyServer {
private static int PORT = 8848;
public NettyServer(int port){
PORT = port;
}
public void start() {
//线程组线程的大小默认值为cup*2 看实际情况来设置,如果请求并发量不高,处理io比较耗时,
//可以 bossgroup设置少一些,workergroup少一些
//接收客户端连接的处理线程组
//获取底层的cpu核数NettyRuntime.availableProcessors() * 2
System.out.println("cpu个数:"+NettyRuntime.availableProcessors());
EventLoopGroup bossGroup = new NioEventLoopGroup();
//工作线程组 主要负责网络io读写交互
EventLoopGroup workerGroup = new NioEventLoopGroup();
//netty用于启动nio服务器的启动类,降低开发复杂度
ServerBootstrap bootstrap = new ServerBootstrap();
//AbstractBootstrap 设置bossGroup
//重要概念链式编程 设置线程组
bootstrap = bootstrap.group(bossGroup,workerGroup);
//设置channel模式
bootstrap = bootstrap.channel(NioServerSocketChannel.class);
//设置处理器的回调请求
bootstrap = bootstrap.childHandler(new NettyServerInitalizer());
//设置NioServerSocketChannel的tcp参数
// bootstrap = bootstrap.option(ChannelOption.SO_BACKLOG,1024);
// bootstrap = bootstrap.option(ChannelOption.SO_KEEPALIVE,true);
//绑定侦听端口,调用sync同步阻塞方法等待绑定操作完成,完成后返回channelfuture
try {
ChannelFuture future = bootstrap.bind(PORT).sync();
System.out.println("服务器启动......");
//调用sync方法进行阻塞,等待服务端链路关闭之后main函数才能退出
future.channel().closeFuture().sync();
System.out.println("服务器关闭......");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//优雅退出,释放线程资源
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new NettyServer(PORT).start();
}
}
public class NettyServerInitalizer extends ChannelInitializer {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
System.out.println("有客户端连接:"+ch.remoteAddress());
ChannelPipeline channelPipeline = ch.pipeline();
//设置解码器
channelPipeline.addLast("decode",new StringDecoder());
//设置编码器
channelPipeline.addLast("encode",new StringEncoder());
//设置事件响应的处理器
channelPipeline.addLast("handler",new NettyServerHandler());
}
}
public class NettyServerHandler extends SimpleChannelInboundHandler {
//定义通道组,实例化一个全局实例
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* 当用客户端连接时,handlerAdder会执行,把客户端的通道记录下来,加入队列
* @param ctx
* @param msg
* @throws Exception
*/
public void handlerAdded(ChannelHandlerContext ctx, String msg) throws Exception {
//获取客户端通道
Channel currentClient = ctx.channel();
//全部客户端通道
for(Channel channel:channels){
if(channel!=currentClient){
channel.writeAndFlush("[欢迎"+currentClient.remoteAddress()+"]进入聊天室\n");
}
}
//把最新的客户端加入队列
channels.add(currentClient);
}
/**
* 当客户端有数据写入的时候触发
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//获取客户端通道
Channel currentClient = ctx.channel();
//全部客户端通道
for(Channel channel:channels){
if(channel!=currentClient){
channel.writeAndFlush("[用户:"+currentClient.remoteAddress()+"说]"+msg
+"\n");
}else {
channel.writeAndFlush("[我说:]"+msg +"\n");
}
}
//把最新的客户端加入队列
channels.add(currentClient);
}
/**
* 客户端离开的时候进行的处理
* @param ctx
*/
public void handlerRemoved(ChannelHandlerContext ctx){
//获取客户端通道
Channel leaveClient = ctx.channel();
//通知其他客户端离开
for(Channel channel:channels){
if(leaveClient!=channel){
channel.writeAndFlush("[再见:"+leaveClient.remoteAddress()+"]离开聊天室\n");
}
}
}
/**
* 当前用户在线
* @param ctx
*/
public void channelActive(ChannelHandlerContext ctx){
Channel currentChannel = ctx.channel();
System.out.println("["+currentChannel.remoteAddress()+"]在线");
}
/**
* 当前用户离线
* @param ctx
*/
public void channelInactive(ChannelHandlerContext ctx){
Channel currentChannel = ctx.channel();
System.out.println("["+currentChannel.remoteAddress()+"]离线");
}
/**
* 客户端通讯异常
* @param ctx
* @param throwable
*/
public void exceptionCaught(ChannelHandlerContext ctx,Throwable throwable){
Channel currentChannel = ctx.channel();
System.out.println("["+currentChannel.remoteAddress()+"]通讯异常");
ctx.close();
}
}
//client端实现
public class NettyClient {
private String HOST;
private int PORT;
public NettyClient(String host,int port){
HOST = host;
PORT = port;
}
public void start(){
/**
* 事件循环组
*/
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
//客户端启动
Bootstrap bootstrap = new Bootstrap();
//客户端添加线程组
bootstrap = bootstrap.group(eventLoopGroup);
//设置客户端通道
bootstrap = bootstrap.channel(NioSocketChannel.class);
//设置客户端事件处理器
bootstrap.handler(new NettyClientInitalizer());
try {
//进行服务器连接获取客户端channel
Channel channel = bootstrap.connect(HOST,PORT).sync().channel();
//获取系统输入的信息
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
while (true){
channel.writeAndFlush(input.readLine()+"\n");
}
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new NettyClient("localhost",8848).start();
}
}
public class NettyClientInitalizer extends ChannelInitializer {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
System.out.println("客户端开始启动....");
//socket信息通道
ChannelPipeline channelPipeline = socketChannel.pipeline();
//设置解码器
channelPipeline.addLast("decode",new StringDecoder());
//设置编码器
channelPipeline.addLast("encode",new StringEncoder());
//设置处理器
channelPipeline.addLast("handler",new NettyClientHandler());
}
}
public class NettyClientHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
}
AIO实现聊天室
服务端
public class AIOServer {
private static final String LOCALHSOT="localhost";
private static final int DEFAULT_PORT=8848;
//推出标识
private static final String QUIT="quit";
private static final int BUFFER_SIZE=1024;
private static final int THREADPOLL_SIZE = 8;
//异步通道组
private AsynchronousChannelGroup channelGroup;
//异步的ServerSocketChannel类比于ServerSocketChannel
private AsynchronousServerSocketChannel serverSocketChannel;
//编码格式
private Charset charset = Charset.forName("UTF-8");
//已经连接的客户端集合
private List connectedClients;
private int port;
public AIOServer(){
this(DEFAULT_PORT);
}
public AIOServer(int port){
this.port = port;
this.connectedClients = new ArrayList<>();
}
/**
* 申请退出
* @param msg
* @return
*/
private boolean readtToQuit(String msg){
return QUIT.equals(msg);
}
/**
* 关闭流
* @param closeable
*/
private void close(Closeable closeable){
if(closeable!=null){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 新增连接的客户端
* @param clientHandler
*/
private synchronized void addClient(ClientHandler clientHandler){
connectedClients.add(clientHandler);
System.out.println("已经连接服务器.");
}
private synchronized void removeClient(ClientHandler clientHandler){
connectedClients.remove(clientHandler);
System.out.println("已经断开连接.");
}
/**
* 解码接收的消息
* @param byteBuffer
* @return
*/
private String receive(ByteBuffer byteBuffer){
CharBuffer charBuffer = charset.decode(byteBuffer);
return String.valueOf(charBuffer);
}
/**
* 获取名称
* @param clinetChannel
* @return
*/
private String getClinetName(AsynchronousSocketChannel clinetChannel){
int clientPort = -1;
try {
InetSocketAddress inetSocketAddress = (InetSocketAddress) clinetChannel.getRemoteAddress();
port = inetSocketAddress.getPort();
} catch (IOException e) {
e.printStackTrace();
}
return "客户端["+port+"]";
}
/**
* 向其他客户端发送消息
* @param clientChannel
* @param msg
*/
private synchronized void dispatch(AsynchronousSocketChannel clientChannel,String msg){
for(ClientHandler clientHandler:connectedClients){
if(!clientHandler.clientChannel.equals(clientChannel)){
ByteBuffer byteBuffe =
charset.encode(getClinetName(clientHandler.clientChannel)+":"+msg);
clientHandler.clientChannel.write(byteBuffe,null,clientHandler);
}
}
}
/**
* 事件完成后的处理
*/
private class ClientHandler implements CompletionHandler {
private AsynchronousSocketChannel clientChannel;
public ClientHandler(AsynchronousSocketChannel channel){
this.clientChannel = channel;
}
@Override
public void completed(Integer result,Object attachment){
ByteBuffer buffer = (ByteBuffer) attachment;
if(buffer!=null){//发送了读事件
if(result<=0){
//客户通道发送了异常,从列表中移除
//从列表中删除该对象
removeClient(this);
}else {
buffer.flip();//转化为读操作
String msg = receive(buffer);//从buffer中读取消息
System.out.println(getClinetName(clientChannel)+":"+msg);
//转发消息
dispatch(clientChannel,msg);
buffer.clear();//情况缓冲区
//退出
if(readtToQuit(msg)){
removeClient(this);
}else {
clientChannel.read(buffer,buffer,this);
}
}
}
}
@Override
public void failed(Throwable exc,Object attachment){
System.out.println("读写失败:"+exc);
}
}
//服务端连接事件处理
private class AcceptHandler implements CompletionHandler
@Override
public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
if(serverSocketChannel.isOpen()){//判断连接是否打开
serverSocketChannel.accept(null,this);
}
if(clientChannel!=null&&clientChannel.isOpen()){
ClientHandler clientHandler = new ClientHandler(clientChannel);
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
//将客户添加到列表中
addClient(clientHandler);
clientChannel.read(byteBuffer,byteBuffer,clientHandler);
}
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("读写失败: "+exc);
}
}
private void start(){
//启动一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(THREADPOLL_SIZE);
try {
//将线程池加入异步通道,进行资源共享
channelGroup = AsynchronousChannelGroup.withThreadPool(executorService);
//异步通道打开
serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);
serverSocketChannel.bind(new InetSocketAddress(LOCALHSOT,port));
System.out.println("服务启动,监听端口: "+port);
while (true){
serverSocketChannel.accept(null,new AcceptHandler());
System.in.read();//阻塞调用,防止占用系统资源,一致调用accept函数
}
} catch (IOException e) {
e.printStackTrace();
}finally {
close(serverSocketChannel);
}
}
public static void main(String[] args) {
AIOServer server = new AIOServer(8848);
server.start();
}
}
客户端
public class AIOClient {
private static final String LOCALHSOT ="localhost";
private static final int DEFAUT_PORT=8848;
//退出标识
private static final String QUIT="quit";
private static final int BUFFER=1024;
private String hsot;
private int port;
private AsynchronousSocketChannel clientChannel;
private Charset charset = Charset.forName("UTF-8");
public AIOClient(){
this(LOCALHSOT,DEFAUT_PORT);
}
public AIOClient(String host,int port){
this.hsot = host;
this.port = port;
}
//关闭流并且释放与之关联的资源
private void close(Closeable closeable){
if(closeable!=null){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//判断客户端是否准备退出
public boolean redatQuit(String msg){
return QUIT.equals(msg);
}
private void start(){
//打开连接通道
try {
clientChannel = AsynchronousSocketChannel.open();
//获取一个连接
Future future = clientChannel.connect(new InetSocketAddress(hsot,port));
try {
future.get();//阻塞式调用
//启动一个新的线程,处理用户的输入
new Thread(new AioClientHandler(this)).start();
//申请缓存
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER);
while (true){
//启动异步读操作,从通道读取的数据放入换冲区
Future readResult = clientChannel.read(byteBuffer);
//阻塞拿结果
int result = readResult.get();
if(result<=0){
System.out.println("服务端通讯异常,断开连接");
close(clientChannel);
//非正常退出
System.exit(1);
}else {
byteBuffer.flip();//重置读取位置
//消息解码
String msg = String.valueOf(charset.decode(byteBuffer));
byteBuffer.clear();
System.out.println(msg);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void send(String msg){
if(msg.isEmpty()){
return;
}
//消息加密
ByteBuffer byteBuffer = charset.encode(msg);
//阻塞发送消息
Future writeResult = clientChannel.write(byteBuffer);
try {
//获取发送消息结果
writeResult.get();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("消息发送失败");
} catch (ExecutionException e) {
e.printStackTrace();
System.out.println("消息发送失败");
}
}
public static void main(String[] args) {
AIOClient aioClient = new AIOClient();
aioClient.start();
}
}