@Yano
2017-08-19T09:52:55.000000Z
字数 3634
阅读 1794
Netty
系列文章:http://www.jianshu.com/p/594441fb9c9e
本文完全参考自《Netty权威指南(第2版)》,李林峰著。
基本的网络编程模型是Client/Server
,即两个进程间相互通信,其中服务端提供位置信息(IP地址和端口号),客户端通过连接向服务器监听的地址发起连接请求,通过三次握手建立连接,之后双方就可以通过网络套接字(socket)进行通信。
Java的传统同步阻塞模型中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接。之后双方通过输入和输出流进行同步阻塞式通信。
对于每个客户端,服务端都要新建一个线程。
当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数量呈1:1的关系,当线程数膨胀后,系统的性能将急剧下降。
服务端在接收到字符串QUERY TIME ORDER
后,返回当前日期给客户端。
public class TimeServer {
public static void main(String[] args) throws IOException {
int port = 8080;
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("The time server is start in port : " + port);
Socket socket = null;
while (true) {
socket = serverSocket.accept();
new Thread(new TimeServerHandler(socket)).start();
}
} finally {
if (serverSocket != null) {
System.out.println("The time server close");
serverSocket.close();
serverSocket = null;
}
}
}
}
public class TimeServerHandler implements Runnable {
private Socket socket;
public TimeServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String currentTime = null;
String body = null;
while (true) {
body = in.readLine();
if (body == null) {
break;
}
System.out.println("The time server receive order : " + body);
currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date().toString() : "BAD ORDER";
out.println(currentTime);
}
} catch (Exception e) {
if (in != null) {
try {
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (out != null) {
out.close();
out = null;
}
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
this.socket = null;
}
}
}
}
public class TimeClient {
public static void main(String[] args) throws Exception {
int port = 8080;
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket("127.0.0.1", port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println("QUERY TIME ORDER");
System.out.println("SEND TO SERVER SUCCESSED");
String resp = in.readLine();
System.out.println("Now is : " + resp);
} catch (Exception e) {
// TODO: handle exception
} finally {
if (out != null) {
out.close();
out = null;
}
if (in != null) {
in.close();
in = null;
}
if (socket != null) {
socket.close();
socket = null;
}
}
}
}
The time server is start in port : 8080
The time server receive order : QUERY TIME ORDER
The time server receive order : QUERY TIME ORDER
通过jstack
命令,查看当前堆栈信息:
"main" #1 prio=5 os_prio=31 tid=0x00007ffed1805000 nid=0x1c03 runnable [0x0000700007de7000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at bio.TimeServer.main(TimeServer.java:18)
我们可以看到,代码确实阻塞在accept
操作上。
同步阻塞I/O当海量并发接入时,会导致线程耗尽。可以对线程模型进行优化:后端通过一个线程池来处理多个客户端的请求接入。
但是伪异步I/O也存在很多问题,因为它的本质仍然是同步阻塞。这意味着当对方发送请求或者应答消息比较缓慢,或者网络传输较慢时,读取输入流一方的通信线程会被长时间阻塞;在此期间,其他接入消息只能在消息队列中排队。
首先问自己一个问题,什么才是NIO编程?对于NIO,官方的说法是:New I/O;但是更多的人喜欢称之为Non-block I/O(非阻塞I/O)。
BIO中的Socket类和ServerSocket类,对应于NIO中的SocketChannel和ServerSocketChannel两种不同的套接字通道实现,这两种套接字都支持阻塞和非阻塞两种模式。
在面向流的I/O中,可以直接读取或写入数据至Stream对象中;在NIO中,所有数据都是通过缓冲区处理的:读取数据时,直接读到缓冲区;写入数据时,写入到缓冲区。任何时候访问NIO中的数据,都是通过缓冲区进行的。
传统的流
只有一个方向(InputStream或者OutputStream),而通道可以用于读、写或二者同时进行。
提供选择已经就绪的任务的能力
。Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读写事件,这个Channel处于就绪状态,会被Selector轮询处理,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。
对于NIO的编程十分繁琐,就不作介绍,在接下来的文章中直接使用Netty开发。