Java IO
Java有非常丰富的IO接口,从流类型可划分为字节流和字符流,从流操作的位置可划分为网络IO和本地IO。
-
本地IO
-
- 本地IO时,普通IO和BIO最为常用,BIO较普通IO有更丰富的流操作组件,而NIO、AIO对于本地IO来说反而会在效率上大打折扣
-
- 那么buffer有什么作用呢?它会在内存中开辟一块缓冲区,用于优化对流的操作,读流时:不间断地从设备读到缓冲区中,同时对缓冲区进行间断(每次1024字节)地读取操作,速度加快。写流时:先将流间断(每次1024字节)地写入到缓冲区中,再统一对设备写入,速度加快,mark/reset就是通过这种方式实现的
网络IO
-
- 网络IO时,普通IO效率最低,BIO效率较普通IO更高,但BIO每个连接都需要起一个监控线程,而NIO通过selector完美地解决了这个问题,AIO是并发量较大网络IO的解决方案,它的内部其实是通过线程池去并发处理各个请求。那么是不是意味着普通BIO和NIO中加入线程池就可以替代AIO呢?毋庸置疑不是的,BIO 和 NIO 的使用中加线程池不是什么好主意,因为有channel的限制,所以AIO这套框架并没有白做。
-
- IO:普通的IO,每次操作stream,都需要和磁盘交互,效率最低
-
- BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善
-
- NIO:一个请求一个线程,通过selector对多个channel监听,并统一响应和处理,selector轮询到连接有I/O请求时才启动一个线程进行处理
-
- AIO:一个请求一个线程,客户端的I/O请求都是由OS先完成(将IO直接读到缓冲区)了并返回结果给Channel,再通知服务器应用去启动线程进行处理,处理完成后再主动将结果推送给客户端,那么客户端需要设置接收信息的端口。没有selector的概念,并发情况下效率高于NIO
以下是几种IO的技术架构图
- BIO
- NIO
- AIO
接下来上代码
- 普通IO
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.PushbackInputStream;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Date;
import java.util.zip.ZipInputStream;
/**
* 字节IO
*/
public class IOByteStream {
public static void main(String[] args) {
File fin = new File("C:\\Users\\Administrator\\Desktop\\test\\J0.java");
File fout = new File("C:\\Users\\Administrator\\Desktop\\test\\J1.java");
level1(fin, fout);
level2(fin);
level4Object(fout);
}
public static void level1(File fin, File fout) {
try (
FileInputStream fis = new FileInputStream(fin);
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream(fout, false);
BufferedOutputStream bos = new BufferedOutputStream(fos);
) {
System.out.println("stream markSupported - " + fis.markSupported());
System.out.println("buffer markSupported - " + bis.markSupported());
//mark后读取超过readlimit字节数据,mark标记就会失效
bis.mark(40960);
byte[] b = new byte[1024];
int i = 0;
while ((i = bis.read(b)) > 0) {
bos.write(b, 0, i);
}
bos.flush();
bis.reset();
byte[] b2 = new byte[1024];
int i2 = 0;
while ((i2 = bis.read(b2)) > 0) {
bos.write(b2, 0, i2);
}
bos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void level2(File f) {
try (
FileInputStream fis = new FileInputStream(f);
BufferedInputStream bis = new BufferedInputStream(fis);
PushbackInputStream pbis = new PushbackInputStream(bis);
DataInputStream dis = new DataInputStream(pbis);
) {
int s1 = dis.read();System.out.println(s1);
pbis.unread(s1);
int s2 = dis.read();System.out.println(s2);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void level3Zip(File f) {
try (
FileInputStream fis = new FileInputStream(f);
BufferedInputStream bis = new BufferedInputStream(fis);
ZipInputStream zis = new ZipInputStream(bis);
PushbackInputStream pbis = new PushbackInputStream(zis);
DataInputStream dis = new DataInputStream(pbis);
) {
} catch (Exception e) {
e.printStackTrace();
}
}
public static void level4Object(File f) {
try (
FileOutputStream fos = new FileOutputStream(f, true);
BufferedOutputStream bos = new BufferedOutputStream(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);
) {
Object o = new Object();
oos.writeObject(o);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 字符IO
*/
class IOCharStream {
public static void main(String[] args) {
File f = new File("C:\\Users\\Administrator\\Desktop\\test\\J0.java");
File f2 = new File("C:\\Users\\Administrator\\Desktop\\test\\J1.java");
level2(f2);
level1(f);
}
public static void level1(File f) {
try (
FileInputStream fis = new FileInputStream(f);
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
BufferedReader br = new BufferedReader(isr);
) {
br.lines().forEach(line -> System.out.println(line));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void level2(File f) {
try (
FileOutputStream fos = new FileOutputStream(f, true);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(osw, true);
) {
pw.print("abcs");
pw.println("ab");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* NIO读写
*/
class NIOStream {
public static void main(String[] args) {
File fin = new File("C:\\Users\\Administrator\\Desktop\\test\\J0.java");
File fout = new File("C:\\Users\\Administrator\\Desktop\\test\\J1.java");
level1(fin, fout);
}
public static void level1(File fin, File fout) {
try (
RandomAccessFile fis = new RandomAccessFile(fin, "rw");
FileChannel channelIn = fis.getChannel();
RandomAccessFile fos = new RandomAccessFile(fout, "rw");
FileChannel channelOut = fos.getChannel();
) {
Charset charset = Charset.forName("UTF-8");//Java.nio.charset.Charset处理了字符转换问题。它通过构造CharsetEncoder和CharsetDecoder将字符序列转换成字节和逆转换。
CharsetDecoder decoder = charset.newDecoder();
MappedByteBuffer byteBufferIn = channelIn.map(FileChannel.MapMode.READ_WRITE, 0, channelIn.size());
channelOut.write(byteBufferIn);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 三种流之间的对比
*/
class CompareWithStream {
public static void main(String[] args) {
File fin = new File("C:\\Users\\Administrator\\Desktop\\test\\J0.java");
File fout = new File("C:\\Users\\Administrator\\Desktop\\test\\J1.java");
level1NoBuffer(fin, fout); // 普通IO 读写5000次 耗时12毫秒
level1buffer(fin, fout); // 缓冲IO 读写5000次 耗时13毫秒
level1NIO(fin, fout); // NIO 读写5000次 耗时70毫秒
}
public static void level1NoBuffer(File fin, File fout) {
try (
FileInputStream fis = new FileInputStream(fin);
FileOutputStream fos = new FileOutputStream(fout, false);
) {
byte[] b = new byte[1024];
long time3 = new Date().getTime();
for (int i = 0; i < 5000; i++) {
int c = 0;
while ((c = fis.read(b)) > 0) {
fos.write(b, 0, c);
}
fos.flush();
}
long time4 = new Date().getTime();
System.out.println("普通IO:" + (time4 - time3));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void level1buffer(File fin, File fout) {
try (
FileInputStream fis = new FileInputStream(fin);
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream(fout, false);
BufferedOutputStream bos = new BufferedOutputStream(fos);
) {
byte[] b = new byte[1024];
long time1 = new Date().getTime();
for (int i = 0; i < 5000; i++) {
int c = 0;
while ((c = bis.read(b)) > 0) {
bos.write(b, 0, c);
}
bos.flush();
}
long time2 = new Date().getTime();
System.out.println("缓冲IO:" + (time2 - time1));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void level1NIO(File fin, File fout) {
try (
RandomAccessFile fis = new RandomAccessFile(fin, "rw");
FileChannel channelIn = fis.getChannel();
RandomAccessFile fos = new RandomAccessFile(fout, "rw");
FileChannel channelOut = fos.getChannel();
) {
long time5 = new Date().getTime();
for (int i = 0; i < 5000; i++) {
MappedByteBuffer byteBufferIn = channelIn.map(FileChannel.MapMode.READ_WRITE, 0, channelIn.size());
channelOut.write(byteBufferIn);
}
long time6 = new Date().getTime();
System.out.println("磁盘映射IO:" + (time6 - time5));
} catch (Exception e) {
e.printStackTrace();
}
}
}
- BIO
服务端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端
*/
public final class ServerNormal {
//默认的端口号
private static int DEFAULT_PORT = 12345;
//单例的ServerSocket
private static ServerSocket server;
//根据传入参数设置监听端口,如果没有参数调用以下方法并使用默认值
public static void start() throws IOException{
//使用默认值
start(DEFAULT_PORT);
}
//这个方法不会被大量并发访问,不太需要考虑效率,直接进行方法同步就行了
public synchronized static void start(int port) throws IOException{
if(server != null) return;
try{
//通过构造函数创建ServerSocket
//如果端口合法且空闲,服务端就监听成功
server = new ServerSocket(port);
System.out.println("PID:"+ Thread.currentThread().getId()+ " 服务器已启动,端口号:" + port);
//通过无限循环监听客户端连接
//如果没有客户端接入,将阻塞在accept操作上。
while(true){
Socket socket = server.accept();
//当有新的客户端接入时,会执行下面的代码
//然后创建一个新的线程处理这条Socket链路
new ServerHandler(socket).run();
}
}finally{
//一些必要的清理工作
if(server != null){
System.out.println("服务器已关闭。");
server.close();
server = null;
}
}
}
public static void main(String[] args) {
try {
start();
System.out.println("I have been out.");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class ServerHandler implements Runnable{
private Socket socket;
public ServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try{
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
String expression;
String result;
while(true){
//通过BufferedReader读取一行
//如果已经读到输入流尾部,返回null,退出循环
//如果得到非空值,就尝试计算结果并返回
if((expression = in.readLine())==null) break;
System.out.println("ID:" + Thread.currentThread().getId()+" 服务器收到消息:" + expression);
try{
result = expression+" got it!";
}catch(Exception e){
result = "计算错误:" + e.getMessage();
}
out.println(result);
}
}catch(Exception e){
e.printStackTrace();
}finally{
//一些必要的清理工作
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if(out != null){
out.close();
out = null;
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}
}
}
客户端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 客户端
*/
public class Client {
//默认的端口号
private static int DEFAULT_SERVER_PORT = 12345;
private static String DEFAULT_SERVER_IP = "127.0.0.1";
public static void send(String expression){
send(DEFAULT_SERVER_PORT,expression);
}
public static void send(int port,String expression){
System.out.println("算术表达式为:" + expression);
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(DEFAULT_SERVER_IP,port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
out.println(expression);
System.out.println("___结果为:" + in.readLine());
out.println(expression+" again");
System.out.println("___结果为:" + in.readLine());
}
} catch (Exception e){
e.printStackTrace();
} finally{
//长连接:关闭输出流代表传输完毕,保留输入流代表不关闭当前socket,与服务端保持连接
if(out != null){
out.close();
out = null;
}
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
// if(socket != null){
// try {
// socket.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// socket = null;
// }
}
}
public static void main(String[] args) {
send("发送的值");
}
}
-
NIO
客户端
package com.td.io.nio;
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.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* 客户端
*/
public class Client {
private static String DEFAULT_HOST = "127.0.0.1";
private static int DEFAULT_PORT = 12345;
private static ClientHandle clientHandle;
public static void startByPort(String ip,int port){
start(DEFAULT_HOST,DEFAULT_PORT);
}
public static synchronized void start(String ip,int port){
if(clientHandle!=null)
clientHandle.stop();
clientHandle = new ClientHandle(ip,port);
new Thread(clientHandle,"Server").start();
}
//向服务器发送消息
public static boolean sendMsg(String msg) throws Exception{
if(msg.equals("q")) return false;
clientHandle.sendMsg(msg);
return true;
}
}
class ClientHandle implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean started;
public ClientHandle(String ip,int port) {
this.host = ip;
this.port = port;
try{
//创建选择器
selector = Selector.open();
//打开监听通道
socketChannel = SocketChannel.open();
//如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
socketChannel.configureBlocking(false);//开启非阻塞模式
started = true;
}catch(IOException e){
e.printStackTrace();
System.exit(1);
}
}
public void stop(){
started = false;
}
@Override
public void run() {
try{
doConnect();
}catch(IOException e){
e.printStackTrace();
System.exit(1);
}
//循环遍历selector
while(started){
try{
//无论是否有读写事件发生,selector每隔1s被唤醒一次
selector.select(1000);
//阻塞,只有当至少一个注册的事件发生的时候才会继续.
// selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key = null;
while(it.hasNext()){
key = it.next();
it.remove();
try{
handleInput(key);
}catch(Exception e){
if(key != null){
key.cancel();
if(key.channel() != null){
key.channel().close();
}
}
}
}
}catch(Exception e){
e.printStackTrace();
System.exit(1);
}
}
//selector关闭后会自动释放里面管理的资源
if(selector != null)
try{
selector.close();
}catch (Exception e) {
e.printStackTrace();
}
}
private void handleInput(SelectionKey key) throws IOException{
if(key.isValid()){
SocketChannel sc = (SocketChannel) key.channel();
if(key.isConnectable()){
if(sc.finishConnect());
else System.exit(1);
}
//读消息
if(key.isReadable()){
//创建ByteBuffer,并开辟一个1M的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取请求码流,返回读取到的字节数
int readBytes = sc.read(buffer);
//读取到字节,对字节进行编解码
if(readBytes>0){
//将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
buffer.flip();
//根据缓冲区可读字节数创建字节数组
byte[] bytes = new byte[buffer.remaining()];
//将缓冲区可读字节数组复制到新建的数组中
buffer.get(bytes);
String result = new String(bytes,"UTF-8");
System.out.println("客户端收到消息:" + result);
}
//没有读取到字节 忽略
// else if(readBytes==0);
//链路已经关闭,释放资源
else if(readBytes<0){
key.cancel();
sc.close();
}
}
}
}
//异步发送消息
private void doWrite(SocketChannel channel,String request) throws IOException{
//将消息编码为字节数组
byte[] bytes = request.getBytes();
//根据数组容量创建ByteBuffer
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
//将字节数组复制到缓冲区
writeBuffer.put(bytes);
//flip操作
writeBuffer.flip();
//发送缓冲区的字节数组
channel.write(writeBuffer);
//****此处不含处理“写半包”的代码
}
private void doConnect() throws IOException{
if(socketChannel.connect(new InetSocketAddress(host,port)));
else socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
public void sendMsg(String msg) throws Exception{
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel, msg);
}
}
服务端
package com.td.io.nio;
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.util.Iterator;
import java.util.Set;
/**
* 服务端
*/
public class Server {
private static int DEFAULT_PORT = 12345;
private static ServerHandle serverHandle;
public static void startByPort(int ... port){
start(port);
}
public static synchronized void start(int ... port){
if(serverHandle!=null)
serverHandle.stop();
serverHandle = new ServerHandle(port);
new Thread(serverHandle,"Server").start();
}
}
class ServerHandle implements Runnable{
private Selector selector;
private volatile boolean started;
/**
* 构造方法
* @param port 指定要监听的端口号
*/
public ServerHandle(int ...port) {
try{
//创建选择器
selector = Selector.open();
for (int i = 0; i < port.length; i++) {
ServerSocketChannel serverChannel = null;
//打开监听通道
serverChannel = ServerSocketChannel.open();
//如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
serverChannel.configureBlocking(false);//开启非阻塞模式
//绑定端口 backlog设为1024
serverChannel.socket().bind(new InetSocketAddress(port[i]),1024);
//监听客户端连接请求
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}
//标记服务器已开启
started = true;
System.out.println("服务器已启动,端口号:" + port);
}catch(IOException e){
e.printStackTrace();
System.exit(1);
}
}
public void stop(){
started = false;
}
@Override
public void run() {
//循环遍历selector
while(started){
try{
//无论是否有读写事件发生,selector每隔1s被唤醒一次
selector.select(1000);
//阻塞,只有当至少一个注册的事件发生的时候才会继续.
// selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key = null;
while(it.hasNext()){
key = it.next();
it.remove();
try{
handleInput(key);
}catch(Exception e){
if(key != null){
key.cancel();
if(key.channel() != null){
key.channel().close();
}
}
}
}
}catch(Throwable t){
t.printStackTrace();
}
}
//selector关闭后会自动释放里面管理的资源
if(selector != null)
try{
selector.close();
}catch (Exception e) {
e.printStackTrace();
}
}
private void handleInput(SelectionKey key) throws IOException{
if(key.isValid()){
//处理新接入的请求消息
if(key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//通过ServerSocketChannel的accept创建SocketChannel实例
//完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
SocketChannel sc = ssc.accept();
//设置为非阻塞的
sc.configureBlocking(false);
//注册为读
sc.register(selector, SelectionKey.OP_READ);
}
//读消息
if(key.isReadable()){
SocketChannel sc = (SocketChannel) key.channel();
//创建ByteBuffer,并开辟一个1M的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取请求码流,返回读取到的字节数
int readBytes = sc.read(buffer);
//读取到字节,对字节进行编解码
if(readBytes>0){
//将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
buffer.flip();
//根据缓冲区可读字节数创建字节数组
byte[] bytes = new byte[buffer.remaining()];
//将缓冲区可读字节数组复制到新建的数组中
buffer.get(bytes);
String expression = new String(bytes,"UTF-8");
System.out.println("服务器收到消息:" + expression);
//处理数据
String result = null;
try{
result = "result : "+ expression;
}catch(Exception e){
result = "计算错误:" + e.getMessage();
}
//发送应答消息
doWrite(sc,result);
}
//没有读取到字节 忽略
// else if(readBytes==0);
//链路已经关闭,释放资源
else if(readBytes<0){
key.cancel();
sc.close();
}
}
}
}
//异步发送应答消息
private void doWrite(SocketChannel channel,String response) throws IOException{
//将消息编码为字节数组
byte[] bytes = response.getBytes();
//根据数组容量创建ByteBuffer
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
//将字节数组复制到缓冲区
writeBuffer.put(bytes);
//flip操作
writeBuffer.flip();
//发送缓冲区的字节数组
channel.write(writeBuffer);
//****此处不含处理“写半包”的代码
}
}
启动器
package com.td.io.nio;
public class Sender {
public static void main(String[] args) throws Exception{
//运行服务器
Server.start(12345, 12346);
//避免客户端先于服务器启动前执行代码
Thread.sleep(100);
//运行客户端
Client.start("127.0.0.1", 12345);
Thread.sleep(100);
Client.sendMsg("value");
//运行客户端
Client.start("127.0.0.1", 12346);
Thread.sleep(100);
Client.sendMsg("value");
}
}
- AIO
客户端
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
public class Client {
private static String DEFAULT_HOST = "127.0.0.1";
private static int DEFAULT_PORT = 12345;
private static AsyncClientHandler clientHandle;
public static void start(){
start(DEFAULT_HOST,DEFAULT_PORT);
}
public static synchronized void start(String ip,int port){
if(clientHandle!=null)
return;
clientHandle = new AsyncClientHandler(ip,port);
new Thread(clientHandle,"Client").start();
}
//向服务器发送消息
public static boolean sendMsg(String msg) throws Exception{
if(msg.equals("q")) return false;
clientHandle.sendMsg(msg);
return true;
}
@SuppressWarnings("resource")
public static void main(String[] args) throws Exception{
Client.start();
System.out.println("请输入请求消息:");
Scanner scanner = new Scanner(System.in);
while(Client.sendMsg(scanner.nextLine()));
}
}
class AsyncClientHandler implements CompletionHandler<Void, AsyncClientHandler>, Runnable {
private AsynchronousSocketChannel clientChannel;
private String host;
private int port;
private CountDownLatch latch;
public AsyncClientHandler(String host, int port) {
this.host = host;
this.port = port;
try {
//创建异步的客户端通道
clientChannel = AsynchronousSocketChannel.open();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//创建CountDownLatch等待
latch = new CountDownLatch(1);
//发起异步连接操作,回调参数就是这个类本身,如果连接成功会回调completed方法
clientChannel.connect(new InetSocketAddress(host, port), this, this);
try {
latch.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//连接服务器成功
//意味着TCP三次握手完成
@Override
public void completed(Void result, AsyncClientHandler attachment) {
System.out.println("客户端成功连接到服务器...");
}
//连接服务器失败
@Override
public void failed(Throwable exc, AsyncClientHandler attachment) {
System.err.println("连接服务器失败...");
exc.printStackTrace();
try {
clientChannel.close();
latch.countDown();
} catch (IOException e) {
e.printStackTrace();
}
}
//向服务器发送消息
public void sendMsg(String msg){
byte[] req = msg.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
//异步写
clientChannel.write(writeBuffer, writeBuffer,new WriteHandler(clientChannel, latch));
}
}
class WriteHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel clientChannel;
private CountDownLatch latch;
public WriteHandler(AsynchronousSocketChannel clientChannel,CountDownLatch latch) {
this.clientChannel = clientChannel;
this.latch = latch;
}
@Override
public void completed(Integer result, ByteBuffer buffer) {
//完成全部数据的写入
if (buffer.hasRemaining()) {
clientChannel.write(buffer, buffer, this);
}
else {
//读取数据
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
clientChannel.read(readBuffer,readBuffer,new ReadHandler(clientChannel, latch));
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("数据发送失败...");
try {
clientChannel.close();
latch.countDown();
} catch (IOException e) {
}
}
}
class ReadHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel clientChannel;
private CountDownLatch latch;
public ReadHandler(AsynchronousSocketChannel clientChannel,CountDownLatch latch) {
this.clientChannel = clientChannel;
this.latch = latch;
}
@Override
public void completed(Integer result,ByteBuffer buffer) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body;
try {
body = new String(bytes,"UTF-8");
System.out.println("客户端收到结果:"+ body);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc,ByteBuffer attachment) {
System.err.println("数据读取失败...");
try {
clientChannel.close();
latch.countDown();
} catch (IOException e) {
}
}
}
服务端
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;
public class Server {
private static int DEFAULT_PORT = 12345;
private static AsyncServerHandler serverHandle;
public volatile static long clientCount = 0;
public static void start() {
start(DEFAULT_PORT);
}
public static synchronized void start(int port) {
if (serverHandle != null)
return;
serverHandle = new AsyncServerHandler(port);
new Thread(serverHandle, "Server").start();
}
public static void main(String[] args) {
Server.start();
}
}
class AsyncServerHandler implements Runnable {
public CountDownLatch latch;
public AsynchronousServerSocketChannel channel;
public AsyncServerHandler(int port) {
try {
//创建服务端通道
channel = AsynchronousServerSocketChannel.open();
//绑定端口
channel.bind(new InetSocketAddress(port));
System.out.println("服务器已启动,端口号:" + port);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//CountDownLatch初始化
//它的作用:在完成一组正在执行的操作之前,允许当前的现场一直阻塞
//此处,让现场在此阻塞,防止服务端执行完成后退出
//也可以使用while(true)+sleep
//生成环境就不需要担心这个问题,以为服务端是不会退出的
latch = new CountDownLatch(1);
//用于接收客户端的连接
channel.accept(this,new AcceptHandler());
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncServerHandler> {
@Override
public void completed(AsynchronousSocketChannel channel,AsyncServerHandler serverHandler) {
//继续接受其他客户端的请求
Server.clientCount++;
System.out.println("连接的客户端数:" + Server.clientCount);
serverHandler.channel.accept(serverHandler, this);
//创建新的Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//异步读 第三个参数为接收消息回调的业务Handler
channel.read(buffer, buffer, new ReadHandler(channel));
}
@Override
public void failed(Throwable exc, AsyncServerHandler serverHandler) {
exc.printStackTrace();
serverHandler.latch.countDown();
}
}
class ReadHandler implements CompletionHandler<Integer, ByteBuffer> {
//用于读取半包消息和发送应答
private AsynchronousSocketChannel channel;
public ReadHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
//读取到消息后的处理
@Override
public void completed(Integer result, ByteBuffer attachment) {
//flip操作
attachment.flip();
//根据
byte[] message = new byte[attachment.remaining()];
attachment.get(message);
try {
String expression = new String(message, "UTF-8");
System.out.println("服务器收到消息: " + expression);
String calrResult = null;
try{
calrResult = "result" + expression;
}catch(Exception e){
calrResult = "计算错误:" + e.getMessage();
}
//向客户端发送消息
doWrite(calrResult);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
//发送消息
private void doWrite(String result) {
byte[] bytes = result.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
//异步写数据 参数与前面的read一样
channel.write(writeBuffer, writeBuffer,new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
//如果没有发送完,就继续发送直到完成
if (buffer.hasRemaining())
channel.write(buffer, buffer, this);
else{
//创建新的Buffer
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//异步读 第三个参数为接收消息回调的业务Handler
channel.read(readBuffer, readBuffer, new ReadHandler(channel));
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
} catch (IOException e) {
}
}
});
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
启动器
import java.util.Scanner;
import com.td.io.aio.client.Client;
import com.td.io.aio.server.Server;
public class Sender {
@SuppressWarnings("resource")
public static void main(String[] args) throws Exception{
//运行服务器
Server.start();
//避免客户端先于服务器启动前执行代码
Thread.sleep(100);
//运行客户端
Client.start();
System.out.println("请输入请求消息:");
Scanner scanner = new Scanner(System.in);
while(Client.sendMsg(scanner.nextLine()));
}
}