从这篇文章开始,将一步步探索Netty框架,从入门到精通,再到实际应用,我会把学到的总结下来,并与大家分享,如果有理解错误的地方,还请诸位大神予以纠正,希望与大家相互学习,共同进步,,还是那句话--“千里之行,始于足下”。
参考文档:《Netty权威指南》http://download.csdn.net/detail/kavu1/9689220
一、BIO编程
1、首先了解传统的BIO编程,我们所学的JAVA网络编程的基本模型是client/server模型,也就是两个进程之间进行相互通信,具体步骤是服务器开启后提供绑定的IP地址与监听端口,客户端通过连接操作向服务器发送请求,通过TCP三次握手建立连接,连接建立成功后,双方就可以通过网络套接字(Socket)进行通信,主要代码如下:
客户机(Client)主要代码:
//获取服务器参数
String remoteName=txtRemoteName.getText(); //获取客户端的IP地址
int remotePort=Integer.parseInt(txtRemotePort.getText()); //获取客户端的端口号
//构建一个Socket格式的地址
SocketAddress remoteAddr=new InetSocketAddress(remoteName,remotePort);
//1.创建客户机Scocket
clientSocket=new Socket();
try {
//2.连接服务器
clientSocket.connect(remoteAddr);
//3.连接成功后,构建套接字的输入输出流,即网络通信流
out=new PrintWriter(clientSocket.getOutputStream(),true);
in=new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(), "连接错误", JOptionPane.ERROR_MESSAGE);
return;
}
服务器主要代码:
new Thread(new Runnable() {
@Override
public void run() {
//获取服务器工作参数
String hostName=txtHostName.getText();
int hostPort=Integer.parseInt(txtHostPort.getText());
//构建服务器的Socket格式地址
SocketAddress serverAddr=new InetSocketAddress(hostName,hostPort);
try {
//1.创建服务器侦听套接字
listenSocket=new ServerSocket();
//2.绑定服务器工作地址
listenSocket.bind(serverAddr);
//3.开始侦听...
//4.等待和接受客户机连接
txtArea.append("服务器开始等待客户机连接...\n");
toClientSocket=listenSocket.accept();//无连接到达则阻塞等待;有连接到达则创建与客户会话的新套接字toClientSocket向下执行
txtArea.append(toClientSocket.getInetAddress()+ "会话开始...\n");
//5.按照echo协议,现在开始收发来自客户机的消息
//5.1收发消息之前创建会话的网络输入和输出流
in=new BufferedReader(new InputStreamReader(toClientSocket.getInputStream()));
out=new PrintWriter(toClientSocket.getOutputStream(),true);
//5.2 按照echo协议接收消息
String recvStr;
while ((recvStr=in.readLine())!=null){
txtArea.append(toClientSocket.getInetAddress()+" : "+recvStr+"\n"); //收到的消息显示到文本框
//5.3 按照echo协议原封不动回送消息
out.println(recvStr);
}//end while
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(), "错误提示", JOptionPane.ERROR_MESSAGE);
}
txtArea.append(toClientSocket.getInetAddress()+ "会话结束...\n");
btnStart.setEnabled(true);//激活按钮
try { //关闭套接字和网络流
if (listenSocket!=null) listenSocket.close();
if (toClientSocket!=null) toClientSocket.close();
if (in!=null) in.close();
if (out!=null) out.close();
} catch (IOException ex) { }
}//end run()
}).start();
这种同步阻塞I/O模型的缺点是当客户端并行访问数量增加后服务器的线程个数和客户端访问数是1:1的关系,随着访问量增大,系统会发生线程堆栈溢出等问题,
为了改进这种一线程一客户模型,后来引进了一种通过线程池或者消息队列实现一个或多个线程处理多个客户端的模型,但是他底层的通信机制依然是同步阻塞I/O,所以被称为“伪异步I/O模型”,
二、伪异步I/O编程
伪异步I/O模型采用线程池或消息队列实现通信,原理是:当新的客户端接入时,将客户端的Socket封装并放到线程池中处理,线程池维护一个消息队列和N个活跃线程对消息队列中的任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,因此它占有的资源是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽,
下面是改进的服务器端(Server)的代码:
new Thread(new Runnable() {
public void run() {
try {
//获取启动参数
String hostName=txtHostName.getText();
int hostPort=Integer.parseInt(txtHostPort.getText());
//构建套接字格式的地址
SocketAddress serverAddr=new InetSocketAddress(InetAddress.getByName(hostName),hostPort);
listenSocket=new ServerSocket();//创建侦听套接字
listenSocket.bind(serverAddr); //绑定到工作地址
int processors=Runtime.getRuntime().availableProcessors();//cpu数量
fixedPool=Executors.newFixedThreadPool(4);//线程池的初始化,指定线程池的大小
long currentId=Thread.currentThread().getId();
txtArea.append("服务器cpu数:"+processors+" 当前主线程ID:"+currentId+" 正等待客户机连接...\n");
while (true) { //处理来自客户机的连接
toClientSocket=listenSocket.accept();//如果无连接,则阻塞,否则接受连接并创建新的会话套接字
clientCounts++; //客户机数加1
txtArea.append(toClientSocket.getRemoteSocketAddress()+" 客户机编号:"+clientCounts+" 连接到服务器,会话开始...\n");
//实现线程池调度
Thread worker;
// SwingWorker,String> worker;
worker=new ClientThread(toClientSocket,clientCounts);
fixedPool.execute(worker);//把线程放入线程池,调度线程运行
//worker.execute();//这是一客户一线程的做法,启动子线程
}//end while
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(), "错误提示", JOptionPane.ERROR_MESSAGE);
}
}//end run()
}).start();
初始化线程池大小为4,用while循环一直等待客户机的连接,当收到新的客户端连接的时候,客户机数量加一,创建套接字toClientSocket,将线程放到线程池,通过execute()方法调度每个线程运行,,,
伪异步I/O通信模型采用线程池技术,避免了为每个客户端的请求都创建线程,不会造成资源枯竭,但它的底层依然是同步阻塞I/O模型,还是有弊端的,
当某一方发送请求或者应答消息较慢、或者网络传输较慢,读取网络流的一方的进程将被长时间阻塞,在此期间,其他接入的消息只能在消息队列中排队,,,解决这个问题就要引入NIO(Non-block I/O)编程。。