├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs ├── org.eclipse.m2e.core.prefs └── org.maven.ide.eclipse.prefs ├── README.md ├── conf ├── spring-client.xml └── spring-server.xml ├── pom.xml └── src └── main └── java └── com └── netboy └── netty ├── client ├── ClientThread.java ├── NettyClient.java └── RunClient.java ├── handler ├── ClientHandler.java └── ServerHandler.java └── server ├── NettyServer.java └── RunServer.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ChatNetty 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.maven.ide.eclipse.maven2Builder 15 | 16 | 17 | 18 | 19 | org.eclipse.m2e.core.maven2Builder 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.m2e.core.maven2Nature 26 | org.eclipse.jdt.core.javanature 27 | org.maven.ide.eclipse.maven2Nature 28 | 29 | 30 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding/=UTF-8 4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | #Sat Mar 23 19:24:27 CST 2013 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 4 | org.eclipse.jdt.core.compiler.compliance=1.5 5 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 6 | org.eclipse.jdt.core.compiler.source=1.5 7 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.maven.ide.eclipse.prefs: -------------------------------------------------------------------------------- 1 | #Sat Mar 23 19:24:27 CST 2013 2 | activeProfiles= 3 | eclipse.preferences.version=1 4 | fullBuildGoals=process-test-resources 5 | resolveWorkspaceProjects=true 6 | resourceFilterGoals=process-resources resources\:testResources 7 | skipCompilerPlugin=true 8 | version=1 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ChatNetty 2 | ========= 3 | 4 | 用Netty实现NIO Socket聊天系统示例 5 | -------------------------------------------------------------------------------- /conf/spring-client.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /conf/spring-server.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.netboy.netty 6 | ChatNetty 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | ChatNetty 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | junit 20 | junit 21 | 3.8.1 22 | test 23 | 24 | 25 | 26 | 27 | io.netty 28 | netty 29 | 3.5.6.Final 30 | 31 | 32 | 33 | 34 | org.springframework 35 | spring 36 | 2.5.6 37 | 38 | 39 | 40 | log4j 41 | log4j 42 | 1.2.16 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/com/netboy/netty/client/ClientThread.java: -------------------------------------------------------------------------------- 1 | package com.netboy.netty.client; 2 | 3 | import java.util.Scanner; 4 | 5 | import org.jboss.netty.channel.Channel; 6 | 7 | public class ClientThread extends Thread { 8 | private NettyClient nettyClient; 9 | 10 | private Scanner scanner = new Scanner(System.in); 11 | 12 | public void init() { 13 | nettyClient.init(); 14 | nettyClient.start(); 15 | } 16 | 17 | public void run() { 18 | while(true) { 19 | 20 | Channel channel = nettyClient.getChannelFuture().getChannel(); 21 | System.out.println("发送消息(Enter发送):"); 22 | Object msg = scanner.next(); 23 | if(msg.toString().equals("quit")) { 24 | System.out.println("wait, you will quit.."); 25 | nettyClient.stop(); 26 | 27 | } 28 | channel.write(msg); 29 | } 30 | } 31 | 32 | public void setNettyClient(NettyClient nettyClient) { 33 | this.nettyClient = nettyClient; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/netboy/netty/client/NettyClient.java: -------------------------------------------------------------------------------- 1 | package com.netboy.netty.client; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.util.concurrent.Executors; 5 | 6 | import org.jboss.netty.bootstrap.ClientBootstrap; 7 | import org.jboss.netty.channel.ChannelFuture; 8 | import org.jboss.netty.channel.ChannelPipeline; 9 | import org.jboss.netty.channel.ChannelPipelineFactory; 10 | import org.jboss.netty.channel.Channels; 11 | import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; 12 | import org.jboss.netty.handler.codec.string.StringDecoder; 13 | import org.jboss.netty.handler.codec.string.StringEncoder; 14 | 15 | import com.netboy.netty.handler.ClientHandler; 16 | 17 | public class NettyClient { 18 | private int port=9090; 19 | private String host="127.0.0.1"; 20 | private ClientBootstrap bootstrap; 21 | private ClientHandler handler; 22 | private ChannelFuture channelFuture; 23 | /** 24 | * 初始化客户端 25 | */ 26 | public void init() { 27 | bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory( 28 | Executors.newCachedThreadPool(), 29 | Executors.newCachedThreadPool())); 30 | 31 | bootstrap.setPipelineFactory(new ChannelPipelineFactory() { 32 | public ChannelPipeline getPipeline() throws Exception { 33 | ChannelPipeline channelPipeline = Channels.pipeline(); 34 | channelPipeline.addLast("encode", new StringEncoder()); 35 | channelPipeline.addLast("decode", new StringDecoder()); 36 | channelPipeline.addLast("handler", handler); 37 | return channelPipeline; 38 | } 39 | }); 40 | bootstrap.setOption("tcpNoDelay", true); 41 | bootstrap.setOption("keepAlive", true); 42 | bootstrap.setOption("reuseAddress", true); 43 | } 44 | 45 | public void start() { 46 | channelFuture = bootstrap.connect(new InetSocketAddress(host,port)); 47 | System.out.println("连接远程服务器"+host+":"+port+"端口成功,你现在可以开始发消息了。"); 48 | } 49 | 50 | 51 | public void stop() { 52 | channelFuture.awaitUninterruptibly(); 53 | if (!channelFuture.isSuccess()) { 54 | channelFuture.getCause().printStackTrace(); 55 | } 56 | //等待或者监听数据全部完成 57 | channelFuture.getChannel().getCloseFuture().awaitUninterruptibly(); 58 | //释放连接资源 59 | bootstrap.releaseExternalResources(); 60 | 61 | } 62 | 63 | 64 | public void setBootstrap(ClientBootstrap bootstrap) { 65 | this.bootstrap = bootstrap; 66 | } 67 | 68 | public void setHandler(ClientHandler handler) { 69 | this.handler = handler; 70 | } 71 | 72 | 73 | public void setPort(int port) { 74 | this.port = port; 75 | } 76 | 77 | public ClientHandler getHandler() { 78 | return handler; 79 | } 80 | 81 | public void setHost(String host) { 82 | this.host = host; 83 | } 84 | 85 | public ChannelFuture getChannelFuture() { 86 | return channelFuture; 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /src/main/java/com/netboy/netty/client/RunClient.java: -------------------------------------------------------------------------------- 1 | package com.netboy.netty.client; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.support.FileSystemXmlApplicationContext; 5 | 6 | /** 7 | * TODO 8 | * Administrator 2013-3-23下午07:50:49 9 | */ 10 | public class RunClient { 11 | 12 | public static void main(String[] args) { 13 | 14 | String contextFile = "./conf/spring-client.xml"; 15 | 16 | ApplicationContext context = null; 17 | try { 18 | context = new FileSystemXmlApplicationContext(contextFile); 19 | } catch (Exception e) { 20 | System.out.println("RunMain has some exception"); 21 | e.printStackTrace(); 22 | } 23 | ClientThread client = (ClientThread)context.getBean("clientThread"); 24 | 25 | client.init(); 26 | client.start(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/netboy/netty/handler/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.netboy.netty.handler; 2 | 3 | import java.util.Date; 4 | 5 | import org.jboss.netty.channel.ChannelHandlerContext; 6 | import org.jboss.netty.channel.MessageEvent; 7 | import org.jboss.netty.channel.SimpleChannelHandler; 8 | 9 | public class ClientHandler extends SimpleChannelHandler { 10 | 11 | public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) 12 | throws Exception { 13 | String content = (String) e.getMessage(); 14 | System.out.println(""+ new Date().toString() + "\n" + content); 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/com/netboy/netty/handler/ServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.netboy.netty.handler; 2 | 3 | import java.util.Date; 4 | 5 | import org.jboss.netty.channel.Channel; 6 | import org.jboss.netty.channel.ChannelHandlerContext; 7 | import org.jboss.netty.channel.ChannelStateEvent; 8 | import org.jboss.netty.channel.ExceptionEvent; 9 | import org.jboss.netty.channel.MessageEvent; 10 | import org.jboss.netty.channel.SimpleChannelHandler; 11 | import org.jboss.netty.channel.group.ChannelGroup; 12 | import org.jboss.netty.channel.group.DefaultChannelGroup; 13 | 14 | /** 15 | * 服务器端handler 16 | * TODO 17 | * Administrator 2013-3-24下午03:34:28 18 | */ 19 | public class ServerHandler extends SimpleChannelHandler { 20 | private static ChannelGroup channelGroup; 21 | 22 | /** 23 | * 构造函数,在spring加载的时候初始化一次。 24 | */ 25 | public ServerHandler() { 26 | super(); 27 | /*获得客户端在服务器端注册的所有信息,用于向所有客户端分发消息*/ 28 | channelGroup = new DefaultChannelGroup("client-channel-group"); 29 | } 30 | /** 31 | * 用于扑捉客户端退出的消息。 32 | * 并将其从服务器端的注册表中删掉。 33 | */ 34 | @Override 35 | public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) 36 | throws Exception { 37 | e.getCause().printStackTrace(); 38 | Channel channel = e.getChannel(); 39 | channel.close(); 40 | if (channelGroup.contains(channel)) { 41 | System.out.println("一个客户端退出:"+channel.getId()); 42 | channelGroup.remove(channel); 43 | } 44 | } 45 | /** 46 | * 关键方法 47 | * 用于接收从客户端发来的消息,进行相应的逻辑处理 48 | * 这里,我们将任何一个客户端发来的消息,都将其转发给所有的客户端。 49 | */ 50 | @Override 51 | public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) 52 | throws Exception { 53 | String content = (String) e.getMessage(); 54 | System.out.println("服务器收到" + e.getChannel().getId() 55 | + " 的消息 时间:" + new Date().toString() + " 消息内容:\n"+ content); 56 | content=e.getChannel().getId()+":"+content; 57 | if (content.equalsIgnoreCase("quit")) { 58 | e.getChannel().close(); 59 | channelGroup.remove(e.getChannel()); 60 | return; 61 | } else { 62 | System.out.println("开始转发到其他客户端!:size="+channelGroup.size()); 63 | for(Channel ch:channelGroup){ 64 | System.out.println("开始转发到其他客户端!:"+ch.getId()); 65 | ch.write(content); 66 | } 67 | } 68 | } 69 | /** 70 | * 对新连接的用户进行注册 71 | */ 72 | @Override 73 | public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)throws Exception { 74 | System.out.println("新的客户端连入:"+e.getChannel().getId()); 75 | channelGroup.add(e.getChannel()); 76 | } 77 | 78 | public ChannelGroup getChannelGroup() { 79 | return channelGroup; 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/java/com/netboy/netty/server/NettyServer.java: -------------------------------------------------------------------------------- 1 | package com.netboy.netty.server; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.util.concurrent.Executors; 5 | 6 | import org.jboss.netty.bootstrap.ServerBootstrap; 7 | import org.jboss.netty.channel.ChannelPipeline; 8 | import org.jboss.netty.channel.ChannelPipelineFactory; 9 | import org.jboss.netty.channel.Channels; 10 | import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; 11 | import org.jboss.netty.handler.codec.string.StringDecoder; 12 | import org.jboss.netty.handler.codec.string.StringEncoder; 13 | 14 | import com.netboy.netty.handler.ServerHandler; 15 | 16 | public class NettyServer { 17 | private int port = 9090; 18 | private ServerBootstrap bootstrap; 19 | private ServerHandler handler; 20 | /** 21 | * 初始化服务器端 22 | */ 23 | public void init() { 24 | bootstrap = new ServerBootstrap( 25 | new NioServerSocketChannelFactory( 26 | Executors.newCachedThreadPool(), //boss 监听请求,并分派给slave进行处理 27 | Executors.newCachedThreadPool()//slave 处理请求,将其丢到线程池中处理 28 | ) 29 | ); 30 | 31 | bootstrap.setPipelineFactory(new ChannelPipelineFactory() { 32 | public ChannelPipeline getPipeline() throws Exception { 33 | ChannelPipeline pipeline = Channels.pipeline(); 34 | /*典型的过滤式处理*/ 35 | pipeline.addLast("encode", new StringEncoder()); 36 | pipeline.addLast("decode", new StringDecoder()); 37 | /*添加自定义的handler,对请求进行处理*/ 38 | pipeline.addLast("handler", handler); 39 | return pipeline; 40 | } 41 | }); 42 | 43 | /*使用tcp长连接*/ 44 | bootstrap.setOption("child.tcpNoDelay", true); 45 | bootstrap.setOption("child.keepAlive", true); 46 | bootstrap.setOption("reuseAddress", true); 47 | } 48 | /** 49 | * 绑定端口,启动netty服务 50 | */ 51 | public void start() { 52 | bootstrap.bind(new InetSocketAddress(port)); 53 | System.out.println("服务器启动,端口:" + port); 54 | } 55 | /** 56 | * 关闭netty,释放资源。 57 | */ 58 | public void stop() { 59 | bootstrap.releaseExternalResources(); 60 | } 61 | 62 | public void setPort(int port) { 63 | this.port = port; 64 | } 65 | 66 | public void setHandler(ServerHandler handler) { 67 | this.handler = handler; 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/main/java/com/netboy/netty/server/RunServer.java: -------------------------------------------------------------------------------- 1 | package com.netboy.netty.server; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.support.FileSystemXmlApplicationContext; 5 | 6 | /** 7 | * TODO 8 | * Administrator 2013-3-23下午07:49:32 9 | */ 10 | public class RunServer { 11 | 12 | public static void main(String[] args) { 13 | 14 | String contextFile = "./conf/spring-server.xml"; 15 | 16 | ApplicationContext context = null; 17 | try { 18 | context = new FileSystemXmlApplicationContext(contextFile); 19 | } catch (Exception e) { 20 | System.out.println("RunServer has some exception"); 21 | e.printStackTrace(); 22 | } 23 | final NettyServer server =(NettyServer)context.getBean("nettyServer"); 24 | 25 | Runtime.getRuntime().addShutdownHook(new Thread() { 26 | 27 | @Override 28 | public void run() { 29 | try { 30 | server.stop(); 31 | } catch (Exception e) { 32 | System.out.println("run main stop error!"); 33 | } 34 | } 35 | 36 | } ); 37 | server.init(); 38 | server.start(); 39 | } 40 | 41 | } 42 | --------------------------------------------------------------------------------