先谈谈我对同步 异步 阻塞 非阻塞的认识:
同步:Client端发送请求,等待结果返回
eg: 小明去买东西,去超市买回来,东西买没买到,立即知道结果
异步:Client端发送请求,不等待结果返回,(后续等Server端通知等)
eg: 小明打电话让超市送过来,不知道东西有没有买到,不管结果
阻塞:Client端发送请求结束后,线程会被挂起
eg: 小明打完电话后一直等东西送过来,不做其他事情/小明去超市买东西不做其他事情
非阻塞:Client端发送请求结束后,线程不会被挂起
eg: 小明打完电话后去上网了,等到东西送过来/小明去超市买东西,同时可以拿个快递
同步是个过程,阻塞是线程的一种状态
/分割线//
BIO 同步阻塞
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。 BIO一个连接是一个线程
客户端:
public class Client {
public Client() throws IOException {
init();
}
public void init() throws IOException {
try (Socket socket = new Socket(InetAddress.getLocalHost(), 9090);
PrintWriter printWriter = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF8"))) {
printWriter.println("this is a test message!!!");
}
}
public static void main(String[] args) throws IOException {
new Client();
}
}
服务端:
public class Server {
public Server() throws IOException {
init();
}
public void init() throws IOException {
try (ServerSocket serverSocket = new ServerSocket(9090, 100,
InetAddress.getLocalHost())) {
Socket socket = serverSocket.accept();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(
socket.getInputStream(), "UTF8"))) {
String respone = reader.readLine();
System.out.println("server receive:" + respone);
}
}
}
public static void main(String[] args) throws IOException {
new Server();
}
}
NIO 同步非阻塞
同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,NIO是一个请求一个线程。
客户端:
public class Client {
private final Integer PORT = 9090;
public Client() throws IOException, InterruptedException {
init();
}
public void init() throws IOException, InterruptedException {
try (Selector selector = Selector.open(); SocketChannel socketChannel = SocketChannel.open()) {
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), PORT));
socketChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
if (selector.select() > 0) {
Iterator<SelectionKey> set = selector.selectedKeys().iterator();
while (set.hasNext()) {
SelectionKey key = set.next();
set.remove();
SocketChannel ch = (SocketChannel) key.channel();
if (key.isConnectable()) {
ch.register(selector, SelectionKey.OP_READ |
SelectionKey.OP_WRITE, new Integer(1));
ch.finishConnect();
}
if (key.isReadable()) {
key.attach(new Integer(1));
ByteArrayOutputStream output = new ByteArrayOutputStream();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = ch.read(buffer)) != 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
output.write(bytes);
buffer.clear();
}
System.out.println("接收到服务端消息:" + new String(output.toByteArray()));
output.close();
}
if (key.isWritable()) {
key.attach(new Integer(1));
ch.write(ByteBuffer.wrap((("这是一条测试消息")).getBytes()));
TimeUnit.SECONDS.sleep(5);
}
}
}
}
}
}
public static void main(String[] args) throws IOException, InterruptedException {
new Client();
}
}
服务端:
public class Server {
public Server() throws IOException {
init();
}
public void init() throws IOException {
// 创建多路复用器
// 创建一个通道
try (Selector selector = Selector.open();
ServerSocketChannel socketChannel = ServerSocketChannel.open()) {
socketChannel.configureBlocking(false);
//绑定ip和端口
socketChannel.socket().bind(new InetSocketAddress(InetAddress.getLocalHost(), 9090), 1024);
//监听客户端连接请求
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
//阻塞,直到有请求
System.out.println("等待客户端链接...");
while (true) {
selector.select();
System.out.println("客户端链接进入...");
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
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);
//发送应答消息
doWrite(sc, "这是一条返回信息");
}
//链路已经关闭,释放资源
else if (readBytes < 0) {
key.cancel();
sc.close();
}
}
}
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().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);
}
public static void main(String[] args) throws IOException {
new Server();
}
AIO 异步非阻塞
在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。 AIO是一个有效请求一个线程 。
客户端:
服务端:
public class Client {
public Client() throws IOException, InterruptedException {
init();
}
public void init() throws IOException, InterruptedException {
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//连接服务
socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 9090),
null, new CompletionHandler<Void, Void>() {
final ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);
@Override
public void completed(Void result, Void attachment) {
//连接成功后, 异步调用OS向服务器写一条消息
try {
ByteBuffer buffer = Charset.forName("UTF-8").
newEncoder().encode(CharBuffer.wrap("this is test aio msg"));
socketChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
if (buffer.hasRemaining()) {
socketChannel.write(buffer, buffer, this);
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
} catch (CharacterCodingException e) {
e.printStackTrace();
}
readBuffer.clear();
//异步调用OS读取服务器发送的消息
socketChannel.read(readBuffer, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
try {
//异步读取完成后处理
if (result > 0) {
readBuffer.flip();
CharsetDecoder decoder = Charset.forName("utf-8").newDecoder();
CharBuffer charBuffer = decoder.decode(readBuffer);
String answer = charBuffer.toString();
System.out.println(Thread.currentThread().getName() + "---" + answer);
readBuffer.clear();
socketChannel.read(readBuffer, null, this);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 读取失败处理
* @param exc
* @param attachment
*/
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("client read failed: " + exc);
}
});
}
/**
* 连接失败处理
* @param exc
* @param attachment
*/
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("client connect to server failed: " + exc);
}
});
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
服务端:
public class Server {
public Server() throws IOException {
init();
}
public void init() throws IOException {
try (AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(InetAddress.getLocalHost(), 9090), 100)) {
serverChannel.accept(this, new AcceptHandler());
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* accept到一个请求时的回调
*/
private class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Server> {
@Override
public void completed(final AsynchronousSocketChannel client, Server attachment) {
try {
System.out.println("远程地址:" + client.getRemoteAddress());
//tcp各项参数
client.setOption(StandardSocketOptions.TCP_NODELAY, true);
client.setOption(StandardSocketOptions.SO_SNDBUF, 1024);
client.setOption(StandardSocketOptions.SO_RCVBUF, 1024);
if (client.isOpen()) {
System.out.println("client.isOpen:" + client.getRemoteAddress());
final ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.clear();
client.read(buffer, client, new ReadHandler(buffer));
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Server attachment) {
exc.printStackTrace();
}
}
/**
* Read到请求数据的回调
*/
private class ReadHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> {
private ByteBuffer buffer;
public ReadHandler(ByteBuffer buffer) {
this.buffer = buffer;
}
@Override
public void completed(Integer result, AsynchronousSocketChannel attachment) {
try {
if (result < 0) {
} else if (result == 0) {
System.out.println("空数据");
} else {
// 读取请求,处理客户端发送的数据
buffer.flip();
CharsetDecoder decoder = Charset.forName("utf-8").newDecoder();
CharBuffer charBuffer = decoder.decode(buffer);
System.out.println("获取的数据:" + charBuffer.toString());
//响应操作,服务器响应结果
buffer.clear();
String res = "我给你回了一条数据";
buffer = ByteBuffer.wrap(res.getBytes());
attachment.write(buffer, attachment, new WriteHandler(buffer));
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
exc.printStackTrace();
}
}
/**
* Write响应完请求的回调
*/
private class WriteHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> {
private ByteBuffer buffer;
public WriteHandler(ByteBuffer buffer) {
this.buffer = buffer;
}
@Override
public void completed(Integer result, AsynchronousSocketChannel attachment) {
buffer.clear();
}
@Override
public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
exc.printStackTrace();
}
}
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
代码github:https://github.com/krauser1991/socket