netty自定义协议

男娘i 2022-06-12 12:45 327阅读 0赞

《netty权威指南》一书中关于自定义协议开发的源码中有一部分错误导致代码无法运行,加了一点改变可以完美运行了,

  1. package nettyAgreement.decoder;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import io.netty.buffer.ByteBuf;
  5. import io.netty.channel.ChannelHandlerContext;
  6. import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
  7. import nettyAgreement.Header;
  8. import nettyAgreement.MarshallingCodecFactory;
  9. import nettyAgreement.NettyMessage;
  10. /**
  11. * 消息解码类
  12. * @author <font color="red"><b>Gong.YiYang</b></font>
  13. * @Date 2017年7月19日
  14. * @Version
  15. * @Description
  16. */
  17. public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder {
  18. NettyMarshallingDecoder marshallingDecoder;
  19. public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
  20. super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
  21. // TODO Auto-generated constructor stub
  22. marshallingDecoder = MarshallingCodecFactory.buMarshallingDecoder();
  23. }
  24. public NettyMessageDecoder(int maxFrameLength,
  25. int lengthFieldOffset, int lengthFieldLength,
  26. int lengthAdjustment, int initialBytesToStrip) {
  27. // TODO Auto-generated constructor stub
  28. super( maxFrameLength,
  29. lengthFieldOffset, lengthFieldLength,
  30. lengthAdjustment, initialBytesToStrip);
  31. marshallingDecoder = MarshallingCodecFactory.buMarshallingDecoder();
  32. }
  33. @Override
  34. protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
  35. // TODO Auto-generated method stub
  36. ByteBuf frame = (ByteBuf)super.decode(ctx, in);
  37. if (frame == null) {
  38. return null;
  39. }
  40. NettyMessage message = new NettyMessage();
  41. Header header = new Header();
  42. header.setCrcCode(frame.readInt());
  43. header.setLength(frame.readInt());
  44. header.setSessionID(frame.readLong());
  45. header.setType(frame.readByte());
  46. header.setPriority(frame.readByte());
  47. int size = frame.readInt();
  48. if (size>0) {
  49. Map<String, Object> attch = new HashMap<String,Object>(size);
  50. int keySize = 0;
  51. byte[] keyArray = null;
  52. String key = null;
  53. for (int i = 0; i < size; i++) {
  54. keySize = frame.readInt();
  55. keyArray = new byte[keySize];
  56. frame.readBytes(keyArray);
  57. key = new String(keyArray, "UTF-8");
  58. attch.put( key, marshallingDecoder.decode(ctx, frame));
  59. }
  60. keyArray = null;
  61. key = null;
  62. header.setAttachment(attch);
  63. }
  64. if (frame.readableBytes() > 4) {
  65. message.setBody(marshallingDecoder.decode(ctx, frame));
  66. }
  67. message.setHeader(header);
  68. return message;
  69. }
  70. }
  71. package nettyAgreement.encoder;
  72. import java.util.List;
  73. import java.util.Map.Entry;
  74. import io.netty.buffer.ByteBuf;
  75. import io.netty.buffer.Unpooled;
  76. import io.netty.channel.ChannelHandlerContext;
  77. import io.netty.handler.codec.MessageToMessageEncoder;
  78. import nettyAgreement.MarshallingCodecFactory;
  79. import nettyAgreement.NettyMessage;
  80. /**
  81. * 消息编码
  82. * @author <font color="red"><b>Gong.YiYang</b></font>
  83. * @Date 2017年7月19日
  84. * @Version
  85. * @Description
  86. */
  87. public class NettyMessageEncoder extends MessageToMessageEncoder<NettyMessage>{
  88. private NettyMarshallingEncoder marshallingEncoder;
  89. public NettyMessageEncoder() {
  90. this.marshallingEncoder = MarshallingCodecFactory.buildMarshallingEncoder();
  91. }
  92. @Override
  93. public void encode(ChannelHandlerContext ctx, NettyMessage msg, List<Object> out) throws Exception {
  94. // TODO Auto-generated method stub
  95. if (msg == null || msg.getHeader() == null) {
  96. throw new Exception("This encode message is null");
  97. }
  98. ByteBuf sendBuffer = Unpooled.buffer();
  99. sendBuffer.writeInt(msg.getHeader().getCrcCode());
  100. sendBuffer.writeInt(msg.getHeader().getLength());
  101. sendBuffer.writeLong(msg.getHeader().getSessionID());
  102. sendBuffer.writeByte(msg.getHeader().getType());
  103. sendBuffer.writeByte(msg.getHeader().getPriority());
  104. sendBuffer.writeInt(msg.getHeader().getAttachment().size());
  105. String key = null;
  106. byte[] keyArray = null;
  107. Object value = null;
  108. for (Entry<String, Object> param : msg.getHeader().getAttachment().entrySet()) {
  109. key = param.getKey();
  110. keyArray = key.getBytes("UTF-8");
  111. sendBuffer.writeInt(keyArray.length);
  112. sendBuffer.writeBytes(keyArray);
  113. value = param.getValue();
  114. marshallingEncoder.encode(ctx, value, sendBuffer);
  115. }
  116. key = null;
  117. keyArray = null;
  118. value = null;
  119. if (msg.getBody() != null) {
  120. marshallingEncoder.encode(ctx, msg.getBody(), sendBuffer);
  121. }else
  122. sendBuffer.writeInt(0);
  123. // 在第4个字节出写入Buffer的长度
  124. int readableBytes = sendBuffer.readableBytes();
  125. sendBuffer.setInt(4, readableBytes);
  126. // 把Message添加到List传递到下一个Handler
  127. out.add(sendBuffer);
  128. }
  129. }
  130. package nettyAgreement;
  131. /**
  132. * 定义消息头
  133. * @author <font color="red"><b>Gong.YiYang</b></font>
  134. * @Date 2017年7月19日
  135. * @Version
  136. * @Description
  137. */
  138. import java.util.HashMap;
  139. import java.util.Map;
  140. public final class Header {
  141. private int crcCode = 0xabef0101;//长度32,
  142. private int length;//消息长度32
  143. private long sessionID;//会话ID64
  144. private byte type;//消息类型8
  145. private byte priority;//消息优先级8
  146. private Map<String, Object> attachment = new HashMap<String,Object>();//附件
  147. public final int getCrcCode() {
  148. return crcCode;
  149. }
  150. public final void setCrcCode(int crcCode) {
  151. this.crcCode = crcCode;
  152. }
  153. public final int getLength() {
  154. return length;
  155. }
  156. public final void setLength(int length) {
  157. this.length = length;
  158. }
  159. public final long getSessionID() {
  160. return sessionID;
  161. }
  162. public final void setSessionID(long sessionID) {
  163. this.sessionID = sessionID;
  164. }
  165. public final byte getType() {
  166. return type;
  167. }
  168. public final void setType(byte type) {
  169. this.type = type;
  170. }
  171. public final byte getPriority() {
  172. return priority;
  173. }
  174. public final void setPriority(byte priority) {
  175. this.priority = priority;
  176. }
  177. public final Map<String, Object> getAttachment() {
  178. return attachment;
  179. }
  180. public final void setAttachment(Map<String, Object> attachment) {
  181. this.attachment = attachment;
  182. }
  183. @Override
  184. public String toString() {
  185. return "Header [crcCode=" + crcCode + ", length=" + length + ", sessionID=" + sessionID + ", type=" + type
  186. + ", priority=" + priority + ", attachment=" + attachment + "]";
  187. }
  188. }
  189. package nettyAgreement;
  190. import java.util.concurrent.TimeUnit;
  191. import io.netty.channel.ChannelHandlerAdapter;
  192. import io.netty.channel.ChannelHandlerContext;
  193. import io.netty.util.concurrent.ScheduledFuture;
  194. import nettyAgreement.Header;
  195. import nettyAgreement.MessageType;
  196. import nettyAgreement.NettyMessage;
  197. /**
  198. * 客户端心跳消息
  199. * @author <font color="red"><b>Gong.YiYang</b></font>
  200. * @Date 2017年7月20日
  201. * @Version
  202. * @Description
  203. */
  204. public class HeartBeatReqHandler extends ChannelHandlerAdapter {
  205. private volatile ScheduledFuture<?> heartBeat;
  206. @Override
  207. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  208. // TODO Auto-generated method stub
  209. NettyMessage message = (NettyMessage)msg;
  210. //握手成功,主动发送心跳消息
  211. if (message.getHeader() != null && message.getHeader().getType() == MessageType.LOGIN_RESP.type) {
  212. heartBeat = ctx.executor().scheduleAtFixedRate(new HeartBeatReqHandler.HeartBeatTask(ctx), 0, 5000, TimeUnit.MILLISECONDS);
  213. }else if (message.getHeader() != null && message.getHeader().getType() == MessageType.HEART_RESP.type) {
  214. System.out.println("Client receive server heart beat message : -->"+message);
  215. }else
  216. ctx.fireChannelRead(msg);
  217. }
  218. //心跳任务
  219. private class HeartBeatTask implements Runnable{
  220. private final ChannelHandlerContext ctx;
  221. public HeartBeatTask(final ChannelHandlerContext ctx) {
  222. this.ctx = ctx;
  223. }
  224. @Override
  225. public void run() {
  226. // TODO Auto-generated method stub
  227. NettyMessage heartBeat = buildHeartBeat();
  228. System.out.println("Client send heart beat message to server : -->"+heartBeat);
  229. ctx.writeAndFlush(heartBeat);
  230. }
  231. private NettyMessage buildHeartBeat(){
  232. NettyMessage message = new NettyMessage();
  233. Header header = new Header();
  234. header.setType(MessageType.HEART_REQ.type);
  235. message.setHeader(header);
  236. return message;
  237. }
  238. }
  239. @Override
  240. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  241. // TODO Auto-generated method stub
  242. if (heartBeat != null) {
  243. heartBeat.cancel(true);
  244. heartBeat=null;
  245. }
  246. ctx.fireExceptionCaught(cause);
  247. }
  248. }
  249. package nettyAgreement;
  250. import io.netty.channel.ChannelHandlerAdapter;
  251. import io.netty.channel.ChannelHandlerContext;
  252. /**
  253. * 服务端心跳消息
  254. * @author <font color="red"><b>Gong.YiYang</b></font>
  255. * @Date 2017年7月20日
  256. * @Version
  257. * @Description
  258. */
  259. public class HeartBeatRespHandler extends ChannelHandlerAdapter {
  260. @Override
  261. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  262. // TODO Auto-generated method stub
  263. NettyMessage message = (NettyMessage)msg;
  264. if (message.getHeader() != null && message.getHeader().getType() == MessageType.HEART_REQ.type) {
  265. System.out.println("Receive client beat message : -->"+message);
  266. NettyMessage heartBeat = buildHeartBeat();
  267. System.out.println("Send heart beat message to client : -->"+heartBeat);
  268. ctx.writeAndFlush(heartBeat);
  269. }else
  270. ctx.fireChannelRead(msg);
  271. }
  272. private NettyMessage buildHeartBeat(){
  273. NettyMessage message = new NettyMessage();
  274. Header header = new Header();
  275. header.setType(MessageType.HEART_RESP.type);
  276. message.setHeader(header);
  277. return message;
  278. }
  279. }
  280. package nettyAgreement;
  281. import io.netty.channel.ChannelHandlerAdapter;
  282. import io.netty.channel.ChannelHandlerContext;
  283. /**
  284. * 客户端握手认证
  285. *
  286. * @author <font color="red"><b>Gong.YiYang</b></font>
  287. * @Date 2017年7月20日
  288. * @Version
  289. * @Description
  290. */
  291. public class LoginAuthReqHandler extends ChannelHandlerAdapter {
  292. @Override
  293. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  294. // TODO Auto-generated method stub
  295. ctx.writeAndFlush(buildLoginReq());
  296. }
  297. @Override
  298. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  299. // TODO Auto-generated method stub
  300. NettyMessage message = (NettyMessage)msg;
  301. //如果是握手应答消息,需要判断是否认证成功
  302. if (message.getHeader() != null && message.getHeader().getType() == MessageType.LOGIN_RESP.type) {
  303. byte loginResult = (byte)message.getBody();
  304. if (loginResult != (byte)0) {
  305. //握手失败,关闭连接;0表示认证通过,握手成功
  306. ctx.close();
  307. }else {
  308. System.out.println("login is ok :"+message);
  309. ctx.fireChannelRead(msg);
  310. }
  311. }else {
  312. ctx.fireChannelRead(msg);
  313. }
  314. }
  315. private NettyMessage buildLoginReq() {
  316. // TODO Auto-generated method stub
  317. NettyMessage message = new NettyMessage();
  318. Header header = new Header();
  319. header.setType(MessageType.LOGIN_REQ.type);
  320. message.setHeader(header);
  321. return message;
  322. }
  323. @Override
  324. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  325. // TODO Auto-generated method stub
  326. ctx.fireExceptionCaught(cause);
  327. }
  328. }
  329. package nettyAgreement;
  330. import java.net.InetSocketAddress;
  331. import java.util.Map;
  332. import java.util.concurrent.ConcurrentHashMap;
  333. import io.netty.channel.ChannelHandlerAdapter;
  334. import io.netty.channel.ChannelHandlerContext;
  335. /**
  336. * 服务端握手请求,安全认证
  337. * @author <font color="red"><b>Gong.YiYang</b></font>
  338. * @Date 2017年7月20日
  339. * @Version
  340. * @Description
  341. */
  342. public class LoginAuthRespHandler extends ChannelHandlerAdapter {
  343. private Map<String, Boolean> nodeCheck = new ConcurrentHashMap<String,Boolean>();
  344. private String[] whiteList = {"127.0.0.1","192.168.1.104"};//白名单
  345. @Override
  346. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  347. // TODO Auto-generated method stub
  348. NettyMessage message = (NettyMessage)msg;
  349. //如果是握手请求消息处理,其它消息透传
  350. if (message.getHeader() != null && message.getHeader().getType() == MessageType.LOGIN_REQ.type) {
  351. String nodeIndex = ctx.channel().remoteAddress().toString();//拿到远程地址
  352. NettyMessage loginResp = null;
  353. //重复登录,拒绝
  354. if (nodeCheck.containsKey(nodeIndex)) {
  355. loginResp = buildResponse((byte)-1);
  356. }else {
  357. InetSocketAddress address = (InetSocketAddress)ctx.channel().remoteAddress();
  358. String ip = address.getAddress().getHostAddress();
  359. boolean isOK = false;
  360. for (String Wip : whiteList) {
  361. if (Wip.equals(ip)) {
  362. isOK = true;
  363. break;
  364. }
  365. }
  366. loginResp = isOK ? buildResponse((byte)0) : buildResponse((byte)-1);
  367. if (isOK)
  368. nodeCheck.put(nodeIndex, true);
  369. ConnectList.list.put(ip, ctx);
  370. }
  371. System.out.println("The login response is:"+loginResp+"body["+loginResp.getBody()+"]");
  372. ctx.writeAndFlush(loginResp);
  373. }else {
  374. ctx.fireChannelRead(msg);
  375. }
  376. }
  377. private NettyMessage buildResponse(byte result) {
  378. // TODO Auto-generated method stub
  379. NettyMessage message = new NettyMessage();
  380. Header header = new Header();
  381. header.setType(MessageType.LOGIN_RESP.type);
  382. message.setHeader(header);
  383. message.setBody(result);
  384. return message;
  385. }
  386. @Override
  387. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  388. // TODO Auto-generated method stub
  389. nodeCheck.remove(ctx.channel().remoteAddress().toString());
  390. ctx.fireExceptionCaught(cause);
  391. }
  392. }
  393. package nettyAgreement;
  394. /**
  395. * 消息类型
  396. * @author <font color="red"><b>Gong.YiYang</b></font>
  397. * @Date 2017年7月20日
  398. * @Version
  399. * @Description
  400. */
  401. public enum MessageType {
  402. BUSINESS_REQ((byte)0,"业务请求消息"),
  403. BUSINESS_RESP((byte)1,"业务应答消息"),
  404. ONE_WAY((byte)2,"业务ONE WAY消息"),
  405. LOGIN_REQ((byte)3,"握手请求消息"),
  406. LOGIN_RESP((byte)4,"握手应答消息"),
  407. HEART_REQ((byte)5,"心跳请求消息"),
  408. HEART_RESP((byte)6,"心跳应答消息");
  409. public byte type;
  410. String describe;
  411. private MessageType(byte type, String describe) {
  412. this.type = type;
  413. this.describe = describe;
  414. }
  415. }
  416. package nettyAgreement;
  417. public class NettyConstant {
  418. public static final String REMOTEIP = "127.0.0.1";
  419. public static final int PORT = 8080;
  420. public static final int LOCAL_PORT = 12088;
  421. public static final String LOCALIP = "127.0.0.1";
  422. }
  423. package nettyAgreement;
  424. /**
  425. * 消息定义
  426. * @author <font color="red"><b>Gong.YiYang</b></font>
  427. * @Date 2017年7月18日
  428. * @Version
  429. * @Description
  430. */
  431. public final class NettyMessage {
  432. private Header header;//消息头
  433. private Object body;//消息体
  434. public final Header getHeader() {
  435. return header;
  436. }
  437. public final void setHeader(Header header) {
  438. this.header = header;
  439. }
  440. public final Object getBody() {
  441. return body;
  442. }
  443. public final void setBody(Object body) {
  444. this.body = body;
  445. }
  446. @Override
  447. public String toString() {
  448. return "NettyMessage [header=" + header + "]";
  449. }
  450. }
  451. package nettyAgreement.client;
  452. /**
  453. * 客户端
  454. * @author <font color="red"><b>Gong.YiYang</b></font>
  455. * @Date 2017年7月21日
  456. * @Version
  457. * @Description
  458. */
  459. import java.net.InetSocketAddress;
  460. import java.util.concurrent.Executors;
  461. import java.util.concurrent.ScheduledExecutorService;
  462. import java.util.concurrent.TimeUnit;
  463. import io.netty.bootstrap.Bootstrap;
  464. import io.netty.channel.ChannelFuture;
  465. import io.netty.channel.ChannelInitializer;
  466. import io.netty.channel.ChannelOption;
  467. import io.netty.channel.EventLoopGroup;
  468. import io.netty.channel.nio.NioEventLoopGroup;
  469. import io.netty.channel.socket.SocketChannel;
  470. import io.netty.channel.socket.nio.NioSocketChannel;
  471. import io.netty.handler.timeout.ReadTimeoutHandler;
  472. import nettyAgreement.HeartBeatReqHandler;
  473. import nettyAgreement.LoginAuthReqHandler;
  474. import nettyAgreement.NettyConstant;
  475. import nettyAgreement.decoder.NettyMessageDecoder;
  476. import nettyAgreement.encoder.NettyMessageEncoder;
  477. public class NettyClient {
  478. private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  479. EventLoopGroup group = new NioEventLoopGroup();
  480. public void connect(int port,String host) throws InterruptedException{
  481. try {
  482. Bootstrap b = new Bootstrap();
  483. b.group(group)
  484. .channel(NioSocketChannel.class)
  485. .option(ChannelOption.TCP_NODELAY, true)
  486. .handler(new ChannelInitializer<SocketChannel>() {
  487. @Override
  488. protected void initChannel(SocketChannel ch) throws Exception {
  489. // TODO Auto-generated method stub
  490. ch.pipeline().addLast(new NettyMessageDecoder(1024 * 1024, 4, 4,-8,0));
  491. ch.pipeline().addLast("MessageEncoder",new NettyMessageEncoder());
  492. ch.pipeline().addLast("readTimeOutHandler",new ReadTimeoutHandler(50));
  493. ch.pipeline().addLast("LoginAuthHandler",new LoginAuthReqHandler());
  494. ch.pipeline().addLast("HeartBeatHandler",new HeartBeatReqHandler());
  495. }
  496. });
  497. //发起异步连接操作
  498. ChannelFuture future = b.connect(
  499. new InetSocketAddress(host, port),
  500. new InetSocketAddress(NettyConstant.LOCALIP, NettyConstant.LOCAL_PORT)).sync();
  501. future.channel().closeFuture().sync();
  502. }
  503. finally {
  504. //所有资源释放完成之后,清空资源,再次发起重连操作
  505. executor.execute(new Runnable() {
  506. @Override
  507. public void run() {
  508. // TODO Auto-generated method stub
  509. try {
  510. TimeUnit.SECONDS.sleep(5);
  511. try {
  512. connect(NettyConstant.PORT, NettyConstant.REMOTEIP);//发起重连操作
  513. } catch (Exception e) {
  514. // TODO: handle exception
  515. e.printStackTrace();
  516. }
  517. } catch (InterruptedException e) {
  518. // TODO: handle exception
  519. e.printStackTrace();
  520. }
  521. }
  522. });
  523. }
  524. }
  525. public static void main(String[] args) throws InterruptedException {
  526. new NettyClient().connect(NettyConstant.PORT, NettyConstant.REMOTEIP);
  527. }
  528. }
  529. package nettyAgreement.server;
  530. import io.netty.bootstrap.ServerBootstrap;
  531. import io.netty.channel.ChannelFuture;
  532. import io.netty.channel.ChannelInitializer;
  533. import io.netty.channel.ChannelOption;
  534. import io.netty.channel.nio.NioEventLoopGroup;
  535. import io.netty.channel.socket.SocketChannel;
  536. import io.netty.channel.socket.nio.NioServerSocketChannel;
  537. import io.netty.handler.logging.LogLevel;
  538. import io.netty.handler.logging.LoggingHandler;
  539. import io.netty.handler.timeout.ReadTimeoutHandler;
  540. import nettyAgreement.HeartBeatRespHandler;
  541. import nettyAgreement.LoginAuthRespHandler;
  542. import nettyAgreement.NettyConstant;
  543. import nettyAgreement.decoder.NettyMessageDecoder;
  544. import nettyAgreement.encoder.NettyMessageEncoder;
  545. /**
  546. * 服务端
  547. * @author <font color="red"><b>Gong.YiYang</b></font>
  548. * @Date 2017年7月21日
  549. * @Version
  550. * @Description
  551. */
  552. public class NettyServer {
  553. public void build() throws InterruptedException{
  554. NioEventLoopGroup bossGroup = new NioEventLoopGroup();
  555. NioEventLoopGroup workGroup = new NioEventLoopGroup();
  556. try {
  557. ServerBootstrap b = new ServerBootstrap();
  558. b.group(bossGroup, workGroup)
  559. .channel(NioServerSocketChannel.class)
  560. .option(ChannelOption.SO_BACKLOG, 100)
  561. .handler(new LoggingHandler(LogLevel.INFO))
  562. .childHandler(new ChannelInitializer<SocketChannel>() {
  563. @Override
  564. protected void initChannel(SocketChannel ch) throws Exception {
  565. // TODO Auto-generated method stub
  566. ch.pipeline().addLast(new NettyMessageDecoder(1024 * 1024, 4, 4,-8,0));
  567. ch.pipeline().addLast(new NettyMessageEncoder());
  568. ch.pipeline().addLast("readTimeOutHandler",new ReadTimeoutHandler(50));
  569. ch.pipeline().addLast(new LoginAuthRespHandler());
  570. ch.pipeline().addLast("HeartBeatHandler",new HeartBeatRespHandler());
  571. }
  572. });
  573. //绑定端口,等待同步
  574. ChannelFuture future = b.bind(NettyConstant.PORT).sync();
  575. System.out.println("Netty server start ok :"+(NettyConstant.REMOTEIP+":"+NettyConstant.PORT));
  576. future.channel().closeFuture().sync();
  577. } finally{
  578. bossGroup.shutdownGracefully();
  579. workGroup.shutdownGracefully();
  580. }
  581. }
  582. public static void main(String[] args) throws InterruptedException {
  583. new NettyServer().build();
  584. }
  585. }

发表评论

表情:
评论列表 (有 0 条评论,327人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Netty实现定义协议

    关于协议,使用最为广泛的是HTTP协议,但是在一些服务交互领域,其使用则相对较少,主要原因有三方面: HTTP协议会携带诸如header和cookie等信息,其本身对字

    相关 netty定义协议

    《netty权威指南》一书中关于自定义协议开发的源码中有一部分错误导致代码无法运行,加了一点改变可以完美运行了, package nettyAgreement.dec