http://www.blogjava.net/hankchen/archive/2012/02/04/369378.html
Netty是一个高性能的NIO通信框架,提供异步的、事件驱动的网络编程模型。使用Netty可以方便用户开发各种常用协议的网络程序。例如:TCP、UDP、HTTP等等。
Netty的最新版本是3.2.7,官网地址是:http://www.jboss.org/netty
本文的主要目的是基于Netty实现一个通用二进制协议的高效数据传输。协议是通用的二进制协议,高效并且扩展性很好。
一个好的协议有两个标准:
(1)生成的传输数据要少,即数据压缩比要高。这样可以减少网络开销。
(2)传输数据和业务对象之间的转换速度要快。
(友情提示:本博文章欢迎转载,但请注明出处:hankchen,http://www.blogjava.net/hankchen)
一、协议的定义
无论是请求还是响应,报文都由一个通用报文头和实际数据组成。报文头在前,数据在后。
(1)报文头:由数据解析类型,数据解析方法,编码,扩展字节,包长度组成,共16个字节:
编码方式(1byte)、加密(1byte)、扩展1(1byte)、扩展2(1byte)、会话ID(4byte)、命令或者结果码(4byte)、数据包长(4byte)
(2)数据:由数据包长指定。请求或回复数据。类型对应为JAVA的Map<String,String>
数据格式定义:
字段1键名长度 字段1键名 字段1值长度 字段1值
字段2键名长度 字段2键名 字段2值长度 字段2值
字段3键名长度 字段3键名 字段3值长度 字段3值
… … … …
长度为整型,占4个字节
代码中用两个Vo对象来表示:XLRequest和XLResponse。
1
package
org.jboss.netty.example.xlsvr.vo;
2
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
3
import
java.util.HashMap;
4
import
java.util.Map;
5
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
6
/**
7
*@authorhankchen
10
* 2012-2-3 下午02:46:52
11
*/
12
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
13
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
14
/**
15
* 响应数据
16
*/
17
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
18
/**
19
* 通用协议介绍
20
*
21
* 通用报文格式:无论是请求还是响应,报文都由一个通用报文头和实际数据组成。报文头在前,数据在后
22
* (1)报文头:由数据解析类型,数据解析方法,编码,扩展字节,包长度组成,共16个字节:
23
* 编码方式(1byte)、加密(1byte)、扩展1(1byte)、扩展2(1byte)、会话ID(4byte)、命令或者结果码(4byte)、包长(4byte)
24
* (2)数据:由包长指定。请求或回复数据。类型对应为JAVA的Map<String,String>
25
* 数据格式定义:
26
* 字段1键名长度 字段1键名 字段1值长度 字段1值
27
* 字段2键名长度 字段2键名 字段2值长度 字段2值
28
* 字段3键名长度 字段3键名 字段3值长度 字段3值
29
* … … … …
30
* 长度为整型,占4个字节
31
*/
32
public
class
XLResponse
{
33
privatebyteencode;//数据编码格式。已定义:0:UTF-8,1:GBK,2:GB2312,3:ISO8859-1
34
privatebyteencrypt;//加密类型。0表示不加密
35
privatebyteextend1;//用于扩展协议。暂未定义任何值
36
privatebyteextend2;//用于扩展协议。暂未定义任何值
37
privateintsessionid;//会话ID
38
privateintresult;//结果码
39
privateintlength;//数据包长
40![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
41
privateMap<String,String>values=newHashMap<String, String>();
42![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
43
privateString ip;
44![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
45
publicvoidsetValue(String key,String value){
46
values.put(key, value);
47
}
48![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
49
publicString getValue(String key){
50
if(key==null){
51
returnnull;
52
}
53
returnvalues.get(key);
54
}
55![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
56
publicbytegetEncode(){
57
returnencode;
58
}
59![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
60
publicvoidsetEncode(byteencode){
61
this.encode=encode;
62
}
63![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
64
publicbytegetEncrypt(){
65
returnencrypt;
66
}
67![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
68
publicvoidsetEncrypt(byteencrypt){
69
this.encrypt=encrypt;
70
}
71![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
72
publicbytegetExtend1(){
73
returnextend1;
74
}
75![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
76
publicvoidsetExtend1(byteextend1){
77
this.extend1=extend1;
78
}
79![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
80
publicbytegetExtend2(){
81
returnextend2;
82
}
83![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
84
publicvoidsetExtend2(byteextend2){
85
this.extend2=extend2;
86
}
87![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
88
publicintgetSessionid(){
89
returnsessionid;
90
}
91![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
92
publicvoidsetSessionid(intsessionid){
93
this.sessionid=sessionid;
94
}
95![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
96
publicintgetResult(){
97
returnresult;
98
}
99![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
100
publicvoidsetResult(intresult){
101
this.result=result;
102
}
103![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
104
publicintgetLength(){
105
returnlength;
106
}
107![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
108
publicvoidsetLength(intlength){
109
this.length=length;
110
}
111![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
112
publicMap<String, String>getValues(){
113
returnvalues;
114
}
115![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
116
publicString getIp(){
117
returnip;
118
}
119![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
120
publicvoidsetIp(String ip){
121
this.ip=ip;
122
}
123![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
124
publicvoidsetValues(Map<String, String>values){
125
this.values=values;
126
}
127![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
128
@Override
129
publicString toString(){
130
return"XLResponse [encode="+encode+", encrypt="+encrypt+", extend1="+extend1+", extend2="+extend2
131
+", sessionid="+sessionid+", result="+result+", length="+length+", values="+values+", ip="+ip+"]";
132
}
133
}
1
package
org.jboss.netty.example.xlsvr.vo;
2
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
3
import
java.util.HashMap;
4
import
java.util.Map;
5
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
6
/**
7
*@authorhankchen
8
* 2012-2-3 下午02:46:41
9
*/
10
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
11
/**
12
* 请求数据
13
*/
14
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
15
/**
16
* 通用协议介绍
17
*
18
* 通用报文格式:无论是请求还是响应,报文都由一个通用报文头和实际数据组成。报文头在前,数据在后
19
* (1)报文头:由数据解析类型,数据解析方法,编码,扩展字节,包长度组成,共16个字节:
20
* 编码方式(1byte)、加密(1byte)、扩展1(1byte)、扩展2(1byte)、会话ID(4byte)、命令或者结果码(4byte)、包长(4byte)
21
* (2)数据:由包长指定。请求或回复数据。类型对应为JAVA的Map<String,String>
22
* 数据格式定义:
23
* 字段1键名长度 字段1键名 字段1值长度 字段1值
24
* 字段2键名长度 字段2键名 字段2值长度 字段2值
25
* 字段3键名长度 字段3键名 字段3值长度 字段3值
26
* … … … …
27
* 长度为整型,占4个字节
28
*/
29
public
class
XLRequest
{
30
privatebyteencode;//数据编码格式。已定义:0:UTF-8,1:GBK,2:GB2312,3:ISO8859-1
31
privatebyteencrypt;//加密类型。0表示不加密
32
privatebyteextend1;//用于扩展协议。暂未定义任何值
33
privatebyteextend2;//用于扩展协议。暂未定义任何值
34
privateintsessionid;//会话ID
35
privateintcommand;//命令
36
privateintlength;//数据包长
37![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
38
privateMap<String,String>params=newHashMap<String, String>();//参数
39![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
40
privateString ip;
41![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
42
publicbytegetEncode(){
43
returnencode;
44
}
45![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
46
publicvoidsetEncode(byteencode){
47
this.encode=encode;
48
}
49![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
50
publicbytegetEncrypt(){
51
returnencrypt;
52
}
53![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
54
publicvoidsetEncrypt(byteencrypt){
55
this.encrypt=encrypt;
56
}
57![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
58
publicbytegetExtend1(){
59
returnextend1;
60
}
61![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
62
publicvoidsetExtend1(byteextend1){
63
this.extend1=extend1;
64
}
65![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
66
publicbytegetExtend2(){
67
returnextend2;
68
}
69![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
70
publicvoidsetExtend2(byteextend2){
71
this.extend2=extend2;
72
}
73![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
74
publicintgetSessionid(){
75
returnsessionid;
76
}
77![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
78
publicvoidsetSessionid(intsessionid){
79
this.sessionid=sessionid;
80
}
81![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
82
publicintgetCommand(){
83
returncommand;
84
}
85![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
86
publicvoidsetCommand(intcommand){
87
this.command=command;
88
}
89![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
90
publicintgetLength(){
91
returnlength;
92
}
93![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
94
publicvoidsetLength(intlength){
95
this.length=length;
96
}
97![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
98
publicMap<String, String>getParams(){
99
returnparams;
100
}
101![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
102
publicvoidsetValue(String key,String value){
103
params.put(key, value);
104
}
105![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
106
publicString getValue(String key){
107
if(key==null){
108
returnnull;
109
}
110
returnparams.get(key);
111
}
112![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
113
publicString getIp(){
114
returnip;
115
}
116![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
117
publicvoidsetIp(String ip){
118
this.ip=ip;
119
}
120![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
121
publicvoidsetParams(Map<String, String>params){
122
this.params=params;
123
}
124![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
125
@Override
126
publicString toString(){
127
return"XLRequest [encode="+encode+", encrypt="+encrypt+", extend1="+extend1+", extend2="+extend2
128
+", sessionid="+sessionid+", command="+command+", length="+length+", params="+params+", ip="+ip+"]";
129
}
130
}
131
二、协议的编码和解码
对于自定义二进制协议,编码解码器往往是Netty开发的重点。这里直接给出相关类的代码。
1
package
org.jboss.netty.example.xlsvr.codec;
2
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
3
import
java.nio.ByteBuffer;
4
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
5
import
org.jboss.netty.buffer.ChannelBuffer;
6
import
org.jboss.netty.buffer.ChannelBuffers;
7
import
org.jboss.netty.channel.ChannelHandlerContext;
8
import
org.jboss.netty.channel.Channels;
9
import
org.jboss.netty.channel.MessageEvent;
10
import
org.jboss.netty.channel.SimpleChannelDownstreamHandler;
11
import
org.jboss.netty.example.xlsvr.util.ProtocolUtil;
12
import
org.jboss.netty.example.xlsvr.vo.XLResponse;
13
import
org.slf4j.Logger;
14
import
org.slf4j.LoggerFactory;
15
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
16
/**
17
*@authorhankchen
18
* 2012-2-3 上午10:48:15
19
*/
20
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
21
/**
22
* 服务器端编码器
23
*/
24
public
class
XLServerEncoder
extends
SimpleChannelDownstreamHandler
{
25
Logger logger=LoggerFactory.getLogger(XLServerEncoder.class);
26![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
27
@Override
28
publicvoidwriteRequested(ChannelHandlerContext ctx, MessageEvent e)throwsException{
29
XLResponse response=(XLResponse)e.getMessage();
30
ByteBuffer headBuffer=ByteBuffer.allocate(16);
31
/**
32
* 先组织报文头
33
*/
34
headBuffer.put(response.getEncode());
35
headBuffer.put(response.getEncrypt());
36
headBuffer.put(response.getExtend1());
37
headBuffer.put(response.getExtend2());
38
headBuffer.putInt(response.getSessionid());
39
headBuffer.putInt(response.getResult());
40![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
41
/**
42
* 组织报文的数据部分
43
*/
44
ChannelBuffer dataBuffer=ProtocolUtil.encode(response.getEncode(),response.getValues());
45
intlength=dataBuffer.readableBytes();
46
headBuffer.putInt(length);
47
/**
48
* 非常重要
49
* ByteBuffer需要手动flip(),ChannelBuffer不需要
50
*/
51
headBuffer.flip();
52
ChannelBuffer totalBuffer=ChannelBuffers.dynamicBuffer();
53
totalBuffer.writeBytes(headBuffer);
54
logger.info("totalBuffer size="+totalBuffer.readableBytes());
55
totalBuffer.writeBytes(dataBuffer);
56
logger.info("totalBuffer size="+totalBuffer.readableBytes());
57
Channels.write(ctx, e.getFuture(), totalBuffer);
58
}
59![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
60
}
61
1
package
org.jboss.netty.example.xlsvr.codec;
2
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
3
import
org.jboss.netty.buffer.ChannelBuffer;
4
import
org.jboss.netty.buffer.ChannelBuffers;
5
import
org.jboss.netty.channel.Channel;
6
import
org.jboss.netty.channel.ChannelHandlerContext;
7
import
org.jboss.netty.example.xlsvr.util.ProtocolUtil;
8
import
org.jboss.netty.example.xlsvr.vo.XLResponse;
9
import
org.jboss.netty.handler.codec.frame.FrameDecoder;
10
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
11
/**
12
*@authorhankchen
13
* 2012-2-3 上午10:47:54
14
*/
15
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
16
/**
17
* 客户端解码器
18
*/
19
public
class
XLClientDecoder
extends
FrameDecoder
{
20![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
21
@Override
22
protectedObject decode(ChannelHandlerContext context, Channel channel, ChannelBuffer buffer)throwsException{
23
if(buffer.readableBytes()<16){
24
returnnull;
25
}
26
buffer.markReaderIndex();
27
byteencode=buffer.readByte();
28
byteencrypt=buffer.readByte();
29
byteextend1=buffer.readByte();
30
byteextend2=buffer.readByte();
31
intsessionid=buffer.readInt();
32
intresult=buffer.readInt();
33
intlength=buffer.readInt();//数据包长
34
if(buffer.readableBytes()<length){
35
buffer.resetReaderIndex();
36
returnnull;
37
}
38
ChannelBuffer dataBuffer=ChannelBuffers.buffer(length);
39
buffer.readBytes(dataBuffer, length);
40![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
41
XLResponse response=newXLResponse();
42
response.setEncode(encode);
43
response.setEncrypt(encrypt);
44
response.setExtend1(extend1);
45
response.setExtend2(extend2);
46
response.setSessionid(sessionid);
47
response.setResult(result);
48
response.setLength(length);
49
response.setValues(ProtocolUtil.decode(encode, dataBuffer));
50
response.setIp(ProtocolUtil.getClientIp(channel));
51
returnresponse;
52
}
53![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
54
}
1
package
org.jboss.netty.example.xlsvr.util;
2
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
3
import
java.net.SocketAddress;
4
import
java.nio.charset.Charset;
5
import
java.util.HashMap;
6
import
java.util.Map;
7
import
java.util.Map.Entry;
8
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
9
import
org.jboss.netty.buffer.ChannelBuffer;
10
import
org.jboss.netty.buffer.ChannelBuffers;
11
import
org.jboss.netty.channel.Channel;
12
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
13
/**
14
*@authorhankchen
15
* 2012-2-4 下午01:57:33
16
*/
17
public
class
ProtocolUtil
{
18![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
19
/**
20
* 编码报文的数据部分
21
*@paramencode
22
*@paramvalues
23
*@return
24
*/
25
publicstaticChannelBuffer encode(intencode,Map<String,String>values){
26
ChannelBuffer totalBuffer=null;
27
if(values!=null&&values.size()>0){
28
totalBuffer=ChannelBuffers.dynamicBuffer();
29
intlength=0,index=0;
30
ChannelBuffer [] channelBuffers=newChannelBuffer[values.size()];
31
Charset charset=XLCharSetFactory.getCharset(encode);
32
for(Entry<String,String>entry:values.entrySet()){
33
String key=entry.getKey();
34
String value=entry.getValue();
35
ChannelBuffer buffer=ChannelBuffers.dynamicBuffer();
36
buffer.writeInt(key.length());
37
buffer.writeBytes(key.getBytes(charset));
38
buffer.writeInt(value.length());
39
buffer.writeBytes(value.getBytes(charset));
40
channelBuffers[index++]=buffer;
41
length+=buffer.readableBytes();
42
}
43![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
44
for(inti=0; i<channelBuffers.length; i++){
45
totalBuffer.writeBytes(channelBuffers[i]);
46
}
47
}
48
returntotalBuffer;
49
}
50![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
51
/**
52
* 解码报文的数据部分
53
*@paramencode
54
*@paramdataBuffer
55
*@return
56
*/
57
publicstaticMap<String,String>decode(intencode,ChannelBuffer dataBuffer){
58
Map<String,String>dataMap=newHashMap<String, String>();
59
if(dataBuffer!=null&&dataBuffer.readableBytes()>0){
60
intprocessIndex=0,length=dataBuffer.readableBytes();
61
Charset charset=XLCharSetFactory.getCharset(encode);
62
while(processIndex<length){
63
/**
64
* 获取Key
65
*/
66
intsize=dataBuffer.readInt();
67
byte[] contents=newbyte[size];
68
dataBuffer.readBytes(contents);
69
String key=newString(contents, charset);
70
processIndex=processIndex+size+4;
71
/**
72
* 获取Value
73
*/
74
size=dataBuffer.readInt();
75
contents=newbyte[size];
76
dataBuffer.readBytes(contents);
77
String value=newString(contents, charset);
78
dataMap.put(key, value);
79
processIndex=processIndex+size+4;
80
}
81
}
82
returndataMap;
83
}
84![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
85
/**
86
* 获取客户端IP
87
*@paramchannel
88
*@return
89
*/
90
publicstaticString getClientIp(Channel channel){
91
/**
92
* 获取客户端IP
93
*/
94
SocketAddress address=channel.getRemoteAddress();
95
String ip="";
96
if(address!=null){
97
ip=address.toString().trim();
98
intindex=ip.lastIndexOf(':');
99
if(index<1){
100
index=ip.length();
101
}
102
ip=ip.substring(1, index);
103
}
104
if(ip.length()>15){
105
ip=ip.substring(Math.max(ip.indexOf("/")+1, ip.length()-15));
106
}
107
returnip;
108
}
109
}
110
三、服务器端实现
服务器端提供的功能是:
1、接收客户端的请求(非关闭命令),返回XLResponse类型的数据。
2、如果客户端的请求是关闭命令:shutdown,则服务器端关闭自身进程。
为了展示多协议的运用,这里客户端的请求采用的是基于问本行(\n\r)的协议。
具体代码如下:
1
package
org.jboss.netty.example.xlsvr;
2
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
3
import
java.net.InetSocketAddress;
4
import
java.util.concurrent.Executors;
5
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
6
import
org.jboss.netty.bootstrap.ServerBootstrap;
7
import
org.jboss.netty.channel.Channel;
8
import
org.jboss.netty.channel.ChannelPipeline;
9
import
org.jboss.netty.channel.group.ChannelGroup;
10
import
org.jboss.netty.channel.group.ChannelGroupFuture;
11
import
org.jboss.netty.channel.group.DefaultChannelGroup;
12
import
org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
13
import
org.jboss.netty.example.xlsvr.codec.XLServerEncoder;
14
import
org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
15
import
org.jboss.netty.handler.codec.frame.Delimiters;
16
import
org.jboss.netty.handler.codec.string.StringDecoder;
17
import
org.jboss.netty.util.CharsetUtil;
18
import
org.slf4j.Logger;
19
import
org.slf4j.LoggerFactory;
20
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
21
/**
22
*@authorhankchen
23
* 2012-1-30 下午03:21:38
24
*/
25
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
26
public
class
XLServer
{
27
publicstaticfinalintport=8080;
28
publicstaticfinalLogger logger=LoggerFactory.getLogger(XLServer.class);
29
publicstaticfinalChannelGroup allChannels=newDefaultChannelGroup("XLServer");
30
privatestaticfinalServerBootstrap serverBootstrap=newServerBootstrap(newNioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
31![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
32
publicstaticvoidmain(String [] args){
33
try{
34
XLServer.startup();
35
}catch(Exception e){
36
e.printStackTrace();
37
}
38
}
39![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
40
publicstaticbooleanstartup()throwsException{
41
/**
42
* 采用默认ChannelPipeline管道
43
* 这意味着同一个XLServerHandler实例将被多个Channel通道共享
44
* 这种方式对于XLServerHandler中无有状态的成员变量是可以的,并且可以提高性能!
45
*/
46
ChannelPipeline pipeline=serverBootstrap.getPipeline();
47
/**
48
* 解码器是基于文本行的协议,\r\n或者\n\r
49
*/
50
pipeline.addLast("frameDecoder",newDelimiterBasedFrameDecoder(80, Delimiters.lineDelimiter()));
51
pipeline.addLast("stringDecoder",newStringDecoder(CharsetUtil.UTF_8));
52
pipeline.addLast("encoder",newXLServerEncoder());
53
pipeline.addLast("handler",newXLServerHandler());
54![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
55
serverBootstrap.setOption("child.tcpNoDelay",true);//注意child前缀
56
serverBootstrap.setOption("child.keepAlive",true);//注意child前缀
57![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
58
/**
59
* ServerBootstrap对象的bind方法返回了一个绑定了本地地址的服务端Channel通道对象
60
*/
61
Channel channel=serverBootstrap.bind(newInetSocketAddress(port));
62
allChannels.add(channel);
63
logger.info("server is started on port"+port);
64
returnfalse;
65
}
66![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
67
publicstaticvoidshutdown()throwsException{
68
try{
69
/**
70
* 主动关闭服务器
71
*/
72
ChannelGroupFuture future=allChannels.close();
73
future.awaitUninterruptibly();//阻塞,直到服务器关闭
74
//serverBootstrap.releaseExternalResources();
75
}catch(Exception e){
76
e.printStackTrace();
77
logger.error(e.getMessage(),e);
78
}
79
finally{
80
logger.info("server is shutdown on port"+port);
81
System.exit(1);
82
}
83
}
84
}
85
1
package
org.jboss.netty.example.xlsvr;
2
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
3
import
java.util.Random;
4
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
5
import
org.jboss.netty.channel.Channel;
6
import
org.jboss.netty.channel.ChannelFuture;
7
import
org.jboss.netty.channel.ChannelHandlerContext;
8
import
org.jboss.netty.channel.ChannelHandler.Sharable;
9
import
org.jboss.netty.channel.ChannelStateEvent;
10
import
org.jboss.netty.channel.ExceptionEvent;
11
import
org.jboss.netty.channel.MessageEvent;
12
import
org.jboss.netty.channel.SimpleChannelHandler;
13
import
org.jboss.netty.example.xlsvr.vo.XLResponse;
14
import
org.slf4j.Logger;
15
import
org.slf4j.LoggerFactory;
16
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
17
/**
18
*@authorhankchen
19
* 2012-1-30 下午03:22:24
20
*/
21
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
22
@Sharable
23
public
class
XLServerHandler
extends
SimpleChannelHandler
{
24
privatestaticfinalLogger logger=LoggerFactory.getLogger(XLServerHandler.class);
25![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
26
@Override
27
publicvoidmessageReceived(ChannelHandlerContext ctx, MessageEvent e)throwsException{
28
logger.info("messageReceived");
29
if(e.getMessage()instanceofString){
30
String content=(String)e.getMessage();
31
logger.info("content is"+content);
32
if("shutdown".equalsIgnoreCase(content)){
33
//e.getChannel().close();
34
XLServer.shutdown();
35
}else{
36
sendResponse(ctx);
37
}
38
}else{
39
logger.error("message is not a String.");
40
e.getChannel().close();
41
}
42
}
43![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
44
@Override
45
publicvoidexceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)throwsException{
46
logger.error(e.getCause().getMessage(),e.getCause());
47
e.getCause().printStackTrace();
48
e.getChannel().close();
49
}
50![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
51
@Override
52
publicvoidchannelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)throwsException{
53
logger.info("channelConnected");
54
sendResponse(ctx);
55
}
56![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
57
@Override
58
publicvoidchannelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)throwsException{
59
logger.info("channelClosed");
60
//删除通道
61
XLServer.allChannels.remove(e.getChannel());
62
}
63![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
64
@Override
65
publicvoidchannelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e)throwsException{
66
logger.info("channelDisconnected");
67
super.channelDisconnected(ctx, e);
68
}
69![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
70
@Override
71
publicvoidchannelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)throwsException{
72
logger.info("channelOpen");
73
//增加通道
74
XLServer.allChannels.add(e.getChannel());
75
}
76![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
77
/**
78
* 发送响应内容
79
*@paramctx
80
*@parame
81
*@return
82
*/
83
privateChannelFuture sendResponse(ChannelHandlerContext ctx){
84
Channel channel=ctx.getChannel();
85
Random random=newRandom();
86
XLResponse response=newXLResponse();
87
response.setEncode((byte)0);
88
response.setResult(1);
89
response.setValue("name","hankchen");
90
response.setValue("time", String.valueOf(System.currentTimeMillis()));
91
response.setValue("age",String.valueOf(random.nextInt()));
92
/**
93
* 发送接收信息的时间戳到客户端
94
* 注意:Netty中所有的IO操作都是异步的!
95
*/
96
ChannelFuture future=channel.write(response);//发送内容
97
returnfuture;
98
}
99
}
100
四、客户端实现
客户端的功能是连接服务器,发送10次请求,然后发送关闭服务器的命令,最后主动关闭客户端。
关键代码如下:
1
/**
2
* Copyright (C): 2012
3
*@authorhankchen
4
* 2012-1-30 下午03:21:26
5
*/
6
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
7
/**
8
* 服务器特征:
9
* 1、使用专用解码器解析服务器发过来的数据
10
* 2、客户端主动关闭连接
11
*/
12
public
class
XLClient
{
13
publicstaticfinalintport=XLServer.port;
14
publicstaticfinalString host="localhost";
15
privatestaticfinalLogger logger=LoggerFactory.getLogger(XLClient.class);
16
privatestaticfinalNioClientSocketChannelFactory clientSocketChannelFactory=newNioClientSocketChannelFactory(Executors.newCachedThreadPool(),Executors.newCachedThreadPool());
17
privatestaticfinalClientBootstrap clientBootstrap=newClientBootstrap(clientSocketChannelFactory);
18![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
19
/**
20
*@paramargs
21
*@throwsException
22
*/
23
publicstaticvoidmain(String[] args)throwsException{
24
ChannelFuture future=XLClient.startup();
25
logger.info("future state is"+future.isSuccess());
26
}
27![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
28
/**
29
* 启动客户端
30
*@return
31
*@throwsException
32
*/
33
publicstaticChannelFuture startup()throwsException{
34
/**
35
* 注意:由于XLClientHandler中有状态的成员变量,因此不能采用默认共享ChannelPipeline的方式
36
* 例如,下面的代码形式是错误的:
37
* ChannelPipeline pipeline=clientBootstrap.getPipeline();
38
* pipeline.addLast("handler", new XLClientHandler());
39
*/
40
clientBootstrap.setPipelineFactory(newXLClientPipelineFactory());//只能这样设置
41
/**
42
* 请注意,这里不存在使用“child.”前缀的配置项,客户端的SocketChannel实例不存在父级Channel对象
43
*/
44
clientBootstrap.setOption("tcpNoDelay",true);
45
clientBootstrap.setOption("keepAlive",true);
46![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
47
ChannelFuture future=clientBootstrap.connect(newInetSocketAddress(host, port));
48
/**
49
* 阻塞式的等待,直到ChannelFuture对象返回这个连接操作的成功或失败状态
50
*/
51
future.awaitUninterruptibly();
52
/**
53
* 如果连接失败,我们将打印连接失败的原因。
54
* 如果连接操作没有成功或者被取消,ChannelFuture对象的getCause()方法将返回连接失败的原因。
55
*/
56
if(!future.isSuccess()){
57
future.getCause().printStackTrace();
58
}else{
59
logger.info("client is connected to server"+host+":"+port);
60
}
61
returnfuture;
62
}
63![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
64
/**
65
* 关闭客户端
66
*@paramfuture
67
*@throwsException
68
*/
69
publicstaticvoidshutdown(ChannelFuture future)throwsException{
70
try{
71
/**
72
* 主动关闭客户端连接,会阻塞等待直到通道关闭
73
*/
74
future.getChannel().close().awaitUninterruptibly();
75
//future.getChannel().getCloseFuture().awaitUninterruptibly();
76
/**
77
* 释放ChannelFactory通道工厂使用的资源。
78
* 这一步仅需要调用 releaseExternalResources()方法即可。
79
* 包括NIO Secector和线程池在内的所有资源将被自动的关闭和终止。
80
*/
81
clientBootstrap.releaseExternalResources();
82
}catch(Exception e){
83
e.printStackTrace();
84
logger.error(e.getMessage(),e);
85
}
86
finally{
87
System.exit(1);
88
logger.info("client is shutdown to server"+host+":"+port);
89
}
90
}
91
}
1
public
class
XLClientPipelineFactory
implements
ChannelPipelineFactory
{
2![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
3
@Override
4
publicChannelPipeline getPipeline()throwsException{
5
ChannelPipeline pipeline=Channels.pipeline();
6
/**
7
* 使用专用的解码器,解决数据分段的问题
8
* 从业务逻辑代码中分离协议处理部分总是一个很不错的想法。
9
*/
10
pipeline.addLast("decoder",newXLClientDecoder());
11
/**
12
* 有专门的编码解码器,这时处理器就不需要管数据分段和数据格式问题,只需要关注业务逻辑了!
13
*/
14
pipeline.addLast("handler",newXLClientHandler());
15
returnpipeline;
16
}
17![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
18
}
1
/**
2
* Copyright (C): 2012
3
*@authorhankchen
4
* 2012-1-30 下午03:21:52
5
*/
6
![](http://www.blogjava.net/Images/OutliningIndicators/None.gif)
7
/**
8
* 服务器特征:
9
* 1、使用专用的编码解码器,解决数据分段的问题
10
* 2、使用POJO替代ChannelBuffer传输
11
*/
12
public
class
XLClientHandler
extends
SimpleChannelHandler
{
13
privatestaticfinalLogger logger=LoggerFactory.getLogger(XLClientHandler.class);
14
privatefinalAtomicInteger count=newAtomicInteger(0);//计数器
15![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
16
@Override
17
publicvoidmessageReceived(ChannelHandlerContext ctx, MessageEvent e)throwsException{
18
processMethod1(ctx, e);//处理方式一
19
}
20![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
21
/**
22
*@paramctx
23
*@parame
24
*@throwsException
25
*/
26
publicvoidprocessMethod1(ChannelHandlerContext ctx, MessageEvent e)throwsException{
27
logger.info("processMethod1……,count="+count.addAndGet(1));
28
XLResponse serverTime=(XLResponse)e.getMessage();
29
logger.info("messageReceived,content:"+serverTime.toString());
30
Thread.sleep(1000);
31![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
32
if(count.get()<10){
33
//从新发送请求获取最新的服务器时间
34
ctx.getChannel().write(ChannelBuffers.wrappedBuffer("again\r\n".getBytes()));
35
}else{
36
//从新发送请求关闭服务器
37
ctx.getChannel().write(ChannelBuffers.wrappedBuffer("shutdown\r\n".getBytes()));
38
}
39
}
40![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
41
@Override
42
publicvoidexceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)throwsException{
43
logger.info("exceptionCaught");
44
e.getCause().printStackTrace();
45
ctx.getChannel().close();
46
}
47![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
48
@Override
49
publicvoidchannelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)throwsException{
50
logger.info("channelClosed");
51
super.channelClosed(ctx, e);
52
}
53![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
54![](http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif)
55
}
全文代码较多,写了很多注释,希望对读者有用,谢谢!
(友情提示:本博文章欢迎转载,但请注明出处:hankchen,http://www.blogjava.net/hankchen)