1.新建NettyTest工程
工程结构如图所示:
2.各个类:
ClientPipelineFactory.java
public class ClientPipelineFactory implements ChannelPipelineFactory {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("encode", new StringEncoder());
pipeline.addLast("decode", new StringDecoder());
pipeline.addLast("handler", new ClientHandler());
return pipeline;
}
}
ClientThread.java(未用)
public class ClientThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
NettyClient n = new NettyClient();
n.run();
}
}
NettyClient.java
public class NettyClient {
final static Logger log = Logger.getLogger(NettyClient.class);
String host = "localhost";
int port = 8882;
// 实例化一个客户端Bootstrap实例,其中NioClientSocketChannelFactory实例由Netty提供
static ChannelFactory factory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
static ClientBootstrap bootstrap = new ClientBootstrap(factory);
private ChannelFuture future;
public ChannelFuture getFuture() {
return future;
}
public void setFuture(ChannelFuture future) {
this.future = future;
}
/**
*
* @author linfenliang
* @date 2012-7-2
* @version V1.0.0 void
*/
public void run() {
// TODO Auto-generated method stub
// 设置PipelineFactory,由客户端自己实现
bootstrap.setPipelineFactory(new ClientPipelineFactory());
bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("keepAlive", true);
bootstrap.setOption("remoteAddress", new InetSocketAddress(host, port));
// 向目标地址发起一个连接
future = bootstrap.connect();
// 等待链接成功,成功后发起的connected事件将会使handler开始发送信息并且等待messageRecive
future.awaitUninterruptibly();
log.info(future.isSuccess() ? "连接服务器成功!" : "连接服务器失败!");
}
/**
* 发送消息
*
* @author linfenliang
* @date 2012-7-3
* @version V1.0.0 void
*/
public void sendMessage(String value) {
if (future != null) {
Channel ch = future.getChannel();
if (value != null && value.length()!=0) {
ch.write(value);
} else {
ch.write("SHUTDOWN_CHANNELL/r/r");
}
} else {
log.info("未建立连接");
}
}
/**
* 客户端关闭
*
* @author linfenliang
* @date 2012-7-4
* @version V1.0.0
* @param future
* void
*/
public static void shutdown(ChannelFuture future) {
if (future.getChannel().isOpen())
future.getChannel().close().awaitUninterruptibly();
bootstrap.releaseExternalResources();
System.exit(0);
}
/**
* 多线程并发测试
*
* @author linfenliang
* @date 2012-7-4
* @version V1.0.0
* @param runnable
* @param daemon
* void
*/
public static void thread(Runnable runnable, boolean daemon) {
Thread brokerThread = new Thread(runnable);
brokerThread.setDaemon(daemon);
brokerThread.start();
}
}
ClientHandler.java
public class ClientHandler extends SimpleChannelHandler {
final static Logger log = Logger.getLogger(ClientHandler.class);
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {
// 异常处理
log.error(e.getCause());
e.getChannel().close();
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
// 客户端消息的接收
// ChannelBuffer buf = (ChannelBuffer) e.getMessage();
// byte[] b = new byte[200];
// int i=0;
// while(buf.readable()){
// b[i++]=buf.readByte();
// }
// byte[] dest = new byte[i];
// System.arraycopy(b, 0, dest,0, i);
// message = new Message(dest);
// System.out.println("获取服务器端下发的消息:"+message.getValue());
Object message = e.getMessage();
if (message instanceof String) {
log.info("获取服务器端下发的消息:" + message);
}
}
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
log.info("。。通道已连接。。");
}
}
Message.java(未用到)
public class Message implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String value;
private String clientName;
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public Message(byte[] valueBytes) {
try {
value = new String(valueBytes, "utf-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
NettyServer.java
public class NettyServer {
final static Logger log = Logger.getLogger(NettyServer.class);
static ChannelFactory CHANNEL_FACTORY = new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
static ServerBootstrap SERVER_BOOTSTRAP = new ServerBootstrap(CHANNEL_FACTORY);
/**
*
* @author linfenliang
* @date 2012-7-2
* @version V1.0.0
* @param args
* void
*/
public static void serverStartUp() {
// TODO Auto-generated method stub
SERVER_BOOTSTRAP.setPipelineFactory(new ServerPipelineFactory());
// 这个配置项仅适用于我们接收到的通道实例,而不是ServerSocketChannel实例
SERVER_BOOTSTRAP.setOption("child.tcpNoDelay", true);
SERVER_BOOTSTRAP.setOption("child.keepAlive", true);
SERVER_BOOTSTRAP.setOption("reuseAddress", true);
// SERVER_BOOTSTRAP.setOption("child.backlog", 1000);
// 绑定这个服务使用的端口
SERVER_BOOTSTRAP.bind(new InetSocketAddress(8882));
log.info("....服务器已启动....");
}
public static void serverShutDown(){
Tools.CHANNEL_GROUP.close();
Tools.CHANNEL_MAP.clear();
SERVER_BOOTSTRAP.releaseExternalResources();
System.exit(1);
}
}
ServerPipelineFactory.java
public class ServerPipelineFactory implements ChannelPipelineFactory {
@Override
public ChannelPipeline getPipeline() throws Exception {
// TODO Auto-generated method stub
ChannelPipeline pipeline = Channels.pipeline();
// LengthFieldBasedFrameDecoder 解决数据量过大分包传输时不同client之间数据混乱的问题
// pipeline.addLast("decode",new LengthFieldBasedFrameDecoder(Tools.MAXBYTELENGTH,0,4,0,4));
// pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192,
// Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder());
// pipeline.addLast("decode",new
// LengthFieldBasedFrameDecoder(Tools.MAXBYTELENGTH,0,4,0,4));
pipeline.addLast("encoder", new StringEncoder());
// 任何时候当服务器接收到一个新的连接,一个新的ChannelPipeline管道对象将被创建,
// 并且所有在这里添加的ChannelHandler对象将被添加至这个新的ChannelPipeline管道对象。
pipeline.addLast("handler", new ServerHandler());
return pipeline;
}
}
Server2ClientMessage.java
final static Logger log = Logger.getLogger(Server2ClientMessage.class);
public void sendMessage(String value, String clientName) {
Channel ch = null;
for (Entry<Integer, Object> entry : Tools.CHANNEL_MAP.entrySet()) {
if (entry.getValue() != null && entry.getValue().equals(clientName)) {
ch = Tools.CHANNEL_GROUP.find(entry.getKey());
}
}
// 消息流(数据下发)
if (ch != null && ch.isOpen()) {
value = "client ID:" + ch.getId() + ",data :" + value;
log.info("服务器向客户端下发的数据:" + value);
try {
ch.write(new String(value.getBytes(),"utf-8"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
log.error(e.getMessage());
}
} else {
log.error("消息下发失败,通道未连接或未注册");
}
}
}
ServerHandler.java
public class ServerHandler extends SimpleChannelHandler {
final static Logger log = Logger.getLogger(ServerHandler.class);
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
Channel ch = e.getChannel();
log.info("连接管道已建立,管道ID:" + ch.getId());
}
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
// TODO Auto-generated method stub
Tools.CHANNEL_MAP.remove(e.getChannel().getId());
Tools.CHANNEL_GROUP.remove(e.getChannel());
super.channelClosed(ctx, e);
}
@Override
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
// TODO Auto-generated method stub
log.info("管道已打开,管道记录到Map中");
Channel ch = e.getChannel();
Tools.CHANNEL_MAP.put(ch.getId(), null);
Tools.CHANNEL_GROUP.add(ch);
super.channelOpen(ctx, e);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {
// 异常捕获
e.getCause().printStackTrace();
e.getChannel().close();
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
Channel ch = e.getChannel();
// Tools.CHANNEL_GROUP.add(ch);
// 判断消息类型,解析 消息
Object msg = e.getMessage();
String clientName = null;
if (msg instanceof String) {
log.info("服务器端接收到客户端传来的String消息:"
+ new String(msg.toString().getBytes(),"utf-8"));
String[] str = new String(msg.toString().getBytes(),"utf-8").split("/r/r");
if(str[0].equals("SHUTDOWN_CHANNELL")){
log.info("MAP中原有管道:"+Tools.CHANNEL_MAP.entrySet()+"要关闭的客户端通道:"+Tools.CHANNEL_MAP.get(ch.getId()));
Tools.CHANNEL_GROUP.remove(ch);
Tools.CHANNEL_MAP.remove(ch.getId());
ch.close().awaitUninterruptibly();
log.info("MAP中执行remove后管道:"+Tools.CHANNEL_MAP.entrySet());
}else{
clientName = str[0];
}
// if(msg.toString().equals("SHUTDOWN_CHANNELL")){
// System.out.println("MAP中原有管道数量:"+Tools.CHANNEL_MAP.size());
// Tools.CHANNEL_GROUP.remove(ch);
// Tools.CHANNEL_MAP.remove(ch.getId());
// System.out.println("MAP中执行remove后管道数量:"+Tools.CHANNEL_MAP.size());
// }
// clientName = msg.toString().split("/r/r")[0];
} else if (msg instanceof ChannelBuffer) {
ChannelBuffer cb = (ChannelBuffer) e.getMessage();
Message m = (Message) Tools.bytes2Obj(cb.array());
// Message m = (Message) e.getMessage();
log.info("服务器端接收到客户端传来的Object消息:" + m.getValue()
+ e.getChannel().getId());
} else {
log.info("接收到的数据类型不匹配");
}
if (Tools.CHANNEL_MAP.containsKey(ch.getId())
&& Tools.CHANNEL_MAP.get(ch.getId()) == null) {
Tools.CHANNEL_MAP.put(ch.getId(), clientName);
Server2ClientMessage scm = new Server2ClientMessage();
scm.sendMessage("client "+clientName+" register success", clientName);
}
log.info("管道:"+Tools.CHANNEL_MAP.entrySet());
}
}
ClientTest.java
public class ClientTest {
final static Logger log = Logger.getLogger(ClientTest.class);
/**标记服务器端是否准备好发送信息*/
static boolean isReady = false;
/**
* 运行于命令行
*
* @author linfenliang
* @date 2012-7-4
* @version V1.0.0
* @param args
* void
* @throws UnsupportedEncodingException
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
NettyClient n = new NettyClient();
n.run();
boolean isSuccess = n.getFuture().isSuccess();
log.info(isSuccess ? "已成功连接到服务器..."
: "连接服务器失败,系统即将退出...");
if (!isSuccess) {
log.info("...系统退出...");
System.exit(0);
}
log.info(",请输入要发往服务器的信息");
log.info("信息和客户端名称以冒号分隔(仅第一次时需要),直接回车为关闭本通道,\";\"为退出):");
while (true) {
Scanner scan = new Scanner(System.in);
String value = scan.nextLine();
String[] ss = value.split(":");
isReady = ss.length>1 || isReady;
try {
if (value.equals(";")) {
n.sendMessage(null);
Thread.sleep(1000);
NettyClient.shutdown(n.getFuture());
System.exit(0);
} else if (isReady) {
String s = null;
if(ss.length>1){
s = new String((ss[1]
+ "/r/r" + ss[0]).getBytes(), "utf-8");
}else{
s = new String(("/r/r" + value).getBytes(), "utf-8");
}
n.sendMessage(s);
} else if (value == null || value.length() == 0) {
n.sendMessage(null);
}
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
log.error(e.getMessage());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
log.error(e.getMessage());
}
}
}
}
ServerTest.java
public class ServerTest {
final static Logger log = Logger.getLogger(ServerTest.class);
/**
*
* @author linfenliang
* @date 2012-7-4
* @version V1.0.0
* @param args
* void
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
NettyServer.serverStartUp();
Server2ClientMessage scm = new Server2ClientMessage();
log.info("请输入发往客户端的信息以及客户端名称,以冒号分隔");
log.info("退出系统输入 ; ");
while (true) {
// Scanner scan = new Scanner(System.in);
// System.out.println(scan.nextLine().toString());
String value = null;
try {
BufferedReader br = new BufferedReader(new InputStreamReader(
System.in, "GBK"));
value = br.readLine();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
log.error(e.getMessage());
} catch (IOException e) {
// TODO Auto-generated catch block
log.error(e.getMessage());
}
log.info(value);
String[] v = value.split(":");
if(value.equals(";")){
NettyServer.serverShutDown();
System.exit(0);
}
else if (v.length > 1) {
scm.sendMessage("hello,client:"+v[1]+",this is a message from server:" + v[0], v[1]);
} else {
log.error("数据格式错误:" + value);
}
}
}
}
FileOperate.java(未用到)
public class FileOperate {
public static Logger log = Logger.getLogger(FileOperate.class);
/**
*
* 概述:读取文件保存到byte数组中
*
* @Title: readFile
* @param file:源文件路径
* @return byte[]
* @author <a href=mailto:linfenliang@126.com>林芬亮</a>
*/
public static byte[] fileTobyte(String file) {
if (file == null || "".equals(file)) {
return null;
}
File f = new File(file);
if (!f.isFile()) {
return null;
}
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
FileInputStream in = new FileInputStream(file);
int c;
byte buffer[] = new byte[1024];
while ((c = in.read(buffer)) != -1) {
for (int i = 0; i < c; i++)
baos.write(buffer[i]);
}
in.close();
baos.close();
} catch (Exception e) {
log.error(e);
throw new RuntimeException();
}
byte[] buf = baos.toByteArray();
return buf;
}
}
Tools.java
public class Tools {
public static final int MAXBYTELENGTH = 999999999;
/** 服务器活动的管道集合 */
public static ChannelGroup CHANNEL_GROUP = null;
/** 服务器建立连接的管道数量 */
public static Map<Integer, Object> CHANNEL_MAP = null;
public static int INCREASE_NUM = 0;
static {
/*-----------数据初始化-------------*/
CHANNEL_GROUP = new DefaultChannelGroup();
CHANNEL_MAP = new HashMap<Integer, Object>();
}
}
log4j.properties
#配置日志的输出级别,此级别及以上的级别的日志可输出显示
log4j.rootLogger=INFO,info,f
#,drfa,DATABASE,MAIL,html,xml
#打印到控制台,便于项目开发及测试
log4j.appender.info = org.apache.log4j.ConsoleAppender
#日志编码方式
log4j.appender.info.Encoding=UTF-8
#是否立即输出(false时不输出)
log4j.appender.info.ImmediateFlush=true
#使用System.err输出
log4j.appender.info.Target=System.err
#布局方式为表达式布局
log4j.appender.info.layout=org.apache.log4j.PatternLayout
log4j.appender.info.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss.SSS} [%C]-[%p] %m%n
log4j.logger.com.opensymphony=INFO
log4j.logger.org.apache.struts2=DEBUG
#需要单独设置的包名(org.com.utils)及级别(WARN)
#log4j.category.org.com.utils=WARN
#打印到日志,一般项目正常工作的时候设置为此模式(按大小滚动文件)
log4j.appender.f=org.apache.log4j.RollingFileAppender
#日志名称及路径
log4j.appender.f.File=D://worklog.log
#${WORKDIR}/log/worklog.log
#${catalina.home}/webapps/MyBlog/WEB-INF/log/worklog.log
#追加文件内容(默认为true)
log4j.appender.f.Append=true
#文件达到10M自动改名
log4j.appender.f.MaxFileSize=10MB
#最多备份10个文件
log4j.appender.f.MaxBackupIndex=10
log4j.appender.f.Encoding=UTF-8
log4j.appender.f.layout=org.apache.log4j.PatternLayout
log4j.appender.f.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss.SSS} [%C]-[%p] %m%n
导出后的文件夹结构:
bat 命令
SERVER_RUN.bat:java -jar SERVER_RUN.jar
ClIENT_RUN.bat:java -jar CLIENT_RUN.jar