服务端代码 及 解释
public class ServerHelloWorld {
//监听线程组,监听客户端请求
private EventLoopGroup acceptorGroup = null;
//处理客户端相关线程组,负责处理与客户端的数据通讯
private EventLoopGroup clientGroup = null;
//服务启动相关配置信息
private ServerBootstrap bootstrap = null;
public ServerHelloWorld(){
init();
}
private void init(){
//初始化线程组
acceptorGroup = new NioEventLoopGroup();
clientGroup = new NioEventLoopGroup();
//初始化服务的配置
bootstrap = new ServerBootstrap();
//绑定线程组
bootstrap.group(acceptorGroup,clientGroup);
//设置通讯模式为NIO,(同步非阻塞)
bootstrap.channel(NioServerSocketChannel.class);
//设置缓冲区大小,单位是字节
bootstrap.option(ChannelOption.SO_BACKLOG,1024);
//SO_SNDBUF发送缓冲区,SO_RCVBUF接收缓冲区,SO_KEEPALIVE开启心跳检测(保证连接有效)
bootstrap.option(ChannelOption.SO_SNDBUF,16*1024)
.option(ChannelOption.SO_RCVBUF,16*1024)
.option(ChannelOption.SO_KEEPALIVE,true);
}
public ChannelFuture doAccept(int port, final ChannelHandler... acceptorsHandlers)throws Exception{
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch){
ch.pipeline().addLast(acceptorsHandlers);
}
});
ChannelFuture future = bootstrap.bind(port).sync();
return future;
}
public void release(){
this.acceptorGroup.shutdownGracefully();
this.clientGroup.shutdownGracefully();
}
public static void main(String[] args) {
ChannelFuture future = null;
ServerHelloWorld server = null;
try{
server = new ServerHelloWorld();
future = server.doAccept(9999,new ServerHelloWorldHandler());
System.out.println("server started .");
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(null!=future){
try{
future.channel().closeFuture().sync();
}catch(InterruptedException e){
e.printStackTrace();
}
}
if(null !=future){
server.release();
}
}
}
}
- 这里我们看到我们创建了两个线程组,对应传统IO两大线程组,用生活中的例子来讲就是,一个工厂要运作,必然要有一个老板负责从外面接活,然后有很多员工,负责具体干活,老板就是acceptorGroup ,员工们就是clientGroup ,acceptorGroup 接收完连接,扔给clientGroup 去处理。
- 同时创建了一个引导类 ServerBootstrap,这个类将引导我们进行服务端的启动工作,其中.group(bossGroup, workerGroup)给引导类配置两大线程组。
- 我们指定我们服务端的 IO 模型为NIO,我们通过.channel(NioServerSocketChannel.class)来指定 IO 模型,当然,这里也有其他的选择,如果你想指定 IO 模型为 BIO,那么这里配置上OioServerSocketChannel.class类型即可,当然通常我们也不会这么做,因为Netty的优势就在于NIO。
业务逻辑如下代码所示
package netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
/**
* <p> </p>
*
* @author ly
* @since 2018/12/9
*/
public class ServerHelloWorldHandler extends ChannelHandlerAdapter{
/**
* 业务处理逻辑
* 用于处理读取数据请求的逻辑
* @param ctx 上下文对象,其中包含客户端建立的所有资源,如:对应的Channel
* @param msg 读取到的数据,默认类型为ByteBuf,是Netty自定义的。是对ByteBufer的封装。不需要考虑复位问题
*/
public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception {
ByteBuf readBuffer = (ByteBuf) msg;
//创建一个字节数组,用于保存缓存中的数据
byte[] tempDatas = new byte[readBuffer.readableBytes()];
readBuffer.readBytes(tempDatas);
String message = new String(tempDatas,"UTF-8");
System.out.println("from client :"+message);
if("exit".equals(message)){
ctx.close();
return;
}
String line = "server message to client";
//写入自动释放缓存,避免内存溢出。如果是write,不会刷新缓存,数据不会发送到客户端,必须调用flush
ctx.writeAndFlush(Unpooled.copiedBuffer(line.getBytes("UTF-8")));
}
//异常处理逻辑,当客户端退出时,也会运行
@Override
public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
System.out.println("server exceptionCaught method run ...");
ctx.close();
}
}