Netty是对NIO的封装。虽然其使用比Java BIO(也就是传统的基于流或字符阻塞型的数据读写)也要复杂。不过效率高。没有那么多废话。本片的目标是使用Netty实现一个聊天功能。功能的完善、需要大家在亲自操刀。
服务器端
Netty服务器端的一般写法。
package com.example.gch;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
public static void main(String[] args){
Server server = new Server(8080);
}
private ServerSocketChannel serverSocketChannel;
public Server(int serverPort) {
bind(serverPort);
}
private void bind(final int serverPort) {
Thread thread = new Thread(new Runnable() {
public void run() {
//服务端要建立两个group,一个负责接收客户端的连接,一个负责处理数据传输
//连接处理group
EventLoopGroup boss = new NioEventLoopGroup();
//事件处理group
EventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
// 绑定处理group
bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
//保持连接数
.option(ChannelOption.SO_BACKLOG, 128)
//保持连接
.childOption(ChannelOption.SO_KEEPALIVE, true)
//处理新连接
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 增加任务处理
ChannelPipeline p = socketChannel.pipeline();
// p.addLast(new SimpleServerHandler());
p.addLast(new ServerHandler());
}
})
;
//绑定端口,同步等待成功
ChannelFuture future;
try {
future = bootstrap.bind(serverPort).sync();
if (future.isSuccess()) {
serverSocketChannel = (ServerSocketChannel) future.channel();
System.out.println("服务端开启成功");
} else {
System.out.println("服务端开启失败");
}
//等待服务监听端口关闭,就是由于这里会将线程阻塞,导致无法发送信息,所以我这里开了线程
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
//优雅地退出,释放线程池资源
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
});
thread.start();
}
public void sendMessage(Object msg) {
if (serverSocketChannel != null) {
serverSocketChannel.writeAndFlush(msg);
}
}
}
使用NettyConfig 记录所有的客户端连接的Channel。
package com.example.gch;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.GlobalEventExecutor;
/**
* 存储整个工程的全局配置
*
* @author NewBies
*/
public class NettyConfig {
/**
* 存储每一个客户端接入进来时的channel对象
*/
public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}
和客户端交互定义的地方(PS.需要进行着重开发)
在**channelRead**中有对单聊和多聊的不同发送方式
package com.example.gch;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Iterator;
public class ServerHandler extends ChannelInboundHandlerAdapter {
/**
* 客户端与服务端创建连接的时候调用
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端与服务端连接开始...");
NettyConfig.group.add(ctx.channel());
}
/**
* 客户端与服务端断开连接时调用
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端与服务端连接关闭...");
NettyConfig.group.remove(ctx.channel());
}
/**
* 服务端接收客户端发送过来的数据结束之后调用
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
System.out.println("信息接收完毕...");
}
/**
* 工程出现异常的时候调用
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
/**
* 服务端处理客户端websocket请求的核心方法,这里接收了客户端发来的信息
*/
@Override
public void channelRead(ChannelHandlerContext channelHandlerContext, Object info) throws Exception {
ByteBuf result = (ByteBuf) info;
byte[] result1 = new byte[result.readableBytes()];
result.readBytes(result1);
System.out.println("我是服务端,我接受到了:" + new String(result1));
//服务端使用这个就能向 每个连接上来的客户端群发消息
// NettyConfig.group.writeAndFlush(info);
channelHandlerContext.writeAndFlush(info);
Iterator<Channel> iterator = NettyConfig.group.iterator();
while (iterator.hasNext()) {
//打印出所有客户端的远程地址
System.out.println((iterator.next()).remoteAddress());
}
try {
Thread.sleep(3000);
// 向客户端发送消息
String response = "hello client";
// 在当前场景下,发送的数据必须转换成ByteBuf数组
ByteBuf encoded = channelHandlerContext.alloc().buffer(4 * response.length());
encoded.writeBytes(response.getBytes());
//单聊
// channelHandlerContext.write(encoded);
// channelHandlerContext.flush();
//群聊
NettyConfig.group.writeAndFlush(encoded);
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端应用
这是Netty客户端一般写法
package com.example.gch;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
public static void main(String[] args) {
Client bootstrap = new Client(8080, "localhost");
}
private int port;
private String host;
private SocketChannel socketChannel;
public Client(int port, String host) {
this.host = host;
this.port = port;
start();
}
private void start() {
Thread thread = new Thread(new Runnable() {
public void run() {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class)
// 保持连接
.option(ChannelOption.SO_KEEPALIVE, true)
// 有数据立即发送
.option(ChannelOption.TCP_NODELAY, true)
// 绑定处理group
.group(eventLoopGroup).remoteAddress(host, port)
.handler(new SimpleClientHandler());
// 进行连接
ChannelFuture future;
try {
future = bootstrap.connect(host, port).sync();
// 判断是否连接成功
if (future.isSuccess()) {
// 得到管道,便于通信
socketChannel = (SocketChannel) future.channel();
System.out.println("客户端开启成功...");
} else {
System.out.println("客户端开启失败...");
}
// 等待客户端链路关闭,就是由于这里会将线程阻塞,导致无法发送信息,所以我这里开了线程
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅地退出,释放相关资源
eventLoopGroup.shutdownGracefully();
}
}
});
thread.start();
}
}
这里是客户端处理服务器连接的方法
package com.example.gch;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class SimpleClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("SimpleClientHandler.channelRead");
ByteBuf result = (ByteBuf) msg;
byte[] result1 = new byte[result.readableBytes()];
result.readBytes(result1);
System.out.println("Server said:" + new String(result1));
result.release();
//write to server.
// try {
// Thread.sleep(3000);
// // 向客户端发送消息
// String response = "hello server!";
// // 在当前场景下,发送的数据必须转换成ByteBuf数组
// ByteBuf encoded = ctx.alloc().buffer(4 * response.length());
// encoded.writeBytes(response.getBytes());
// ctx.write(encoded);
// ctx.flush();
// } catch (Exception e) {
// e.printStackTrace();
// }
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 当出现异常就关闭连接
cause.printStackTrace();
ctx.close();
}
// 连接成功后,向server发送消息
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String msg = "hello Server!";
ByteBuf encoded = ctx.alloc().buffer(4 * msg.length());
encoded.writeBytes(msg.getBytes());
ctx.write(encoded);
ctx.flush();
}
}
项目地址
https://github.com/guchuanhang/HttpLongDemo 服务器端&客户端
https://github.com/guchuanhang/HttpLongAndroidDemo 安卓客户端
欢迎大家fork&star!
对于标志用户谁是谁,可以在刚和服务器建立连接的时候,发送自己的身份信息。在服务器端进行user-channel映射。