基于netty网络编程简单demo
代码如下(示例):
@Component
public class WebSocketServer {
private EventLoopGroup bossGroup; // 主线程池
private EventLoopGroup workerGroup; // 工作线程池
private ServerBootstrap server; // 服务器
private ChannelFuture future; // 回调
public void start() {
future = server.bind(8082);
System.out.println("netty server - 启动成功");
}
public WebSocketServer() {
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
server = new ServerBootstrap();
server.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new WebsocketInitializer());
}
}
@Component
public class NettyListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private WebSocketServer websocketServer;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(event.getApplicationContext().getParent() == null) {
try {
websocketServer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public class WebsocketInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// ------------------
// 用于支持Http协议
// ------------------
// websocket基于http协议,需要有http的编解码器
pipeline.addLast(new HttpServerCodec());
// 对写大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
// 添加对HTTP请求和响应的聚合器:只要使用Netty进行Http编程都需要使用
// 对HttpMessage进行聚合,聚合成FullHttpRequest或者FullHttpResponse
// 在netty编程中都会使用到Handler
pipeline.addLast(new HttpObjectAggregator(1024 * 64));
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
// 添加Netty空闲超时检查的支持
// 1. 读空闲超时(超过一定的时间会发送对应的事件消息)
// 2. 写空闲超时
// 3. 读写空闲超时
pipeline.addLast(new IdleStateHandler(4, 8, 12));
pipeline.addLast(new HearBeatHandler());
// 添加自定义的handler
pipeline.addLast(new ChatHandler());
}
}
/**
* 建立用户ID与通道的关联
*/
public class UserChannelMap {
// 用户保存用户id与通道的Map对象
private static Map<String, Channel> userChannelMap;
static {
userChannelMap = new HashMap<String, Channel>();
}
/**
* 添加用户id与channel的关联
* @param userid
* @param channel
*/
public static void put(String userid, Channel channel) {
userChannelMap.put(userid, channel);
}
/**
* 根据用户id移除用户id与channel的关联
* @param userid
*/
public static void remove(String userid) {
userChannelMap.remove(userid);
}
/**
* 根据通道id移除用户与channel的关联
* @param channelId 通道的id
*/
public static void removeByChannelId(String channelId) {
if(!StringUtils.isNotBlank(channelId)) {
return;
}
for (String s : userChannelMap.keySet()) {
Channel channel = userChannelMap.get(s);
if(channelId.equals(channel.id().asLongText())) {
System.out.println("客户端连接断开,取消用户" + s + "与通道" + channelId + "的关联");
userChannelMap.remove(s);
break;
}
}
}
// 打印所有的用户与通道的关联数据
public static void print() {
for (String s : userChannelMap.keySet()) {
System.out.println("用户id:" + s + " 通道:" + userChannelMap.get(s).id());
}
}
/**
* 根据好友id获取对应的通道
* @param friendid 好友id
* @return Netty通道
*/
public static Channel get(String friendid) {
return userChannelMap.get(friendid);
}
public class Message {
private Integer type; // 消息类型
private TdChatRecord chatRecord; // 聊天消息
private Object ext; // 扩展消息字段
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public TdChatRecord getChatRecord() {
return chatRecord;
}
public void setChatRecord(TdChatRecord chatRecord) {
this.chatRecord = chatRecord;
}
public Object getExt() {
return ext;
}
public void setExt(Object ext) {
this.ext = ext;
}
}
/**
* 建立用户ID与通道的关联
*/
public class UserChannelMap {
// 用户保存用户id与通道的Map对象
private static Map<String, Channel> userChannelMap;
static {
userChannelMap = new HashMap<String, Channel>();
}
/**
* 添加用户id与channel的关联
* @param userid
* @param channel
*/
public static void put(String userid, Channel channel) {
userChannelMap.put(userid, channel);
}
/**
* 根据用户id移除用户id与channel的关联
* @param userid
*/
public static void remove(String userid) {
userChannelMap.remove(userid);
}
/**
* 根据通道id移除用户与channel的关联
* @param channelId 通道的id
*/
public static void removeByChannelId(String channelId) {
if(!StringUtils.isNotBlank(channelId)) {
return;
}
for (String s : userChannelMap.keySet()) {
Channel channel = userChannelMap.get(s);
if(channelId.equals(channel.id().asLongText())) {
System.out.println("客户端连接断开,取消用户" + s + "与通道" + channelId + "的关联");
userChannelMap.remove(s);
break;
}
}
}
// 打印所有的用户与通道的关联数据
public static void print() {
for (String s : userChannelMap.keySet()) {
System.out.println("用户id:" + s + " 通道:" + userChannelMap.get(s).id());
}
}
/**
* 根据好友id获取对应的通道
* @param friendid 好友id
* @return Netty通道
*/
public static Channel get(String friendid) {
return userChannelMap.get(friendid);
}
}
public class HearBeatHandler extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvent = (IdleStateEvent)evt;
if(idleStateEvent.state() == IdleState.READER_IDLE) {
System.out.println("读空闲事件触发...");
}
else if(idleStateEvent.state() == IdleState.WRITER_IDLE) {
System.out.println("写空闲事件触发...");
}
else if(idleStateEvent.state() == IdleState.ALL_IDLE) {
System.out.println("---------------");
System.out.println("读写空闲事件触发");
System.out.println("关闭通道资源");
ctx.channel().close();
}
}
}
}
/**
* 处理消息的handler
* TextWebSocketFrame: 在netty中,是用于为websocket专门处理文本的对象,frame是消息的载体
*/
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
// 用来保存所有的客户端连接
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
//private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd hh:MM");
// 当Channel中有新的事件消息会自动调用
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 当接收到数据后会自动调用
// 获取客户端发送过来的文本消息
String text = msg.text();
System.out.println("接收到消息数据为:" + text);
Message message = JSON.parseObject(text, Message.class);
// 通过SpringUtil工具类获取Spring上下文容器
//ChatRecordService chatRecordService = SpringUtil.getBean(ChatRecordService.class);
switch (message.getType()) {
// 处理客户端连接的消息
case 0:
// 建立用户与通道的关联
String userid = message.getChatRecord().getUserid();
UserChannelMap.put(userid, ctx.channel());
System.out.println("建立用户:" + userid + "与通道" + ctx.channel().id() + "的关联");
UserChannelMap.print();
break;
// 处理客户端发送好友消息
case 1:
System.out.println("接收到用户消息");
// 将聊天消息保存到数据库
TdChatRecord chatRecord = message.getChatRecord();
//chatRecordService.insert(chatRecord);
// 如果发送消息好友在线,可以直接将消息发送给好友
Channel channel = UserChannelMap.get(chatRecord.getFriendid());
if(channel != null) {
channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
}
else {
// 如果不在线,暂时不发送
System.out.println("用户" + chatRecord.getFriendid() + "不在线");
}
break;
// 处理客户端的签收消息
case 2:
// 将消息记录设置为已读
//chatRecordService.updateStatusHasRead(message.getChatRecord().getId());
break;
case 3:
// 接收心跳消息
System.out.println("接收到心跳消息:" + JSON.toJSONString(message));
break;
}
}
// 当有新的客户端连接服务器之后,会自动调用这个方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// 将新的通道加入到clients
clients.add(ctx.channel());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
UserChannelMap.removeByChannelId(ctx.channel().id().asLongText());
ctx.channel().close();
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("关闭通道");
UserChannelMap.removeByChannelId(ctx.channel().id().asLongText());
UserChannelMap.print();
}
}