SpringBoot整合Netty实现socket通讯简单demo
依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
还用到了
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
ChatDto.java
import lombok.Data;
import lombok.experimental.Accessors; /**
* 传输实体类
*/
@Data
@Accessors(chain = true)
public class ChatDto { /**
* 客户端ID 唯一
*/
private String clientId; /**
* 发送的消息
*/
private String msg;
}
NettyChannelMap.java
import io.netty.channel.Channel;
import io.netty.channel.socket.SocketChannel; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; /**
* 存放连接的channel对象
*/
public class NettyChannelMap {
private static Map<String, SocketChannel> map = new ConcurrentHashMap<String, SocketChannel>(); public static void add(String clientId, SocketChannel socketChannel) {
map.put(clientId, socketChannel);
} public static Channel get(String clientId) {
return map.get(clientId);
} public static void remove(SocketChannel socketChannel) {
for (Map.Entry entry : map.entrySet()) {
if (entry.getValue() == socketChannel) {
map.remove(entry.getKey());
}
}
}
}
NettyTcpServerBootstrap.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; /**
* 服务端启动类
*/
public class NettyTcpServerBootstrap {
private int port;
private SocketChannel socketChannel; public NettyTcpServerBootstrap(int port) throws InterruptedException {
this.port = port;
} public void start() throws InterruptedException {
/**
* 创建两个线程组 bossGroup 和 workerGroup
* bossGroup 只是处理连接请求,真正的和客户端业务处理,会交给 workerGroup 完成
* 两个都是无线循环
*/
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); try {
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//设置两个线程组
bootstrap.group(bossGroup, workerGroup)
//使用NioServerSocketChannel 作为服务器的通道实现
.channel(NioServerSocketChannel.class)
//设置线程队列得到连接个数
.option(ChannelOption.SO_BACKLOG, 128)
//设置保持活动连接状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
//通过NoDelay禁用Nagle,使消息立即发出去,不用等待到一定的数据量才发出去
.childOption(ChannelOption.TCP_NODELAY, true)
//可以给 bossGroup 加个日志处理器
.handler(new LoggingHandler(LogLevel.INFO))
//给workerGroup 的 EventLoop 对应的管道设置处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
//给pipeline 设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline p = socketChannel.pipeline();
p.addLast(new ObjectEncoder());
p.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
p.addLast(new NettyServerHandler());
}
}); //启动服务器并绑定一个端口并且同步生成一个 ChannelFuture 对象
ChannelFuture cf = bootstrap.bind(port).sync();
if (cf.isSuccess()) {
System.out.println("socket server start---------------");
} //对关闭通道进行监听
cf.channel().closeFuture().sync();
} finally {
//发送异常关闭
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully(); }
} }
NettyServerHandler.java
import com.alibaba.fastjson.JSON;
import com.example.netty.dto.ChatDto;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j; @Slf4j
public class NettyServerHandler extends SimpleChannelInboundHandler<Object> { /**
* 定义一个channel组管理所有channel
* GlobalEventExecutor.INSTANCE 是一个全局事件执行器 是一个单例
*/
private static ChannelGroup channelGroup=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); /**
* 标识 channel处于活动状态
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { } /**
* 表示连接建立 第一个被执行
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
channelGroup.add(ctx.channel()); /**
* 该方法会将 channelGroup 中所有的channel 遍历一遍然后发送消息 不用我们自己遍历
* 这里只是做个说明 不用
*/
// channelGroup.writeAndFlush("发送所有给所有channel");
} /**
* 断开连接
* @param ctx
* @throws Exception
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { } /**
* 标识channel处于非活动状态
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
NettyChannelMap.remove((SocketChannel) ctx.channel());
} /**
* 服务端 接收到 客户端 发的数据
* @param context
* @param obj
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext context, Object obj) throws Exception { log.info(">>>>>>>>>>>服务端接收到客户端的消息:{}",obj); SocketChannel socketChannel = (SocketChannel) context.channel();
ChatDto dto = JSON.parseObject(obj.toString(), ChatDto.class);
/**
* 客户端ID
*/
String clientId = dto.getClientId(); if (clientId == null) {
/**
* 心跳包处理
*/
ChatDto pingDto=new ChatDto();
pingDto.setMsg("服务端收到心跳包,返回响应");
socketChannel.writeAndFlush(JSON.toJSONString(pingDto));
return;
} Channel channel = NettyChannelMap.get(clientId); if (channel==null){
/**
* 存放所有连接客户端
*/
NettyChannelMap.add(clientId, socketChannel);
channel=socketChannel;
} /**
* 服务器返回客户端消息
*/
ChatDto returnDto=new ChatDto();
returnDto.setClientId(clientId).setMsg("我是服务端,收到你的消息了"); channel.writeAndFlush(JSON.toJSONString(returnDto)); /**
* 在这里可以设置异步执行 提交任务到该channel的taskQueue 中
*/ context.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10*1000);
log.info(">>>>>>>>>休眠十秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}); /**
* 可以设置多个异步任务
* 但是这个会在上面异步任务执行完之后才执行
*/
context.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10*1000);
log.info(">>>>>>>>>休眠二十秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}); ReferenceCountUtil.release(obj);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
ctx.close();
}
}
客户端
NettyClientBootstrap.java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup; /**
* 客户端启动类
*/
public class NettyClientBootstrap {
private int port;
private String host;
public SocketChannel socketChannel;
private static final EventExecutorGroup group = new DefaultEventExecutorGroup(20); public NettyClientBootstrap(int port, String host) throws InterruptedException {
this.port = port;
this.host = host;
start();
} private void start() throws InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.remoteAddress(host, port)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
/**
* 0 表示禁用
* readerIdleTime读空闲超时时间设定,如果channelRead()方法超过readerIdleTime时间未被调用则会触发超时事件调用userEventTrigger()方法;
*
* writerIdleTime写空闲超时时间设定,如果write()方法超过writerIdleTime时间未被调用则会触发超时事件调用userEventTrigger()方法;
*
* allIdleTime所有类型的空闲超时时间设定,包括读空闲和写空闲;
*/
socketChannel.pipeline().addLast(new IdleStateHandler(20, 10, 0));
socketChannel.pipeline().addLast(new ObjectEncoder());
socketChannel.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
socketChannel.pipeline().addLast(new NettyClientHandler());
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
if (future.isSuccess()) {
socketChannel = (SocketChannel) future.channel();
System.out.println("connect server 成功---------");
}
//给关闭通道进行监听
future.channel().closeFuture().sync();
} finally {
eventLoopGroup.shutdownGracefully();
}
} }
NettyClientHandler.java
import com.alibaba.fastjson.JSON;
import com.example.netty.dto.ChatDto;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j; @Slf4j
public class NettyClientHandler extends SimpleChannelInboundHandler<Object> { @Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
log.info(">>>>>>>>连接");
} @Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
log.info(">>>>>>>>退出");
} @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleStateEvent e = (IdleStateEvent) evt;
switch (e.state()) {
case WRITER_IDLE:
/**
* 利用写空闲发送心跳检测消息
*/
ChatDto pingDto=new ChatDto();
pingDto.setMsg("我是心跳包");
ctx.writeAndFlush(JSON.toJSONString(pingDto));
log.info("send ping to server----------");
break;
default:
break;
}
}
} /**
* 客户端接收到服务端发的数据
* @param channelHandlerContext
* @param obj
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object obj) { log.info(">>>>>>>>>>>>>客户端接收到消息:{}", obj); ReferenceCountUtil.release(obj);
} /**
* socket通道处于活动状态
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info(">>>>>>>>>>socket建立了");
super.channelActive(ctx);
} /**
* socket通道不活动了
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info(">>>>>>>>>>socket关闭了");
super.channelInactive(ctx);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
ctx.close();
}
}
SprigBoot启动类添加服务端启动代码
@SpringBootApplication
public class NettyApplication { public static void main(String[] args) {
SpringApplication.run(NettyApplication.class, args); try {
NettyTcpServerBootstrap bootstrap = new NettyTcpServerBootstrap(9999);
bootstrap.start();
} catch (Exception e) {
e.printStackTrace();
System.out.println("server socket 启动失败");
}
} }
ChatController.java
import com.alibaba.fastjson.JSON;
import com.example.netty.dto.ChatDto;
import com.example.netty.socket.NettyClientBootstrap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController; import java.util.UUID; /**
* 客户端消息发送控制器
*/
@RestController
@Slf4j
public class ChatController { private static String clientId=UUID.randomUUID().toString(); public static NettyClientBootstrap bootstrap; /**
* 发送消息demo
* @param msg
*/
@PostMapping(value = "/send")
public void send(String msg) {
if (bootstrap == null) {
try {
/**
* 连接 输入服务器的端口和ip
*/
bootstrap = new NettyClientBootstrap(9999, "localhost");
} catch (InterruptedException e) {
e.printStackTrace();
log.error(">>>>>>>>> server socket 连接失败");
}
}
/**
* 发送消息
*/
ChatDto dto=new ChatDto();
dto.setClientId(clientId).setMsg(msg);
/**
* json字符串发送
*/
bootstrap.socketChannel.writeAndFlush(JSON.toJSONString(dto)); }
}
访问
SpringBoot整合Netty实现socket通讯简单demo的更多相关文章
- springboot整合netty(二)
目录 前言 正文 代码 1. 新建一个springboot项目,在pom文件中添加netty依赖: 2.新建netty服务 3.netty调用所需的服务类 4 springboot启动类 5.测试 我 ...
- SpringBoot整合Netty并使用Protobuf进行数据传输(附工程)
前言 本篇文章主要介绍的是SpringBoot整合Netty以及使用Protobuf进行数据传输的相关内容.Protobuf会简单的介绍下用法,至于Netty在之前的文章中已经简单的介绍过了,这里就不 ...
- springboot整合netty的多种方式
netty作为一个高性能的io框架,是非好用的一个技术框架, Netty 是一个基于NIO的客户.服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户. ...
- springboot整合netty,多种启动netty的方式,展现bean得多种启动方法
首先讲解下,spring中初始化加载问题: 很多时候,我们自己写的线程池,还有bean对象,还有其他的服务类,都可以通过,相关注解进行交给spring去管理,那么我们如何让nettyserver初始化 ...
- SpringBoot整合Netty
总体来说只需要三个步骤,下面详细介绍 第一步 创建SpringBoot工程snetty 第二步 创建子模块也就是ModuleClient,Server,均为SpringBoot工程 第三步 将服务器端 ...
- 第6章 使用springboot整合netty搭建后台
我们不会去使用自增长的id,在现阶段的互联网开发过程中,自增长的id是已经不适用了.在未来随着系统版本的迭代,用户数量的递增,肯定会做分库分表,去做一些相应的切分.在这个时候我们就需要有一个唯一的id ...
- Node中的Socket.IO 简单Demo及说明
注:下面Demo的Server和Client都是纯后端. 并没有web页面. Server端代码: var express = require('express'); var app = expres ...
- SpringBoot整合Guacamole教程
前言 本文主要介绍的是SpringBoot如何整合Guacamole在浏览器是远程桌面的访问. Guacamole 介绍 Apache Guacamole 是一个无客户端远程桌面网关.它支持标准协议, ...
- springboot整合mybatis出现的一些问题
springboot整合mybatis非常非常的简单,简直简单到发指.但是也有一些坑,这里我会详细的指出会遇到什么问题,并且这些配置的作用 整合mybatis,无疑需要mapper文件,实体类,dao ...
随机推荐
- 快速沃尔什变换&快速莫比乌斯变换小记
u1s1 距离省选只剩 5 days 了,现在学新算法真的合适吗(( 位运算卷积 众所周知,对于最普通的卷积 \(c_i=\sum\limits_{j+k=i}a_jb_k\),\(a_jb_k\) ...
- Codeforces 1188E - Problem from Red Panda(找性质+组合数学)
Codeforces 题面传送门 & 洛谷题面传送门 咦,题解搬运人竟是我? 一道很毒的计数题. 先转化下题意,每一次操作我们可以视作选择一种颜色并将其出现次数 \(+k\),之后将所有颜色的 ...
- UOJ #11 - 【UTR #1】ydc的大树(换根 dp)
题面传送门 Emmm--这题似乎做法挺多的,那就提供一个想起来写起来都不太困难的做法吧. 首先不难想到一个时间复杂度 \(\mathcal O(n^2)\) 的做法:对于每个黑点我们以它为根求出离它距 ...
- 深入了解scanf() getchar()和gets()等函数之间的区别
scanf(), getchar()等都是标准输入函数,一般人都会觉得这几个函数非常简单,没什么特殊的.但是有时候却就是因为使用这些函数除了问题,却找不出其中的原因.下面先看一个很简单的程序: 程序1 ...
- 学习java的第五天
一.今日收获 1.java程序设计完全手册第一章节的小总结 2.完成了部分例题验证解答. 二.今日难题 1.java语言与c++语言的不同点. 2.有例题验证不出来 三.明日目标 1.复习java程序 ...
- python生成器浅析
A 'generator' is a function which returns a generator iterator. It looks like a normal function exce ...
- java9 模块化 jigsaw
java9并没有在语言层面做出很多改变,而是致力于一些新特性,如模块化,其核心就是解决历史遗留问题,为以后的jar包森林理清道路.模块化是一个很大的命题,就不讲那么细致了,关于java9的特性也有很多 ...
- 如何启动、关闭和设置ubuntu防火墙 (转载)
引自:http://www.cnblogs.com/jiangyao/archive/2010/05/19/1738909.html 由于LInux原始的防火墙工具iptables过于繁琐,所以ubu ...
- Stream.toMap
Collectors类的tomap方法将流收集到映射实例中. list 转 map collection.stream().collect(Collectors.toMap(User::getId, ...
- mysql删除数据后不释放空间问题
如果表的引擎是InnoDB,Delete From 结果后是不会腾出被删除的记录(存储)空间的. 需要执行:optimize table 表名; eg:optimize table eh_user_b ...