[经验] Java 使用 netty 框架, 向 Unity 客户端的 C# 实现通信 [1]
这是一个较为立体的思路吧
首先是技术选型:
前端 : HTML5 + jQuery ,简单暴力, 不解释
服务端 : Spring Boot + Netty + Redis/Cache
客户端 : Unity3D + C#
所要实现的效果为:
服务端启动后, 开启端口监听, 然后客户端启动, 连接上服务端, 再由前端将数据请求发送到服务端, 服务端再发送到客户端
为了方便(懒), 所以使用 netty 4.x 作为主要的通讯框架, 由于 5.X好像已经被官方放弃了, 所以我就使用最新版的
在 pom.xml 处添加 netty4.x 的依赖
<!-- netty 通信框架 https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.39.Final</version>
</dependency> <!-- netty websocket 通讯框架依赖 -->
<dependency>
<groupId>org.yeauty</groupId>
<artifactId>netty-websocket-spring-boot-starter</artifactId>
<version>0.8.0</version>
</dependency
老规矩, 从服务端开始, 先创建 netty 的服务端程序
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.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.apache.http.client.utils.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit; /*
*@Description //TODO NIO 服务端$
*@Author 吾王剑锋所指 吾等心之所向
*@Date 2019/8/27 19:18
*/
public class NettyServer {
private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);//默认端口
private Integer defaultPort = 5566;
public void bind(Integer port) throws Exception {
//配置服务端的NIO线程组
EventLoopGroup master = new NioEventLoopGroup();
EventLoopGroup servant = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(master, servant).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//将响应请求消息解码为 HTTP 消息
socketChannel.pipeline().addLast("http-codec", new HttpServerCodec());
//将HTTP消息的多个部分构建成一条完整的 HTTP 消息
socketChannel.pipeline().addLast("aggregator",new HttpObjectAggregator(2048));
//向客户端发送 HTML5 文件
socketChannel.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
//设置心跳检测
socketChannel.pipeline().addLast(new IdleStateHandler(60, 30, 60*30, TimeUnit.SECONDS));
//配置通道, 进行业务处理
socketChannel.pipeline().addLast(new NettyServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_KEEPALIVE, true) // 2小时无数据激活心跳机制
.childHandler(new ServerChannelInitializer()); if(null==port) port=this.defaultPort; // 服务器异步创建绑定 ChannelFuture future = bootstrap.bind(port).sync();
logger.info("服务启动:"+ DateUtils.formatDate(new Date()));
future.channel().closeFuture().sync(); // 关闭服务器通道
} finally {
logger.info("服务停止:"+ DateUtils.formatDate(new Date()));
// 释放线程池资源
master.shutdownGracefully();
servant.shutdownGracefully();
}
}
}
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder; /*
*@Description //TODO nio 服务端实现$
*@Author 吾王剑锋所指 吾等心之所向
*@Date 2019/8/27 19:20
*/
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new LineBasedFrameDecoder(10010));
pipeline.addLast( new StringDecoder());
pipeline.addLast( new StringEncoder());
pipeline.addLast("handler", new NettyServerHandler());
}
}
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import io.netty.util.CharsetUtil; import org.apache.http.client.utils.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.Date; /*
*@Description //TODO 服务业务实现$
*@Author 吾王剑锋所指 吾等心之所向三
*@Date 2019/8/28 9:50
*/
public class NettyServerHandler extends SimpleChannelInboundHandler<Object> {
private static Logger LOGGER = LoggerFactory.getLogger(NettyServerHandler.class); private static final String URI = "websocket";
private WebSocketServerHandshaker handshaker; /**
* 读取客户端发来的数据
*
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
String[] data = msg.toString().split("id=");
if(data != null && data.length > 1) {
String[] data1 = data[1].split(";");
String id = data1[0];
if (NettyServer.map.get(id) != null && NettyServer.map.get(id).equals(ctx)) { //不是第一次连接
LOGGER.info("接收数据成功!" + DateUtils.formatDate(new Date()));
} else { //如果map中没有此ctx 将连接存入map中
NettyServer.map.put(id, ctx);
LOGGER.info("连接成功,加入map管理连接!"+"mn:" +id+" : "+ctx+""+ DateUtils.formatDate(new Date()));
}
}else{
LOGGER.info("不是监测数据"+ msg.toString()+" : "+ DateUtils.formatDate(new Date()));
}
ctx.writeAndFlush("Received your message : " + msg.toString());
} /**
* 读取完毕客户端发送过来的数据之后的操作
* */
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
LOGGER.info("服务端接收数据完毕..");
ctx.channel().write("call ------"); //向客户端发送一条信息
ctx.channel().flush();
} /**
* 客户端主动断开服务端的链接,关闭流
* */
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().localAddress().toString() + " 通道不活跃!");
removeChannelMap(ctx);
ctx.close(); // 关闭流
} /**
* 客户端主动连接服务端 连接成功时向客户端发送一条信息
*
* */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
LOGGER.info("RemoteAddress"+ ctx.channel().remoteAddress() + " active !");
LOGGER.info("msg send active !"+ctx.channel().writeAndFlush("123456"));
ctx.writeAndFlush("啦啦啦!");super.channelActive(ctx);
} /**
* 异常处理
*
* */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
LOGGER.error("连接异常,连接异常:"+ DateUtils.formatDate(new Date())+cause.getMessage(), cause);
ctx.fireExceptionCaught(cause);
removeChannelMap(ctx);
ctx.close();
} /**
*删除map中ChannelHandlerContext
*
* */
private void removeChannelMap(ChannelHandlerContext ctx){
for( String key :NettyServer.map.keySet()){
if( NettyServer.map.get(key)!=null && NettyServer.map.get(key).equals( ctx)){
NettyServer.map.remove(key);
}
}
} /**
* 收发消息处理
*
* */
protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception{
if(msg instanceof HttpRequest){
doHandlerHttpRequest(ctx, (HttpRequest) msg);
}else if(msg instanceof HttpRequest){
doHandlerWebSocketFrame(ctx, (WebSocketFrame) msg);
}
} /**
* 进行心跳检测, 保证用户在线
*
*
* */
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent){
IdleStateEvent stateEvent = (IdleStateEvent) evt;
PingWebSocketFrame ping = new PingWebSocketFrame();
switch (stateEvent.state()){
case READER_IDLE: //读空闲 服务器端
LOGGER.info("{["+ctx.channel().remoteAddress()+"]--->(服务端 read 空闲)}");
ctx.writeAndFlush(ping);
break;
case WRITER_IDLE: //写空闲 服务器端
LOGGER.info("{["+ctx.channel().remoteAddress()+"]--->(服务端 write 空闲)}");
ctx.writeAndFlush(ping);
break;
case ALL_IDLE: //读写空闲 服务器端
LOGGER.info("{["+ctx.channel().remoteAddress()+"]--->(服务端 读写 空闲)}");
}
}
} /**
* websocket 消息处理
*
* */
protected void doHandlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame msg){
if(msg instanceof CloseWebSocketFrame){ //判断 msg 是哪一种类型, 分别作出不同的反应
LOGGER.info("[{---关闭---}]");
handshaker.close(ctx.channel(), (CloseWebSocketFrame) msg);
return;
} if(msg instanceof PingWebSocketFrame){
LOGGER.info("[{---ping}]");
PongWebSocketFrame pong = new PongWebSocketFrame(msg.content().retain());
ctx.channel().writeAndFlush(pong);
return;
} if(!(msg instanceof TextWebSocketFrame)){
LOGGER.info("[{!!----不支持二进制-----!!}]");
}
} /**
* websocket 第一次握手
*
* */
public void doHandlerHttpRequest(ChannelHandlerContext ctx, HttpRequest msg){
//http 解码失败
if(!msg.getDecoderResult().isSuccess() || (!"websocket".equals(msg.headers().get("Upgrade")))){
sendHttpResponse(ctx, (FullHttpRequest) msg,
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.BAD_REQUEST));
} //可以获取 msg 的URI来判断
String uri = msg.getUri();
if(!uri.substring(1).equals(URI)){
ctx.close();
}
ctx.attr(AttributeKey.valueOf("type")).set(uri); //通过 URI 获取其他参数验证
WebSocketServerHandshakerFactory factory =
new WebSocketServerHandshakerFactory(
"ws://"+msg.headers().get("Host")+"/"+URI+"",
null,
false);
handshaker = factory.newHandshaker(msg);
if(handshaker == null){
WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
} //进行连接
handshaker.handshake(ctx.channel(), (FullHttpRequest) msg); } /**
* 返回应答给客户端
*
* */
private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res){
if(res.getStatus().code() != 200){
ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
} // 如果是非 keep-alive , 关闭连接
ChannelFuture cf = ctx.channel().writeAndFlush(res);
if (!HttpHeaders.isKeepAlive(req) || res.getStatus().code() != 200) {
cf.addListener(ChannelFutureListener.CLOSE);
}
} /**
* 断开连接
*
* */
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception{
LOGGER.info("handlerRemoved ---->"+ctx.channel());
} }
然后再在系统启动文件的地方开启 启动netty 服务的 线程就可以
import cn.gzserver.basics.network.netty.NettyServer;
import cn.gzserver.basics.network.socket.SocketServer; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.scheduling.annotation.EnableScheduling; /*@ComponentScan*/
@EnableScheduling
@SpringBootApplication
@EnableDiscoveryClient
public class GzserverApplication { public static void main(String[] args) {
SpringApplication.run(GzserverApplication.class, args); //启动 socket 服务, 接收客户端发送连接请求, 并返回数据
/*SocketServer socketServer = new SocketServer();
socketServer.start();*/ //开启 netty 服务
new Thread(() -> {
try {
new NettyServer().bind(5566);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
} }
然后呢, 客户端的配置基本上没有改变, 可以参考我前面写的一篇博客作为参考就行
https://www.cnblogs.com/unityworld/p/11345431.html
但是,还有一些问题, 会在下一篇文章中说明
[经验] Java 使用 netty 框架, 向 Unity 客户端的 C# 实现通信 [1]的更多相关文章
- [经验] Java 使用 netty 框架, 向 Unity 客户端的 C# 实现通信[2]
在前一篇文章中, 我们实现了从Java netty 服务端到 unity 客户端的通讯, 但是在过程中也发现有一些问题是博主苦苦无法解决的, 但是还好终于有些问题还是被我找刀方法解决了, 现在把这些解 ...
- 基于netty框架的Socket传输
一.Netty框架介绍 什么是netty?先看下百度百科的解释: Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开 ...
- [经验] Java 服务端 和 C# 客户端 实现 Socket 通信
由于项目需要, 我需要通过 Java 开发的服务端对 C# 作为脚本语言开发的 unity 项目实现控制 话不多说, 直接上代码 首先, 我们先来构建服务端的代码, 服务端我们使用 Java 语言 i ...
- 架构-Java-Netty:Netty框架
ylbtech-架构-Java-Netty:Netty框架 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网 ...
- Netty入门——客户端与服务端通信
Netty简介Netty是一个基于JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性.换句话说,Netty是一个NIO框架,使用它可以简单快速 ...
- Http 调用netty 服务,服务调用客户端,伪同步响应.ProtoBuf 解决粘包,半包问题.
实际情况是: 公司需要开发一个接口给新产品使用,需求如下 1.有一款硬件设备,客户用usb接上电脑就可以,但是此设备功能比较单一,所以开发一个服务器程序,辅助此设备业务功能 2.解决方案,使用Sock ...
- Java微服务框架一览
引言:本文首先简单介绍了微服务的概念以及使用微服务所能带来的优势,然后结合实例介绍了几个常见的Java微服务框架. 微服务在开发领域的应用越来越广泛,因为开发人员致力于创建更大.更复杂的应用程序,而这 ...
- Netty框架
Netty框架新版本号:3.0.2.GA,于2008年11月19日公布.Netty项目致力于提供一个异步的.事件驱动的网络应用框架和工具,用于高速开发可维护的.高性能的.高扩展性的server和cli ...
- NetCore Netty 框架 BT.Netty.RPC 系列随讲 二 WHO AM I 之 NETTY/NETTY 与 网络通讯 IO 模型之关系?
一:NETTY 是什么? Netty 是什么? 这个问题其实百度上一搜一堆. 这是官方话的描述:Netty 是一个基于NIO的客户.服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个 ...
随机推荐
- 3、示例(在java中使用JSON)
教程链接(json-smple1.1.1.jar文件) 链接:http://pan.baidu.com/s/1qXPbYHm 密码:v0f0 如何使用java编程语言编码和解码JSON 首先准备环境以 ...
- 自定义xmlhttprequest
/** * xhr_proxy.js * 通过劫持原生XMLHttpRequest实现对页面ajax请求的监听 * @author binaryfire */ const READY_STATE_CH ...
- yii2.0 ajax
2.0用的参数是_csrf token = "<?php echo \Yii::$app->request->getCsrfToken()?>", $.aj ...
- 《TCP/IP网络编程》读书笔记
1.Windows 下的 socket 程序和 Linux 思路相同,但细节有所差别(1) Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.dll,必须提前加载 ...
- 深入浅出Oracle:DBA入门、进阶与诊断案例 PDF 下载
网盘地址: 链接:https://pan.baidu.com/s/1tMFoNSUW7ICKOtmSQ5ZazA 提取码:dbnc
- $ git push -u origin master
我们第一次推送master分支时,由于远程库是空的,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来 ...
- set的使用-Hdu 2094
产生冠军 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- unittest---unittest错误截图
在做自动化的过程中,大多数执行者都不在旁边,那么如果用例失败了我们通常看报告上的失败信息,但是这样有时候可能不够清楚的判断到底哪里出了错误,我们还可以通过自动截图的功能,判断用例走到哪里出了错误. 截 ...
- java 事务解释。
面试的时候,面试人员总喜欢问在spring中, 1. 如果一个主线程上有一个事务,在事务中开启了一个线程.子线程跑出异常,对主线程有没有影响,或者主线程产生异常对子线程有没有影响. 这个时候,你只要记 ...
- 《FA分享》---创业学习--训练营直播第二课--HHR
盛沛涵,以太白泽董事 一,基金投资的出发点: 1,这个赛道是否只有头部一两名有机会,如果不是,投的概率更大. 2, 基金投资的判断逻辑: 1.我是不是要在这个赛道布局 2.这个赛道分布如何,有 ...