导入Maven依赖
<dependency>
<groupId>#maven.io-netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.33.Final</version>
<type>pom</type>
</dependency>
编码实现
服务端
NettyServer.java
import com.framework.common.utils.thredpool.PushThreadPools;
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.nio.NioServerSocketChannel;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
/**
* @author savesl
*/
@Component
public class NettyServer {
/**
* 日志
*/
private static final Logger logger = Logger.getLogger(NettyServer.class);
private int port;
private ServerSocketChannel serverSocketChannel;
public synchronized void startServer(int port) {
try {
this.port = port;
bind();
}catch(Exception ex) {
ex.printStackTrace();
logger.error("NettyServer start failure: "+ ex.getMessage());
}
}
private void bind() {
PushThreadPools.exec.execute(() -> {
//服务端要建立两个group,一个负责接收客户端的连接,一个负责处理数据传输
//连接处理group
EventLoopGroup boss = new NioEventLoopGroup(1);
//事件处理group
EventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
//创建ServerBootstrap实例
ServerBootstrap serverBootstrap=new ServerBootstrap();
// 绑定处理group
bootstrap.group(boss, worker)
//设置将要被实例化的ServerChannel类
.channel(NioServerSocketChannel.class)
///标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度
.option(ChannelOption.SO_BACKLOG, 1024)
//有数据立即发送
.option(ChannelOption.TCP_NODELAY, true)
/// 是否启用心跳保活机机制
.childOption(ChannelOption.SO_KEEPALIVE, true)
// .option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(65535))
//在ServerChannelInitializer中初始化ChannelPipeline责任链,并添加到serverBootstrap中
.childHandler(new ServerChannelInitializer());
ChannelFuture future;
try {
//绑定端口后,开启监听
future = bootstrap.bind(port).sync();
if (future.isSuccess()) {
serverSocketChannel = (ServerSocketChannel) future.channel();
System.out.println("server start success,port:" + port);
} else {
System.out.println("server start failure,port:" + port);
}
//等待服务监听端口关闭,就是由于这里会将线程阻塞,导致无法发送信息,所以我这里开了线程
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
}
finally {
//优雅地退出,释放线程池资源
boss.shutdownGracefully();
worker.shutdownGracefully();
}
});
}
public void sendMessage(Object msg){
if(serverSocketChannel != null){
serverSocketChannel.writeAndFlush(msg);
}
}
public static void main(String[] args) {
new NettyServer().startServer(7000);
}
}
线程池工具类:
public class PushThreadPools {
//核心线程池大小
static final int corePoolSize = 5;
//最大线程池大小
static final int maximumPoolSize = 50;
//线程最大空闲时间
static final long keepAliveTime = 2000L;
//时间单位
static final TimeUnit unit = TimeUnit.MILLISECONDS;
public static ExecutorService exec = new ThreadPoolExecutor(
// 核心线程池大小
corePoolSize,
//最大线程池大小
maximumPoolSize,
//线程最大空闲时间
keepAliveTime,
//时间单位
unit,
//线程等待队列
new LinkedBlockingQueue<Runnable>(64),
//线程创建工厂
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
},
//拒绝策略,这里直接抛出异常
new ThreadPoolExecutor.AbortPolicy()
);
}
ServerChannelInitializer.java
import com.framework.modules.api.netty.util.CodeUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* @author admin
*/
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//数据包结尾标识:【十六进制的0的字节数组】
ByteBuf delimiter = Unpooled.copiedBuffer(CodeUtil.hexItr2Arr("00"));
//设置数据包最大字节数,与结尾标识
pipeline.addLast(new DelimiterBasedFrameDecoder(65535, delimiter));
pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast("handler", new ServerHandler());
System.out.println("Client:"+ch.remoteAddress() +"连接上");
}
}
ServerHandler.java
import com.framework.modules.api.netty.util.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Arrays;
/**
* @author admin
*/
@Component
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Autowired
private Linux5SmallServiceUtil linux5SmallServiceUtil;
public static ServerHandler serverHandler;
public ServerHandler(){
}
@PostConstruct
public void init(){
serverHandler = this;
serverHandler.linux5SmallServiceUtil = this.linux5SmallServiceUtil;
}
/**
* 存储每一个客户端接入进来时的channel对象
*/
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
public void addChannel(Channel channel){
if(!channels.contains(channel)){
System.out.println(channel.remoteAddress()+"加入连接组...");
channels.add(channel);
}
}
public void removeChanel(Channel channel){
if(channels.contains(channel)){
System.out.println(channel.remoteAddress()+"退出连接组...");
channels.remove(channel);
}
}
/**
* 5.覆盖了 channelActive() 事件处理方法。服务端监听到客户端活动
* 客户端与服务端创建连接的时候调用
*/
@Override
public void channelActive(ChannelHandlerContext ctx) { // (5)
Channel incoming = ctx.channel();
addChannel(incoming);
System.out.println("Client:"+incoming.remoteAddress()+"连接开始....");
}
/**
* 6.覆盖了 channelInactive() 事件处理方法。服务端监听到客户端不活动
* 客户端与服务端断开连接时调用
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) { // (6)
Channel incoming = ctx.channel();
removeChanel(incoming);
System.out.println("Client:"+incoming.remoteAddress()+"掉线....");
}
/**
* 服务端接收客户端发送过来的数据结束之后调用
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
System.out.println("Client:"+ctx.channel().remoteAddress()+"信息接收完毕...");
}
/**
* 7.exceptionCaught() 事件处理方法是当出现 Throwable 对象才会被调用,即当 Netty 由于 IO
* 错误或者处理器在处理事件时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来并且把关联的 channel 给关闭掉。
* 然而这个方法的处理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。
* 服务出现异常的时候调用
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (7)
Channel incoming = ctx.channel();
System.out.println("Client:"+incoming.remoteAddress()+"异常");
// 当出现异常就关闭连接
cause.printStackTrace();
ctx.close();
}
/**
*
* 服务端处理客户端socket请求的核心方法,这里接收了客户端发来的信息
* @param ctx
* @param info
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object info) throws Exception {
//以下为业务处理逻辑
RequestData requestData = RequestData.parsingJsonString(info.toString());
ResponseData responseData = serverHandler.linux5SmallServiceUtil.responseController(requestData.getType(), requestData);
String response = ResponseData.toJsonString(responseData);
System.err.println(response);
ByteBuf pingMessage = Unpooled.buffer();
byte[] arr = response.getBytes("UTF-8");
byte[] pix = CodeUtil.hexItr2Arr("00");
byte[] result = Arrays.copyOf(arr, arr.length + pix.length);
System.arraycopy(pix, 0, result, arr.length, pix.length);
pingMessage.writeBytes(result);
ctx.writeAndFlush(pingMessage);
// Channel incoming = ctx.channel();
// for (Channel channel : channels) {
// if (channel == incoming){
// channel.writeAndFlush(str);
// }
// }
}
}
客户端
Client.java
public class Client {
static EventLoopGroup group =null;
static Bootstrap client =null;
public static ChannelFuture future=null;
static {
group = new NioEventLoopGroup();
client = new Bootstrap();
client.group(group);
client.channel(NioSocketChannel.class);
client.option(ChannelOption.SO_KEEPALIVE,true);
client.handler(new ClientChannelInitializer());
try {
future = client.connect("192.168.0.164", 7000).sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void sendMessage(String content) throws Exception{
// Configure the client.
EventLoopGroup group = new NioEventLoopGroup();
try {
byte[] arr = content.getBytes("UTF-8");
//将结尾标识【0的十六进制字节数组】追加到消息字节数组尾部
byte[] pix = CodeUtil.hexItr2Arr("00");
byte[] result = Arrays.copyOf(arr, arr.length + pix.length);
System.arraycopy(pix, 0, result, arr.length, pix.length);
future.channel().writeAndFlush(Unpooled.buffer().writeBytes(result));
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("version", "1.0");
map.put("type", "rsync_person_req");
map.put("sn", "'86E492248B780F1C'");
map.put("seq", 47);
Map<String, Object> data = new HashMap<>();
data.put("person_type", 0);
data.put("person_id", 58);
map.put("data", data);
JSONObject json = new JSONObject(map);
sendMessage(json.toString());
// for(int i=0;i<20;i++){
// new Thread(new UserRequestThread(i)).start();//模拟多线程并发请求
// }
}
ClientChannelInitializer.java
public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline p = channel.pipeline();
//数据包结尾标识:【十六进制的0的字节数组】
ByteBuf delimiter = Unpooled.copiedBuffer(CodeUtil.hexItr2Arr("00"));
//设置数据包最大字节数,与结尾标识
p.addLast(new DelimiterBasedFrameDecoder(65535, delimiter));
p.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
p.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
p.addLast(new ClientHandler());
}
}
ClientHandler.java
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("ClientHandler Active");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("--------");
System.out.println("ClientHandler read Message:"+msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
使用过程中的常见问题:
Netty handler处理类使用@Autowired注入无效
问题产生的原因:ServerHandler是netty启动的时候new出来,并没有交给spring IOC托管
解决方案:使用spring的两个注解@Component及@PostConstruct@Component大家都比较熟悉,就不做介绍了
@PostConstruct: 在方法上加该注解会在项目启动的时候执行该方法,即spring容器初始化的时候执行,它与构造函数及@Autowired的执行顺序为:构造函数 >> @Autowired >> @PostConstruct,由此看来当我们想在生成对象时完成某些初始化操作,而偏偏这些初始化操作又依赖于注入的bean,那么就无法在构造函数中实现,为此可以使用@PostConstruct注解一个init方法来完成初始化,该方法会在bean注入完成后被自动调用。
编码实现
@Component
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Autowired
private Linux5SmallServiceUtil linux5SmallServiceUtil;
public static ServerHandler serverHandler;
public ServerHandler(){
}
@PostConstruct
public void init(){
serverHandler = this;
serverHandler.linux5SmallServiceUtil = this.linux5SmallServiceUtil;
}
使用方式:
serverHandler.linux5SmallServiceUtil.responseController(requestData.getType(), requestData);
修改数据包结尾标识与最大字节长度