├── tcp-server.iml ├── target └── classes │ ├── Main.class │ ├── pojo │ └── User.class │ ├── org │ └── wisdom │ │ ├── client │ │ ├── TcpClient.class │ │ ├── TcpClient$1.class │ │ └── echo │ │ │ ├── EchoHandler.class │ │ │ ├── EchoHandler_2_0.class │ │ │ └── EchoHandler_3_0.class │ │ ├── server │ │ ├── TcpServer.class │ │ ├── TcpServer$1.class │ │ ├── business │ │ │ ├── BusinessHandler.class │ │ │ ├── BusinessHandler_2_0.class │ │ │ └── BusinessHandler_3_0.class │ │ ├── decoder │ │ │ ├── DecoderHandler.class │ │ │ ├── DecoderHandler_2_0.class │ │ │ └── DecoderHandler_3_0.class │ │ └── encoder │ │ │ ├── EncoderHandler.class │ │ │ ├── EncoderHandler_2_0.class │ │ │ └── EncoderHandler_3_0.class │ │ ├── utils │ │ ├── ByteUtils.class │ │ ├── ProtocolUtils.class │ │ └── ProtocolUtils_2_0.class │ │ ├── protocol │ │ ├── TcpProtocol.class │ │ ├── TcpProtocol_2_0.class │ │ └── TcpProtocol_3_0.class │ │ └── dataTransefer │ │ ├── DTObject.class │ │ └── DTObject_2_0.class │ └── log4j.properties ├── .idea ├── misc.xml ├── compiler.xml ├── uiDesigner.xml └── workspace.xml ├── src └── main │ ├── resources │ └── log4j.properties │ └── java │ ├── org │ └── wisdom │ │ ├── dataTransefer │ │ ├── DTObject.java │ │ └── DTObject_2_0.java │ │ ├── server │ │ ├── encoder │ │ │ ├── EncoderHandler.java │ │ │ ├── EncoderHandler_2_0.java │ │ │ └── EncoderHandler_3_0.java │ │ ├── business │ │ │ ├── BusinessHandler_2_0.java │ │ │ ├── BusinessHandler_3_0.java │ │ │ └── BusinessHandler.java │ │ ├── decoder │ │ │ ├── DecoderHandler.java │ │ │ ├── DecoderHandler_2_0.java │ │ │ └── DecoderHandler_3_0.java │ │ └── TcpServer.java │ │ ├── protocol │ │ ├── TcpProtocol_2_0.java │ │ ├── TcpProtocol.java │ │ └── TcpProtocol_3_0.java │ │ ├── utils │ │ ├── ByteUtils.java │ │ ├── ProtocolUtils.java │ │ └── ProtocolUtils_2_0.java │ │ └── client │ │ ├── echo │ │ ├── EchoHandler_3_0.java │ │ ├── EchoHandler_2_0.java │ │ └── EchoHandler.java │ │ └── TcpClient.java │ ├── Main.java │ └── pojo │ └── User.java ├── pom.xml ├── README.md └── logs └── notify-subscription.log /tcp-server.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /target/classes/Main.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/Main.class -------------------------------------------------------------------------------- /target/classes/pojo/User.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/pojo/User.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/client/TcpClient.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/client/TcpClient.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/TcpServer.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/TcpServer.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/utils/ByteUtils.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/utils/ByteUtils.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/client/TcpClient$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/client/TcpClient$1.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/TcpServer$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/TcpServer$1.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/protocol/TcpProtocol.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/protocol/TcpProtocol.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/utils/ProtocolUtils.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/utils/ProtocolUtils.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/client/echo/EchoHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/client/echo/EchoHandler.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/dataTransefer/DTObject.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/dataTransefer/DTObject.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/utils/ProtocolUtils_2_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/utils/ProtocolUtils_2_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/dataTransefer/DTObject_2_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/dataTransefer/DTObject_2_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/protocol/TcpProtocol_2_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/protocol/TcpProtocol_2_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/protocol/TcpProtocol_3_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/protocol/TcpProtocol_3_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/client/echo/EchoHandler_2_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/client/echo/EchoHandler_2_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/client/echo/EchoHandler_3_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/client/echo/EchoHandler_3_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/business/BusinessHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/business/BusinessHandler.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/decoder/DecoderHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/decoder/DecoderHandler.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/encoder/EncoderHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/encoder/EncoderHandler.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/decoder/DecoderHandler_2_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/decoder/DecoderHandler_2_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/decoder/DecoderHandler_3_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/decoder/DecoderHandler_3_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/encoder/EncoderHandler_2_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/encoder/EncoderHandler_2_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/encoder/EncoderHandler_3_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/encoder/EncoderHandler_3_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/business/BusinessHandler_2_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/business/BusinessHandler_2_0.class -------------------------------------------------------------------------------- /target/classes/org/wisdom/server/business/BusinessHandler_3_0.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siwash/netty_TCP/HEAD/target/classes/org/wisdom/server/business/BusinessHandler_3_0.class -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /target/classes/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=debug, ServerDailyRollingFile, stdout 2 | log4j.appender.ServerDailyRollingFile=org.apache.log4j.DailyRollingFileAppender 3 | log4j.appender.ServerDailyRollingFile.DatePattern='.'yyyy-MM-dd 4 | log4j.appender.ServerDailyRollingFile.File=logs/notify-subscription.log 5 | log4j.appender.ServerDailyRollingFile.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.ServerDailyRollingFile.layout.ConversionPattern=%d - %m%n 7 | log4j.appender.ServerDailyRollingFile.Append=true 8 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 9 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 10 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} %p [%c] %m%n 11 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=debug, ServerDailyRollingFile, stdout 2 | log4j.appender.ServerDailyRollingFile=org.apache.log4j.DailyRollingFileAppender 3 | log4j.appender.ServerDailyRollingFile.DatePattern='.'yyyy-MM-dd 4 | log4j.appender.ServerDailyRollingFile.File=logs/notify-subscription.log 5 | log4j.appender.ServerDailyRollingFile.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.ServerDailyRollingFile.layout.ConversionPattern=%d - %m%n 7 | log4j.appender.ServerDailyRollingFile.Append=true 8 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 9 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 10 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} %p [%c] %m%n 11 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/dataTransefer/DTObject.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.dataTransefer; 2 | 3 | import java.util.Arrays; 4 | 5 | /**通过全类名字符串解析成具体的对象 6 | * **/ 7 | public class DTObject { 8 | private String className; 9 | private byte[] object; 10 | 11 | @Override 12 | public String toString() { 13 | return "DTObject{" + 14 | "className='" + className + '\'' + 15 | ", object=" + Arrays.toString(object) + 16 | '}'; 17 | } 18 | 19 | public String getClassName() { 20 | return className; 21 | } 22 | 23 | public void setClassName(String className) { 24 | this.className = className; 25 | } 26 | 27 | public byte[] getObject() { 28 | return object; 29 | } 30 | 31 | public void setObject(byte[] object) { 32 | this.object = object; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/server/encoder/EncoderHandler.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.server.encoder; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | import org.apache.log4j.Logger; 7 | import org.wisdom.protocol.TcpProtocol; 8 | 9 | public class EncoderHandler extends MessageToByteEncoder { 10 | private Logger logger = Logger.getLogger(this.getClass()); 11 | protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { 12 | if (msg instanceof TcpProtocol){ 13 | TcpProtocol protocol = (TcpProtocol) msg; 14 | out.writeByte(protocol.getHeader()); 15 | out.writeInt(protocol.getLen()); 16 | out.writeBytes(protocol.getData()); 17 | out.writeByte(protocol.getTail()); 18 | logger.debug("数据编码成功:"+out); 19 | }else { 20 | logger.info("不支持的数据协议:"+msg.getClass()+"\t期待的数据协议类是:"+TcpProtocol.class); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/dataTransefer/DTObject_2_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.dataTransefer; 2 | 3 | import java.util.Arrays; 4 | 5 | /**通过全类名字符串解析成具体的对象 6 | * **/ 7 | public class DTObject_2_0 { 8 | private String className; 9 | private T object; 10 | 11 | public DTObject_2_0(String className, T object) { 12 | this.className = className; 13 | this.object = object; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return "DTObject_2_0{" + 19 | "className='" + className + '\'' + 20 | ", object=" + object + 21 | '}'; 22 | } 23 | 24 | public DTObject_2_0() { 25 | } 26 | 27 | public String getClassName() { 28 | return className; 29 | } 30 | 31 | public void setClassName(String className) { 32 | this.className = className; 33 | } 34 | 35 | public Object getObject() { 36 | return object; 37 | } 38 | 39 | public void setObject(T object) { 40 | this.object = object; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/server/encoder/EncoderHandler_2_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.server.encoder; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | import org.apache.log4j.Logger; 7 | import org.wisdom.protocol.TcpProtocol_2_0; 8 | import org.wisdom.protocol.TcpProtocol_3_0; 9 | 10 | public class EncoderHandler_2_0 extends MessageToByteEncoder { 11 | private Logger logger = Logger.getLogger(this.getClass()); 12 | protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { 13 | if (msg instanceof TcpProtocol_2_0){ 14 | TcpProtocol_2_0 protocol = (TcpProtocol_2_0) msg; 15 | out.writeByte(protocol.getHeader()); 16 | out.writeByte(protocol.getType()); 17 | out.writeInt(protocol.getLen()); 18 | out.writeBytes(protocol.getData()); 19 | out.writeByte(protocol.getTail()); 20 | logger.debug("数据编码成功:"+out); 21 | }else { 22 | logger.info("不支持的数据协议:"+msg.getClass()+"\t期待的数据协议类是:"+ TcpProtocol_2_0.class); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | import com.fasterxml.jackson.core.JsonProcessingException; 2 | import com.fasterxml.jackson.databind.ObjectMapper; 3 | import org.wisdom.utils.ByteUtils; 4 | import pojo.User; 5 | 6 | import java.io.IOException; 7 | import java.util.Date; 8 | import java.util.UUID; 9 | 10 | public class Main { 11 | public static void main(String[] args) throws ClassNotFoundException { 12 | User user = new User(); 13 | user.setAge(20); 14 | user.setName("mrfox"); 15 | user.setUID(UUID.randomUUID().toString()); 16 | user.setBirthday(new Date()); 17 | System.out.println(User.class.getName()); 18 | Class type=Class.forName(User.class.getName()); 19 | ObjectMapper objectMapper = ByteUtils.InstanceObjectMapper(); 20 | try { 21 | String value = objectMapper.writeValueAsString(user); 22 | System.out.println(value); 23 | System.out.println(objectMapper.readValue(value.getBytes(),type)); 24 | } catch (JsonProcessingException e) { 25 | e.printStackTrace(); 26 | } catch (IOException e) { 27 | e.printStackTrace(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/server/business/BusinessHandler_2_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.server.business; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import org.apache.log4j.Logger; 8 | import org.wisdom.dataTransefer.DTObject_2_0; 9 | import org.wisdom.utils.ByteUtils; 10 | import pojo.User; 11 | 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public class BusinessHandler_2_0 extends ChannelInboundHandlerAdapter { 16 | private ObjectMapper objectMapper= ByteUtils.InstanceObjectMapper(); 17 | private Logger logger = Logger.getLogger(this.getClass()); 18 | @Override 19 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 20 | if (msg instanceof List){ 21 | logger.info("这是一个List:"+(List)msg); 22 | }else if (msg instanceof Map){ 23 | logger.info("这是一个Map:"+(Map)msg); 24 | }else{ 25 | logger.info("这是一个对象:"+msg.getClass().getName()); 26 | logger.info("这是一个对象:"+msg); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/server/business/BusinessHandler_3_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.server.business; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import org.apache.log4j.Logger; 8 | import org.wisdom.dataTransefer.DTObject_2_0; 9 | import org.wisdom.utils.ByteUtils; 10 | import pojo.User; 11 | 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public class BusinessHandler_3_0 extends ChannelInboundHandlerAdapter { 16 | private ObjectMapper objectMapper= ByteUtils.InstanceObjectMapper(); 17 | private Logger logger = Logger.getLogger(this.getClass()); 18 | @Override 19 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 20 | if (msg instanceof List){ 21 | logger.info("这是一个List:"+(List)msg); 22 | }else if (msg instanceof Map){ 23 | logger.info("这是一个Map:"+(Map)msg); 24 | }else{ 25 | logger.info("这是一个对象:"+msg.getClass().getName()); 26 | logger.info("这是一个对象:"+msg); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/protocol/TcpProtocol_2_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.protocol; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | *type 0x51 0x52 0x53 7 | * mean: object list map 8 | * */ 9 | public class TcpProtocol_2_0 { 10 | private byte header=0x58; 11 | private byte type; 12 | private int len; 13 | private byte [] data; 14 | private byte tail=0x63; 15 | 16 | public byte getHeader() { 17 | return header; 18 | } 19 | 20 | public void setHeader(byte header) { 21 | this.header = header; 22 | } 23 | 24 | public byte getType() { 25 | return type; 26 | } 27 | 28 | public void setType(byte type) { 29 | this.type = type; 30 | } 31 | 32 | public int getLen() { 33 | return len; 34 | } 35 | 36 | public void setLen(int len) { 37 | this.len = len; 38 | } 39 | 40 | public byte[] getData() { 41 | return data; 42 | } 43 | 44 | public void setData(byte[] data) { 45 | this.data = data; 46 | } 47 | 48 | public byte getTail() { 49 | return tail; 50 | } 51 | 52 | public void setTail(byte tail) { 53 | this.tail = tail; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/server/encoder/EncoderHandler_3_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.server.encoder; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | import org.apache.log4j.Logger; 7 | import org.wisdom.protocol.TcpProtocol_3_0; 8 | 9 | public class EncoderHandler_3_0 extends MessageToByteEncoder { 10 | private Logger logger = Logger.getLogger(this.getClass()); 11 | protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { 12 | if (msg instanceof TcpProtocol_3_0){ 13 | TcpProtocol_3_0 protocol = (TcpProtocol_3_0) msg; 14 | out.writeByte(protocol.getHeader()); 15 | out.writeByte(protocol.getType()); 16 | out.writeByte(protocol.getClassLen()); 17 | out.writeInt(protocol.getLen()); 18 | out.writeBytes(protocol.getClassName()); 19 | out.writeBytes(protocol.getData()); 20 | out.writeByte(protocol.getTail()); 21 | logger.debug("数据编码成功:"+out); 22 | }else { 23 | logger.info("不支持的数据协议:"+msg.getClass()+"\t期待的数据协议类是:"+ TcpProtocol_3_0.class); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/pojo/User.java: -------------------------------------------------------------------------------- 1 | package pojo; 2 | 3 | import io.netty.channel.group.ChannelGroup; 4 | 5 | import java.util.Date; 6 | 7 | public class User { 8 | private String name; 9 | private int age; 10 | private String UID; 11 | private Date birthday; 12 | 13 | @Override 14 | public String toString() { 15 | return "User{" + 16 | "name='" + name + '\'' + 17 | ", age=" + age + 18 | ", UID='" + UID + '\'' + 19 | ", birthday=" + birthday + 20 | '}'; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public void setName(String name) { 28 | this.name = name; 29 | } 30 | 31 | public int getAge() { 32 | return age; 33 | } 34 | 35 | public void setAge(int age) { 36 | this.age = age; 37 | } 38 | 39 | public String getUID() { 40 | return UID; 41 | } 42 | 43 | public void setUID(String UID) { 44 | this.UID = UID; 45 | } 46 | 47 | public Date getBirthday() { 48 | return birthday; 49 | } 50 | 51 | public void setBirthday(Date birthday) { 52 | this.birthday = birthday; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/utils/ByteUtils.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.utils; 2 | 3 | import com.fasterxml.jackson.databind.DeserializationConfig; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | import java.text.SimpleDateFormat; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.TimeZone; 10 | 11 | public class ByteUtils { 12 | private static ObjectMapper objectMapper=new ObjectMapper(); 13 | private static List datePattern=new ArrayList(); 14 | static { 15 | datePattern.add("yyyy-MM-dd hh/mm/ss"); 16 | datePattern.add("yyyy-MM-dd hh:mm:ss"); 17 | datePattern.add("yyyy/MM/dd hh:mm:dd"); 18 | } 19 | static { 20 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(); 21 | DeserializationConfig config=objectMapper.getDeserializationConfig(); 22 | for (String s : datePattern) { 23 | objectMapper.setDateFormat(new SimpleDateFormat(s)); 24 | } 25 | objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); 26 | } 27 | public static ObjectMapper InstanceObjectMapper(){ 28 | return objectMapper; 29 | } 30 | public List getDatePattern() { 31 | return datePattern; 32 | } 33 | 34 | public void setDatePattern(List datePattern) { 35 | this.datePattern = datePattern; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/client/echo/EchoHandler_3_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.client.echo; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import org.wisdom.protocol.TcpProtocol_3_0; 7 | import org.wisdom.utils.ProtocolUtils; 8 | import pojo.User; 9 | 10 | import java.util.*; 11 | 12 | public class EchoHandler_3_0 extends ChannelInboundHandlerAdapter { 13 | 14 | //连接成功后发送消息测试 15 | @Override 16 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 17 | User user = new User(); 18 | user.setBirthday(new Date()); 19 | user.setUID(UUID.randomUUID().toString()); 20 | user.setName("冉鹏峰"); 21 | user.setAge(24); 22 | Map map=new HashMap<>(); 23 | map.put("数据一",user); 24 | List users=new ArrayList<>(); 25 | users.add(user); 26 | TcpProtocol_3_0 protocol = ProtocolUtils.prtclInstance(map,user.getClass().getName()); 27 | //传map 28 | ctx.write(protocol);//由于设置了编码器,这里直接传入自定义的对象 29 | ctx.flush(); 30 | //传list 31 | ctx.write(ProtocolUtils.prtclInstance(users,user.getClass().getName())); 32 | ctx.flush(); 33 | //传单一实体 34 | ctx.write(ProtocolUtils.prtclInstance(user)); 35 | ctx.flush(); 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/protocol/TcpProtocol.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.protocol; 2 | 3 | import java.util.Arrays; 4 | 5 | public class TcpProtocol { 6 | private byte header=0x58; 7 | private int len; 8 | private byte [] data; 9 | private byte tail=0x63; 10 | 11 | public byte getTail() { 12 | return tail; 13 | } 14 | 15 | public void setTail(byte tail) { 16 | this.tail = tail; 17 | } 18 | 19 | public TcpProtocol(int len, byte[] data) { 20 | this.len = len; 21 | this.data = data; 22 | } 23 | 24 | public TcpProtocol() { 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "TcpProtocol{" + 30 | "header=" + header + 31 | ", len=" + len + 32 | ", data=" + Arrays.toString(data) + 33 | ", tail=" + tail + 34 | '}'; 35 | } 36 | 37 | public byte getHeader() { 38 | return header; 39 | } 40 | 41 | public void setHeader(byte header) { 42 | this.header = header; 43 | } 44 | 45 | public int getLen() { 46 | return len; 47 | } 48 | 49 | public void setLen(int len) { 50 | this.len = len; 51 | } 52 | 53 | public byte[] getData() { 54 | return data; 55 | } 56 | 57 | public void setData(byte[] data) { 58 | this.data = data; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/client/echo/EchoHandler_2_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.client.echo; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import org.wisdom.dataTransefer.DTObject_2_0; 7 | import org.wisdom.protocol.TcpProtocol; 8 | import org.wisdom.protocol.TcpProtocol_2_0; 9 | import org.wisdom.utils.ByteUtils; 10 | import org.wisdom.utils.ProtocolUtils_2_0; 11 | import pojo.User; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Date; 15 | import java.util.HashMap; 16 | import java.util.UUID; 17 | 18 | public class EchoHandler_2_0 extends ChannelInboundHandlerAdapter { 19 | 20 | //连接成功后发送消息测试 21 | @Override 22 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 23 | User user = new User(); 24 | user.setBirthday(new Date()); 25 | user.setUID(UUID.randomUUID().toString()); 26 | user.setName("冉鹏峰"); 27 | user.setAge(24); 28 | HashMap map = new HashMap<>(); 29 | map.put("数据一",user); 30 | map.put("数据2",user); 31 | map.put("数据3",user); 32 | ArrayList list = new ArrayList<>(); 33 | list.add(user); 34 | list.add(user); 35 | list.add(user); 36 | list.add(user); 37 | TcpProtocol_2_0 tcpProtocol= ProtocolUtils_2_0.prtclInstance(map,user.getClass().getName()); 38 | ctx.write(tcpProtocol); 39 | ctx.flush(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/server/business/BusinessHandler.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.server.business; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import org.apache.log4j.Logger; 7 | import org.wisdom.dataTransefer.DTObject; 8 | import org.wisdom.protocol.TcpProtocol; 9 | import org.wisdom.utils.ByteUtils; 10 | 11 | public class BusinessHandler extends ChannelInboundHandlerAdapter { 12 | private ObjectMapper objectMapper= ByteUtils.InstanceObjectMapper(); 13 | private Logger logger = Logger.getLogger(this.getClass()); 14 | @Override 15 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 16 | if (msg instanceof byte []){ 17 | logger.debug("解码后的字节码:"+new String((byte[]) msg,"UTF-8")); 18 | try { 19 | Object objectContainer = objectMapper.readValue((byte[]) msg, DTObject.class); 20 | if (objectContainer instanceof DTObject){ 21 | DTObject data = (DTObject) objectContainer; 22 | if (data.getClassName()!=null&&data.getObject().length>0){ 23 | Object object = objectMapper.readValue(data.getObject(), Class.forName(data.getClassName())); 24 | logger.info("收到实体对象:"+object); 25 | } 26 | } 27 | }catch (Exception e){ 28 | logger.info("对象反序列化出现问题:"+e); 29 | } 30 | 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.wisdom.netty 8 | tcp-server 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-compiler-plugin 15 | 16 | 7 17 | 7 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | io.netty 26 | netty-all 27 | 4.1.6.Final 28 | 29 | 30 | 31 | com.fasterxml.jackson.core 32 | jackson-databind 33 | 2.9.7 34 | 35 | 36 | 37 | log4j 38 | log4j 39 | 1.2.17 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/utils/ProtocolUtils.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.utils; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import org.wisdom.protocol.TcpProtocol_3_0; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public class ProtocolUtils { 10 | public final static byte OBJ_TYPE=0x51; 11 | public final static byte LIST_TYPE=0x52; 12 | public final static byte MAP_TYPE=0x53; 13 | /** 14 | * 创建集合类list map对象 15 | * */ 16 | public static TcpProtocol_3_0 prtclInstance(Object o, String className){ 17 | TcpProtocol_3_0 protocol = new TcpProtocol_3_0(); 18 | if (o instanceof List){ 19 | protocol.setType(LIST_TYPE); 20 | }else if (o instanceof Map){ 21 | protocol.setType(MAP_TYPE); 22 | }else if (o instanceof Object){ 23 | protocol.setType(OBJ_TYPE); 24 | } 25 | initProtocol(o, className, protocol); 26 | 27 | return protocol; 28 | } 29 | /*** 30 | * 31 | * 创建单一的对象 32 | */ 33 | public static TcpProtocol_3_0 prtclInstance(Object o){ 34 | TcpProtocol_3_0 protocol = new TcpProtocol_3_0(); 35 | protocol.setType(OBJ_TYPE); 36 | initProtocol(o, o.getClass().getName(), protocol); 37 | 38 | return protocol; 39 | } 40 | 41 | private static void initProtocol(Object o, String className, TcpProtocol_3_0 protocol) { 42 | byte [] classBytes=className.getBytes(); 43 | try { 44 | byte [] objectBytes= ByteUtils.InstanceObjectMapper().writeValueAsBytes(o); 45 | protocol.setClassLen((byte) classBytes.length); 46 | protocol.setLen(objectBytes.length); 47 | protocol.setData(objectBytes); 48 | protocol.setClassName(classBytes); 49 | } catch (JsonProcessingException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/utils/ProtocolUtils_2_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.utils; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import org.wisdom.dataTransefer.DTObject; 5 | import org.wisdom.protocol.TcpProtocol_2_0; 6 | import org.wisdom.protocol.TcpProtocol_3_0; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | public class ProtocolUtils_2_0 { 12 | public final static byte OBJ_TYPE=0x51; 13 | public final static byte LIST_TYPE=0x52; 14 | public final static byte MAP_TYPE=0x53; 15 | /** 16 | * 创建集合类list map对象 17 | * */ 18 | public static TcpProtocol_2_0 prtclInstance(Object o, String className){ 19 | TcpProtocol_2_0 protocol = new TcpProtocol_2_0(); 20 | if (o instanceof List){ 21 | protocol.setType(LIST_TYPE); 22 | }else if (o instanceof Map){ 23 | protocol.setType(MAP_TYPE); 24 | }else if (o instanceof Object){ 25 | protocol.setType(OBJ_TYPE); 26 | } 27 | initProtocol(o, className, protocol); 28 | 29 | return protocol; 30 | } 31 | /*** 32 | * 33 | * 创建单一的对象 34 | */ 35 | public static TcpProtocol_2_0 prtclInstance(Object o){ 36 | TcpProtocol_2_0 protocol = new TcpProtocol_2_0(); 37 | protocol.setType(OBJ_TYPE); 38 | initProtocol(o, o.getClass().getName(), protocol); 39 | 40 | return protocol; 41 | } 42 | 43 | private static void initProtocol(Object o, String className, TcpProtocol_2_0 protocol) { 44 | try { 45 | DTObject dtObject = new DTObject(); 46 | byte [] objectBytes= ByteUtils.InstanceObjectMapper().writeValueAsBytes(o); 47 | dtObject.setObject(objectBytes); 48 | dtObject.setClassName(className); 49 | byte[] bytes = ByteUtils.InstanceObjectMapper().writeValueAsBytes(dtObject); 50 | protocol.setLen(bytes.length); 51 | protocol.setData(bytes); 52 | } catch (JsonProcessingException e) { 53 | e.printStackTrace(); 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/client/TcpClient.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.client; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.ChannelOption; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.nio.NioSocketChannel; 10 | import io.netty.handler.logging.LoggingHandler; 11 | import org.wisdom.client.echo.EchoHandler; 12 | import org.wisdom.client.echo.EchoHandler_2_0; 13 | import org.wisdom.client.echo.EchoHandler_3_0; 14 | import org.wisdom.server.encoder.EncoderHandler_2_0; 15 | import org.wisdom.server.encoder.EncoderHandler_3_0; 16 | 17 | public class TcpClient { 18 | 19 | private String ip; 20 | private int port; 21 | public void init() throws InterruptedException { 22 | NioEventLoopGroup group = new NioEventLoopGroup(); 23 | try { 24 | Bootstrap bootstrap = new Bootstrap(); 25 | bootstrap.group(group); 26 | bootstrap.channel(NioSocketChannel.class); 27 | bootstrap.option(ChannelOption.SO_KEEPALIVE,true); 28 | bootstrap.handler(new ChannelInitializer() { 29 | @Override 30 | protected void initChannel(Channel ch) throws Exception { 31 | ch.pipeline().addLast("logging",new LoggingHandler("DEBUG")); 32 | ch.pipeline().addLast(new EncoderHandler_3_0()); 33 | ch.pipeline().addLast(new EchoHandler_3_0()); 34 | } 35 | }); 36 | bootstrap.remoteAddress(ip,port); 37 | ChannelFuture future = bootstrap.connect().sync(); 38 | 39 | future.channel().closeFuture().sync(); 40 | } catch (InterruptedException e) { 41 | e.printStackTrace(); 42 | }finally { 43 | group.shutdownGracefully().sync(); 44 | } 45 | } 46 | 47 | public TcpClient(String ip, int port) { 48 | this.ip = ip; 49 | this.port = port; 50 | } 51 | 52 | public static void main(String[] args) throws InterruptedException { 53 | new TcpClient("127.0.0.1",8777).init(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/client/echo/EchoHandler.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.client.echo; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import org.wisdom.dataTransefer.DTObject; 7 | import org.wisdom.protocol.TcpProtocol; 8 | import org.wisdom.utils.ByteUtils; 9 | import pojo.User; 10 | 11 | import java.util.Arrays; 12 | import java.util.Date; 13 | import java.util.UUID; 14 | 15 | public class EchoHandler extends ChannelInboundHandlerAdapter { 16 | 17 | //连接成功后发送消息测试 18 | @Override 19 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 20 | User user = new User(); 21 | user.setBirthday(new Date()); 22 | user.setUID(UUID.randomUUID().toString()); 23 | user.setName("冉鹏峰"); 24 | user.setAge(24); 25 | DTObject dtObject = new DTObject(); 26 | dtObject.setClassName(user.getClass().getName()); 27 | dtObject.setObject(ByteUtils.InstanceObjectMapper().writeValueAsBytes(user)); 28 | TcpProtocol tcpProtocol = new TcpProtocol(); 29 | byte [] objectBytes=ByteUtils.InstanceObjectMapper().writeValueAsBytes(dtObject); 30 | tcpProtocol.setLen(objectBytes.length); 31 | tcpProtocol.setData(objectBytes); 32 | ByteBuf buffer = ctx.alloc().buffer(); 33 | buffer.writeByte(tcpProtocol.getHeader()); 34 | buffer.writeInt(tcpProtocol.getLen()); 35 | buffer.writeBytes(Arrays.copyOfRange(tcpProtocol.getData(),0,tcpProtocol.getLen()/2)); 36 | 37 | ctx.write(buffer); 38 | ctx.flush(); 39 | Thread.sleep(3000); 40 | buffer = ctx.alloc().buffer(); 41 | buffer.writeBytes(Arrays.copyOfRange(tcpProtocol.getData(),tcpProtocol.getLen()/2,tcpProtocol.getLen())); 42 | buffer.writeByte(tcpProtocol.getTail()); 43 | //模拟粘包的第二帧数据 44 | buffer.writeByte(tcpProtocol.getHeader()); 45 | buffer.writeInt(tcpProtocol.getLen()); 46 | buffer.writeBytes(tcpProtocol.getData()); 47 | buffer.writeByte(tcpProtocol.getTail()); 48 | //模拟粘包的第三帧数据 49 | buffer.writeByte(tcpProtocol.getHeader()); 50 | buffer.writeInt(tcpProtocol.getLen()); 51 | buffer.writeBytes(tcpProtocol.getData()); 52 | buffer.writeByte(tcpProtocol.getTail()); 53 | ctx.write(buffer); 54 | ctx.flush(); 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/server/decoder/DecoderHandler.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.server.decoder; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ByteToMessageDecoder; 6 | import org.apache.log4j.Logger; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | public class DecoderHandler extends ByteToMessageDecoder { 12 | //最小的数据长度:开头标准位1字节 13 | private static int MIN_DATA_LEN=6; 14 | //数据解码协议的开始标志 15 | private static byte PROTOCOL_HEADER=0x58; 16 | //数据解码协议的结束标志 17 | private static byte PROTOCOL_TAIL=0x63; 18 | private Logger logger = Logger.getLogger(this.getClass()); 19 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 20 | 21 | if (in.readableBytes()>MIN_DATA_LEN){ 22 | logger.debug("开始解码数据……"); 23 | //标记读操作的指针 24 | in.markReaderIndex(); 25 | byte header=in.readByte(); 26 | if (header==PROTOCOL_HEADER){ 27 | logger.debug("数据开头格式正确"); 28 | //读取字节数据的长度 29 | int len=in.readInt(); 30 | //数据可读长度必须要大于len,因为结尾还有一字节的解释标志位 31 | if (len>=in.readableBytes()){ 32 | logger.debug(String.format("数据长度不够,数据协议len长度为:%1$d,数据包实际可读内容为:%2$d正在等待处理拆包……",len,in.readableBytes())); 33 | in.resetReaderIndex(); 34 | /* 35 | **结束解码,这种情况说明数据没有到齐,在父类ByteToMessageDecoder的callDecode中会对out和in进行判断 36 | * 如果in里面还有可读内容即in.isReadable为true,cumulation中的内容会进行保留,,直到下一次数据到来,将两帧的数据合并起来,再解码。 37 | * 以此解决拆包问题 38 | */ 39 | return; 40 | } 41 | byte [] data=new byte[len]; 42 | in.readBytes(data);//读取核心的数据 43 | byte tail=in.readByte(); 44 | if (tail==PROTOCOL_TAIL){ 45 | logger.debug("数据解码成功"); 46 | out.add(data); 47 | //如果out有值,且in仍然可读,将继续调用decode方法再次解码in中的内容,以此解决粘包问题 48 | }else { 49 | logger.debug(String.format("数据解码协议结束标志位:%1$d [错误!],期待的结束标志位是:%2$d",tail,PROTOCOL_TAIL)); 50 | return; 51 | } 52 | }else { 53 | logger.debug("开头不对,可能不是期待的客服端发送的数,将自动略过这一个字节"); 54 | } 55 | }else { 56 | logger.debug("数据长度不符合要求,期待最小长度是:"+MIN_DATA_LEN+" 字节"); 57 | return; 58 | } 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/protocol/TcpProtocol_3_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.protocol; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | *type 0x51 0x52 0x53 7 | * mean: object list map 8 | * */ 9 | public class TcpProtocol_3_0 { 10 | private byte header=0x58; 11 | private byte type; 12 | private byte classLen; 13 | private int len; 14 | private byte[] className; 15 | private byte [] data; 16 | private byte tail=0x63; 17 | 18 | public byte getType() { 19 | return type; 20 | } 21 | 22 | public void setType(byte type) { 23 | this.type = type; 24 | } 25 | 26 | public byte getClassLen() { 27 | return classLen; 28 | } 29 | 30 | public void setClassLen(byte classLen) { 31 | this.classLen = classLen; 32 | } 33 | 34 | public byte[] getClassName() { 35 | return className; 36 | } 37 | 38 | public void setClassName(byte[] className) { 39 | this.className = className; 40 | } 41 | 42 | 43 | 44 | public byte getTail() { 45 | return tail; 46 | } 47 | 48 | public void setTail(byte tail) { 49 | this.tail = tail; 50 | } 51 | 52 | public TcpProtocol_3_0(int len, byte[] data) { 53 | this.len = len; 54 | this.data = data; 55 | } 56 | 57 | public TcpProtocol_3_0(byte type, byte classLen, int len, byte[] className, byte[] data) { 58 | this.type = type; 59 | this.classLen = classLen; 60 | this.len = len; 61 | this.className = className; 62 | this.data = data; 63 | } 64 | 65 | public TcpProtocol_3_0() { 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "TcpProtocol_3_0{" + 71 | "header=" + header + 72 | ", type=" + type + 73 | ", classLen=" + classLen + 74 | ", len=" + len + 75 | ", className=" + Arrays.toString(className) + 76 | ", data=" + Arrays.toString(data) + 77 | ", tail=" + tail + 78 | '}'; 79 | } 80 | 81 | public byte getHeader() { 82 | return header; 83 | } 84 | 85 | public void setHeader(byte header) { 86 | this.header = header; 87 | } 88 | 89 | public int getLen() { 90 | return len; 91 | } 92 | 93 | public void setLen(int len) { 94 | this.len = len; 95 | } 96 | 97 | public byte[] getData() { 98 | return data; 99 | } 100 | 101 | public void setData(byte[] data) { 102 | this.data = data; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/server/TcpServer.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.server; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelOption; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioServerSocketChannel; 10 | import io.netty.handler.logging.LoggingHandler; 11 | import org.apache.log4j.Logger; 12 | import org.wisdom.server.business.BusinessHandler; 13 | import org.wisdom.server.business.BusinessHandler_2_0; 14 | import org.wisdom.server.business.BusinessHandler_3_0; 15 | import org.wisdom.server.decoder.DecoderHandler; 16 | import org.wisdom.server.decoder.DecoderHandler_2_0; 17 | import org.wisdom.server.decoder.DecoderHandler_3_0; 18 | import org.wisdom.server.encoder.EncoderHandler; 19 | 20 | public class TcpServer { 21 | private int port; 22 | private Logger logger = Logger.getLogger(this.getClass()); 23 | public void init(){ 24 | logger.info("正在启动tcp服务器……"); 25 | NioEventLoopGroup boss = new NioEventLoopGroup();//主线程组 26 | NioEventLoopGroup work = new NioEventLoopGroup();//工作线程组 27 | try { 28 | ServerBootstrap bootstrap = new ServerBootstrap();//引导对象 29 | bootstrap.group(boss,work);//配置工作线程组 30 | bootstrap.channel(NioServerSocketChannel.class);//配置为NIO的socket通道 31 | bootstrap.childHandler(new ChannelInitializer() { 32 | protected void initChannel(SocketChannel ch) throws Exception {//绑定通道参数 33 | ch.pipeline().addLast("logging",new LoggingHandler("DEBUG"));//设置log监听器,并且日志级别为debug,方便观察运行流程 34 | ch.pipeline().addLast("encode",new EncoderHandler());//编码器。发送消息时候用 35 | ch.pipeline().addLast("decode",new DecoderHandler_3_0());//解码器,接收消息时候用 36 | ch.pipeline().addLast("handler",new BusinessHandler_3_0());//业务处理类,最终的消息会在这个handler中进行业务处理 37 | } 38 | }); 39 | bootstrap.option(ChannelOption.SO_BACKLOG,1024);//缓冲区 40 | bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);//ChannelOption对象设置TCP套接字的参数,非必须步骤 41 | ChannelFuture future = bootstrap.bind(port).sync();//使用了Future来启动线程,并绑定了端口 42 | logger.info("启动tcp服务器启动成功,正在监听端口:"+port); 43 | future.channel().closeFuture().sync();//以异步的方式关闭端口 44 | 45 | }catch (InterruptedException e) { 46 | logger.info("启动出现异常:"+e); 47 | }finally { 48 | work.shutdownGracefully(); 49 | boss.shutdownGracefully();//出现异常后,关闭线程组 50 | logger.info("tcp服务器已经关闭"); 51 | } 52 | 53 | } 54 | 55 | public static void main(String[] args) { 56 | new TcpServer(8777).init(); 57 | } 58 | public TcpServer(int port) { 59 | this.port = port; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/server/decoder/DecoderHandler_2_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.server.decoder; 2 | 3 | import com.fasterxml.jackson.databind.JavaType; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.type.TypeFactory; 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.ByteToMessageDecoder; 9 | import org.apache.log4j.Logger; 10 | import org.wisdom.dataTransefer.DTObject; 11 | import org.wisdom.utils.ByteUtils; 12 | import org.wisdom.utils.ProtocolUtils; 13 | 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class DecoderHandler_2_0 extends ByteToMessageDecoder { 18 | //最小的数据长度:开头标准位1字节 19 | private static int MIN_DATA_LEN=6+1+1+1; 20 | //数据解码协议的开始标志 21 | private static byte PROTOCOL_HEADER=0x58; 22 | //数据解码协议的结束标志 23 | private static byte PROTOCOL_TAIL=0x63; 24 | private Logger logger = Logger.getLogger(this.getClass()); 25 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 26 | 27 | if (in.readableBytes()>MIN_DATA_LEN){ 28 | logger.debug("开始解码数据……"); 29 | //标记读操作的指针 30 | in.markReaderIndex(); 31 | byte header=in.readByte(); 32 | if (header==PROTOCOL_HEADER){ 33 | logger.debug("数据开头格式正确"); 34 | //读取class类型 35 | byte type=in.readByte(); 36 | int dataLen=in.readInt(); 37 | if (dataLen Type = Class.forName(dtObject.getClassName()); 46 | logger.debug("数据解码成功"); 47 | logger.debug("开始封装数据……"); 48 | if (type==ProtocolUtils.OBJ_TYPE){ 49 | Object o = objectMapper.readValue(dtObject.getObject(), Type); 50 | out.add(o); 51 | }else if (type==ProtocolUtils.MAP_TYPE){ 52 | JavaType javaType= TypeFactory.defaultInstance().constructMapType(Map.class,String.class,Type); 53 | Object o = objectMapper.readValue(dtObject.getObject(), javaType); 54 | out.add(o); 55 | }else if (type==ProtocolUtils.LIST_TYPE){ 56 | JavaType javaType=TypeFactory.defaultInstance().constructCollectionType(List.class,Type); 57 | Object o = objectMapper.readValue(dtObject.getObject(), javaType); 58 | out.add(o); 59 | } 60 | //如果out有值,且in仍然可读,将继续调用decode方法再次解码in中的内容,以此解决粘包问题 61 | }else { 62 | logger.debug(String.format("数据解码协议结束标志位:%1$d [错误!],期待的结束标志位是:%2$d",tail,PROTOCOL_TAIL)); 63 | return; 64 | } 65 | }catch (ClassNotFoundException e){ 66 | logger.error(String.format("反序列化对象的类找不到,注意包名匹配! ")); 67 | return; 68 | }catch (Exception e){ 69 | logger.error(e); 70 | return; 71 | } 72 | 73 | }else{ 74 | logger.debug(String.format("数据长度不够,数据协议len长度为:%1$d,数据包实际可读内容为:%2$d正在等待处理拆包……",dataLen,in.readableBytes())); 75 | in.resetReaderIndex(); 76 | /* 77 | **结束解码,这种情况说明数据没有到齐,在父类ByteToMessageDecoder的callDecode中会对out和in进行判断 78 | * 如果in里面还有可读内容即in.isReadable位true,cumulation中的内容会进行保留,,直到下一次数据到来,将两帧的数据合并起来,再解码。 79 | * 以此解决拆包问题 80 | */ 81 | return; 82 | } 83 | }else { 84 | logger.debug("开头不对,可能不是期待的客服端发送的数,将自动略过这一个字节"); 85 | } 86 | }else { 87 | logger.debug("数据长度不符合要求,期待最小长度是:"+MIN_DATA_LEN+" 字节"); 88 | return; 89 | } 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/wisdom/server/decoder/DecoderHandler_3_0.java: -------------------------------------------------------------------------------- 1 | package org.wisdom.server.decoder; 2 | 3 | import com.fasterxml.jackson.databind.JavaType; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.type.ReferenceType; 6 | import com.fasterxml.jackson.databind.type.TypeFactory; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.handler.codec.ByteToMessageDecoder; 10 | import org.apache.log4j.Logger; 11 | import org.wisdom.utils.ByteUtils; 12 | import org.wisdom.utils.ProtocolUtils; 13 | 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class DecoderHandler_3_0 extends ByteToMessageDecoder { 18 | //最小的数据长度:开头标准位1字节 19 | private static int MIN_DATA_LEN=6+1+1+1; 20 | //数据解码协议的开始标志 21 | private static byte PROTOCOL_HEADER=0x58; 22 | //数据解码协议的结束标志 23 | private static byte PROTOCOL_TAIL=0x63; 24 | private Logger logger = Logger.getLogger(this.getClass()); 25 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 26 | 27 | if (in.readableBytes()>MIN_DATA_LEN){ 28 | logger.debug("开始解码数据……"); 29 | //标记读操作的指针 30 | in.markReaderIndex(); 31 | byte header=in.readByte(); 32 | if (header==PROTOCOL_HEADER){ 33 | logger.debug("数据开头格式正确"); 34 | //读取字节数据的长度 35 | byte type=in.readByte(); 36 | int typeLen=in.readByte()&255; 37 | int dataLen=in.readInt(); 38 | if (typeLen+dataLen Type = Class.forName(new String(fullClassName)); 46 | if (tail==PROTOCOL_TAIL){ 47 | logger.debug("数据解码成功"); 48 | logger.debug("开始封装数据……"); 49 | ObjectMapper objectMapper = ByteUtils.InstanceObjectMapper(); 50 | if (type==ProtocolUtils.OBJ_TYPE){ 51 | Object o = objectMapper.readValue(data, Type); 52 | out.add(o); 53 | }else if (type==ProtocolUtils.MAP_TYPE){ 54 | JavaType javaType= TypeFactory.defaultInstance().constructMapType(Map.class,String.class,Type); 55 | Object o = objectMapper.readValue(data, javaType); 56 | out.add(o); 57 | }else if (type==ProtocolUtils.LIST_TYPE){ 58 | JavaType javaType=TypeFactory.defaultInstance().constructCollectionType(List.class,Type); 59 | Object o = objectMapper.readValue(data, javaType); 60 | out.add(o); 61 | } 62 | //如果out有值,且in仍然可读,将继续调用decode方法再次解码in中的内容,以此解决粘包问题 63 | }else { 64 | logger.debug(String.format("数据解码协议结束标志位:%1$d [错误!],期待的结束标志位是:%2$d",tail,PROTOCOL_TAIL)); 65 | return; 66 | } 67 | }catch (ClassNotFoundException e){ 68 | logger.error(String.format("反序列化对象的类找不到,期待的全类名是:%1$s,注意包名匹配! ",fullClassName)); 69 | return; 70 | }catch (Exception e){ 71 | logger.error(e); 72 | return; 73 | } 74 | 75 | }else{ 76 | logger.debug(String.format("数据长度不够,数据协议len长度为:%1$d,数据包实际可读内容为:%2$d正在等待处理拆包……",dataLen+typeLen,in.readableBytes())); 77 | in.resetReaderIndex(); 78 | /* 79 | **结束解码,这种情况说明数据没有到齐,在父类ByteToMessageDecoder的callDecode中会对out和in进行判断 80 | * 如果in里面还有可读内容即in.isReadable位true,cumulation中的内容会进行保留,,直到下一次数据到来,将两帧的数据合并起来,再解码。 81 | * 以此解决拆包问题 82 | */ 83 | return; 84 | } 85 | }else { 86 | logger.debug("开头不对,可能不是期待的客服端发送的数,将自动略过这一个字节"); 87 | } 88 | }else { 89 | logger.debug("数据长度不符合要求,期待最小长度是:"+MIN_DATA_LEN+" 字节"); 90 | return; 91 | } 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netty_TCP 2 | 基于netty的TCP服务器/客户端 3 | > 在[上一篇中](https://blog.csdn.net/qq_24874939/article/details/86475285)介绍了基于netty4.x搭建一款灵活、稳健的TCP数据传输服务器,并处理了TCP通信中可能发生的的粘包、拆包问题(实际上是netty帮我们解决了)。能够在不改动解码器源码的前提下,通过Class.forName的作用,在反序列化的时候动态传入Class,实现任意Object的网络传输+灵活解码。但是处理不了集合类:Map、List等。 4 | ## 1.分析问题 5 | 回顾昨天的数据交互协议: 6 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190115175509822.png) 7 | **一个字节作为Header**+**4个字节作为长度len**+**len个字节的实际内容**+**一个字节的tail结束** 8 | 协议的实体类是这样的: 9 | 10 | ```java 11 | public class TcpProtocol { 12 | private byte header=0x58; 13 | private int len; 14 | private byte [] data; 15 | private byte tail=0x63; 16 | } 17 | ``` 18 | 在解码的时候反序列化了两次: 19 | 20 | ```java 21 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 22 | if (msg instanceof byte []){ 23 | logger.debug("解码后的字节码:"+new String((byte[]) msg,"UTF-8")); 24 | try { 25 | Object objectContainer = objectMapper.readValue((byte[]) msg, DTObject.class);//序列化成DTObject 读取FullClassName 26 | if (objectContainer instanceof DTObject){ 27 | DTObject data = (DTObject) objectContainer; 28 | if (data.getClassName()!=null&&data.getObject().length>0){ 29 | Object object = objectMapper.readValue(data.getObject(), Class.forName(data.getClassName()));//获取到FullClassName后才成功反序列成真实要获取的对象 30 | logger.info("收到实体对象:"+object); 31 | } 32 | } 33 | }catch (Exception e){ 34 | logger.info("对象反序列化出现问题:"+e); 35 | } 36 | 37 | } 38 | ``` 39 | 40 | > 由于没有考虑到List、Map的情况,因此这部分缺少判断类型的信息,解决办法有两种:1.是在协议中添加数据类型信息。2.是在DTObject中添加类型描述的字段。这里选择将数据类型放协议中去的方式。 41 | ## 2.方案一 42 | 将包含object类型的信息(map、list、普通object)添加到协议中后: 43 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190115180727369.png) 44 | 45 | - 新的协议类中新增一个type字段: 46 | 47 | ```java 48 | /** 49 | *type 0x51 0x52 0x53 50 | * mean: object list map 51 | * */ 52 | public class TcpProtocol_2_0 { 53 | private byte header=0x58; 54 | private byte type; 55 | private int len; 56 | private byte [] data; 57 | private byte tail=0x63; 58 | } 59 | ``` 60 | * 编码器也对应新增一个字节的内容: 61 | 62 | 63 | ```java 64 | public class EncoderHandler_2_0 extends MessageToByteEncoder { 65 | private Logger logger = Logger.getLogger(this.getClass()); 66 | protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { 67 | if (msg instanceof TcpProtocol_2_0){ 68 | TcpProtocol_2_0 protocol = (TcpProtocol_2_0) msg; 69 | out.writeByte(protocol.getHeader()); 70 | out.writeByte(protocol.getType());//新增Type 71 | out.writeInt(protocol.getLen()); 72 | out.writeBytes(protocol.getData()); 73 | out.writeByte(protocol.getTail()); 74 | logger.debug("数据编码成功:"+out); 75 | }else { 76 | logger.info("不支持的数据协议:"+msg.getClass()+"\t期待的数据协议类是:"+ TcpProtocol_2_0.class); 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | - 解码器解析顺序变成Header-->Type-->len--->data--->tail: 83 | ```java 84 | public class DecoderHandler_2_0 extends ByteToMessageDecoder { 85 | //最小的数据长度:开头标准位1字节 86 | private static int MIN_DATA_LEN=6+1+1+1; 87 | //数据解码协议的开始标志 88 | private static byte PROTOCOL_HEADER=0x58; 89 | //数据解码协议的结束标志 90 | private static byte PROTOCOL_TAIL=0x63; 91 | private Logger logger = Logger.getLogger(this.getClass()); 92 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 93 | 94 | if (in.readableBytes()>MIN_DATA_LEN){ 95 | logger.debug("开始解码数据……"); 96 | //标记读操作的指针 97 | in.markReaderIndex(); 98 | byte header=in.readByte(); 99 | if (header==PROTOCOL_HEADER){ 100 | logger.debug("数据开头格式正确"); 101 | //读取class类型 102 | byte type=in.readByte(); 103 | int dataLen=in.readInt(); 104 | if (dataLen Type = Class.forName(dtObject.getClassName()); 113 | logger.debug("数据解码成功"); 114 | logger.debug("开始封装数据……"); 115 | if (type==ProtocolUtils.OBJ_TYPE){ 116 | Object o = objectMapper.readValue(dtObject.getObject(), Type); 117 | out.add(o); 118 | }else if (type==ProtocolUtils.MAP_TYPE){ 119 | JavaType javaType= TypeFactory.defaultInstance().constructMapType(Map.class,String.class,Type); 120 | Object o = objectMapper.readValue(dtObject.getObject(), javaType); 121 | out.add(o); 122 | }else if (type==ProtocolUtils.LIST_TYPE){ 123 | JavaType javaType=TypeFactory.defaultInstance().constructCollectionType(List.class,Type); 124 | Object o = objectMapper.readValue(dtObject.getObject(), javaType); 125 | out.add(o); 126 | } 127 | //如果out有值,且in仍然可读,将继续调用decode方法再次解码in中的内容,以此解决粘包问题 128 | }else { 129 | logger.debug(String.format("数据解码协议结束标志位:%1$d [错误!],期待的结束标志位是:%2$d",tail,PROTOCOL_TAIL)); 130 | return; 131 | } 132 | }catch (ClassNotFoundException e){ 133 | logger.error(String.format("反序列化对象的类找不到,注意包名匹配! ")); 134 | return; 135 | }catch (Exception e){ 136 | logger.error(e); 137 | return; 138 | } 139 | 140 | }else{ 141 | logger.debug(String.format("数据长度不够,数据协议len长度为:%1$d,数据包实际可读内容为:%2$d正在等待处理拆包……",dataLen,in.readableBytes())); 142 | in.resetReaderIndex(); 143 | /* 144 | **结束解码,这种情况说明数据没有到齐,在父类ByteToMessageDecoder的callDecode中会对out和in进行判断 145 | * 如果in里面还有可读内容即in.isReadable位true,cumulation中的内容会进行保留,,直到下一次数据到来,将两帧的数据合并起来,再解码。 146 | * 以此解决拆包问题 147 | */ 148 | return; 149 | } 150 | }else { 151 | logger.debug("开头不对,可能不是期待的客服端发送的数,将自动略过这一个字节"); 152 | } 153 | }else { 154 | logger.debug("数据长度不符合要求,期待最小长度是:"+MIN_DATA_LEN+" 字节"); 155 | return; 156 | } 157 | 158 | } 159 | } 160 | ``` 161 | 162 | > 这里利用到了jackSon的JavaType来描述泛型,去反序列化Map和List类型的实体,也是反序列化了两次,第一次反序列化成DTObject获取全类名,第二次根据全类名和类型去反序列化真实的实体类。 163 | 164 | - 最后是在`EchoHandler`的`channelActive`方法中去测试发生数据: 165 | 166 | 167 | ```java 168 | public class EchoHandler_2_0 extends ChannelInboundHandlerAdapter { 169 | //连接成功后发送消息测试 170 | @Override 171 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 172 | User user = new User(); 173 | user.setBirthday(new Date()); 174 | user.setUID(UUID.randomUUID().toString()); 175 | user.setName("冉鹏峰"); 176 | user.setAge(24); 177 | HashMap map = new HashMap<>(); 178 | map.put("数据一",user); 179 | map.put("数据2",user); 180 | map.put("数据3",user); 181 | ArrayList list = new ArrayList<>(); 182 | list.add(user); 183 | list.add(user); 184 | list.add(user); 185 | list.add(user); 186 | TcpProtocol_2_0 tcpProtocol= ProtocolUtils_2_0.prtclInstance(list,user.getClass().getName()); 187 | ctx.write(tcpProtocol); 188 | ctx.flush(); 189 | } 190 | } 191 | ``` 192 | ProtocolUtils工具类是用来快速获取tcpProtocol对象的,具体代码: 193 | 194 | ```java 195 | public class ProtocolUtils_2_0 { 196 | public final static byte OBJ_TYPE=0x51; 197 | public final static byte LIST_TYPE=0x52; 198 | public final static byte MAP_TYPE=0x53; 199 | /** 200 | * 创建集合类list map对象 201 | * */ 202 | public static TcpProtocol_2_0 prtclInstance(Object o, String className){ 203 | TcpProtocol_2_0 protocol = new TcpProtocol_2_0(); 204 | if (o instanceof List){ 205 | protocol.setType(LIST_TYPE); 206 | }else if (o instanceof Map){ 207 | protocol.setType(MAP_TYPE); 208 | }else if (o instanceof Object){ 209 | protocol.setType(OBJ_TYPE); 210 | } 211 | initProtocol(o, className, protocol); 212 | 213 | return protocol; 214 | } 215 | /*** 216 | * 217 | * 创建单一的对象 218 | */ 219 | public static TcpProtocol_2_0 prtclInstance(Object o){ 220 | TcpProtocol_2_0 protocol = new TcpProtocol_2_0(); 221 | protocol.setType(OBJ_TYPE); 222 | initProtocol(o, o.getClass().getName(), protocol); 223 | 224 | return protocol; 225 | } 226 | 227 | private static void initProtocol(Object o, String className, TcpProtocol_2_0 protocol) { 228 | try { 229 | DTObject dtObject = new DTObject(); 230 | byte [] objectBytes= ByteUtils.InstanceObjectMapper().writeValueAsBytes(o); 231 | dtObject.setObject(objectBytes); 232 | dtObject.setClassName(className); 233 | byte[] bytes = ByteUtils.InstanceObjectMapper().writeValueAsBytes(dtObject); 234 | protocol.setLen(bytes.length); 235 | protocol.setData(bytes); 236 | } catch (JsonProcessingException e) { 237 | e.printStackTrace(); 238 | } 239 | } 240 | 241 | } 242 | ``` 243 | 最后是运行结果测试: 244 | 245 | - 发送List对象时: 246 | 247 | ``` 248 | 2019-01-15 18:21:10 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 开始解码数据…… 249 | 2019-01-15 18:21:10 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 数据开头格式正确 250 | 2019-01-15 18:21:10 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 数据解码成功 251 | 2019-01-15 18:21:10 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 开始封装数据…… 252 | 2019-01-15 18:21:10 INFO [org.wisdom.server.business.BusinessHandler_2_0] 这是一个List:[User{name='冉鹏峰', age=24, UID='2f7600e2-4714-4625-ad24-99947f182b76', birthday=Tue Jan 15 06:21:00 CST 2019}, User{name='冉鹏峰', age=24, UID='2f7600e2-4714-4625-ad24-99947f182b76', birthday=Tue Jan 15 06:21:00 CST 2019}, User{name='冉鹏峰', age=24, UID='2f7600e2-4714-4625-ad24-99947f182b76', birthday=Tue Jan 15 06:21:00 CST 2019}, User{name='冉鹏峰', age=24, UID='2f7600e2-4714-4625-ad24-99947f182b76', birthday=Tue Jan 15 06:21:00 CST 2019}] 253 | ``` 254 | List中的User泛型也成功的解码出来 255 | - 发生Map对象时 256 | 257 | 258 | ``` 259 | 2019-01-15 18:22:49 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 开始解码数据…… 260 | 2019-01-15 18:22:49 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 数据开头格式正确 261 | 2019-01-15 18:22:49 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 数据解码成功 262 | 2019-01-15 18:22:49 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 开始封装数据…… 263 | 2019-01-15 18:22:49 INFO [org.wisdom.server.business.BusinessHandler_2_0] 这是一个Map:{数据一=User{name='冉鹏峰', age=24, UID='b975ac02-ae29-4190-866a-bdf19d373924', birthday=Tue Jan 15 06:22:00 CST 2019}, 数据2=User{name='冉鹏峰', age=24, UID='b975ac02-ae29-4190-866a-bdf19d373924', birthday=Tue Jan 15 06:22:00 CST 2019}, 数据3=User{name='冉鹏峰', age=24, UID='b975ac02-ae29-4190-866a-bdf19d373924', birthday=Tue Jan 15 06:22:00 CST 2019}} 264 | ``` 265 | Map中的泛型User也被成功解码并识别出来了 266 | 267 | > 这里通过测试说明是能够处理传输集合类的信息的,实体类当然不在话下。但是通过一套流程下来:在客户端那里序列化了两次,在服务端反序列化了两次。序列化和反序列都是十分消耗性能的操作,按理说只序列化一次才是正常操作:对象实体--序列化---->字节数组 ;解码时候:字节数组----反序列化---->对象实体。下面是只序列化一次的方案2。 268 | - 方案2 269 | 为了数据接收端能够动态的反序列化对象,因此把实体对象的class信息也一并传输过去,并将对象字节组和className放到`DTObject`这个实体做为数据的二次载体。 270 | 271 | 272 | ```java 273 | public class DTObject { 274 | private String className; 275 | private byte[] object; 276 | } 277 | ``` 278 | 这样导致发送端和接收端都会为了`DTObject`而额外多做一次解析。**如果目的是为了简化协议结构则用方案一比较合适,如果考虑性更多性能上的问题,下面这种方式可能会更好。** 279 | 280 | 1. 重新设计传输协议 281 | 分析:在传入泛型对象时候,由于反编译需要同时声明泛型class和实体class,因此在协议中需要将这个泛型的类型type、实体的全类名className传入到接收端。可以在两边约定一种键值对类型的类型参照表: 282 | 283 | 284 | ``` 285 | |泛型类型| 代替数字 | 286 | |map | 0x51 | 287 | |list | 0x52 | 288 | |单实体 | 0x53 | 289 | ``` 290 | 甚至将实体的类型也对应的使用上面的方式:数字(key),全类名(value)在两端约定好。但是由于实体多样特效,可能需要将这些配置信息保存到一个激活的map中去,去而避免复杂的if else判断写法。随着实体类型的增加被激活的map的体积也要不断增加。 291 | 所以,简单处理:直接将className也包含到传输的数据中去。最终协议如下: 292 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190218110227248.png) 293 | 对应的`TcpProtocol_3_0`代码: 294 | 295 | ```java 296 | public class TcpProtocol_3_0 { 297 | private final byte header=0x58; 298 | private byte type; 299 | private byte classLen; 300 | private int len; 301 | private byte[] className; 302 | private byte [] data; 303 | private final byte tail=0x63; 304 | } 305 | ``` 306 | 将协议实体组装工具`ProtocolUtils`稍作修改: 307 | 308 | ```java 309 | public class ProtocolUtils { 310 | public final static byte OBJ_TYPE=0x51; 311 | public final static byte LIST_TYPE=0x52; 312 | public final static byte MAP_TYPE=0x53; 313 | /** 314 | * 创建集合类list map对象 315 | * */ 316 | public static TcpProtocol_3_0 prtclInstance(Object o, String className){ 317 | TcpProtocol_3_0 protocol = new TcpProtocol_3_0(); 318 | if (o instanceof List){ 319 | protocol.setType(LIST_TYPE); 320 | }else if (o instanceof Map){ 321 | protocol.setType(MAP_TYPE); 322 | }else if (o instanceof Object){ 323 | protocol.setType(OBJ_TYPE); 324 | } 325 | initProtocol(o, className, protocol); 326 | 327 | return protocol; 328 | } 329 | /*** 330 | * 331 | * 创建单一的对象 332 | */ 333 | public static TcpProtocol_3_0 prtclInstance(Object o){ 334 | TcpProtocol_3_0 protocol = new TcpProtocol_3_0(); 335 | protocol.setType(OBJ_TYPE); 336 | initProtocol(o, o.getClass().getName(), protocol); 337 | 338 | return protocol; 339 | } 340 | private static void initProtocol(Object o, String className, TcpProtocol_3_0 protocol) { 341 | byte [] classBytes=className.getBytes(); 342 | try { 343 | byte [] objectBytes= ByteUtils.InstanceObjectMapper().writeValueAsBytes(o); 344 | protocol.setClassLen((byte) classBytes.length); 345 | protocol.setLen(objectBytes.length); 346 | protocol.setData(objectBytes); 347 | protocol.setClassName(classBytes); 348 | } catch (JsonProcessingException e) { 349 | e.printStackTrace(); 350 | } 351 | } 352 | } 353 | ``` 354 | 355 | 2. 编码器 356 | 357 | 358 | ```java 359 | public class EncoderHandler_3_0 extends MessageToByteEncoder { 360 | private Logger logger = Logger.getLogger(this.getClass()); 361 | protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { 362 | if (msg instanceof TcpProtocol_3_0){ 363 | TcpProtocol_3_0 protocol = (TcpProtocol_3_0) msg; 364 | out.writeByte(protocol.getHeader()); 365 | out.writeByte(protocol.getType()); 366 | out.writeByte(protocol.getClassLen()); 367 | out.writeInt(protocol.getLen()); 368 | out.writeBytes(protocol.getClassName()); 369 | out.writeBytes(protocol.getData()); 370 | out.writeByte(protocol.getTail()); 371 | logger.debug("数据编码成功:"+out); 372 | }else { 373 | logger.info("不支持的数据协议:"+msg.getClass()+"\t期待的数据协议类是:"+ TcpProtocol_3_0.class); 374 | } 375 | } 376 | } 377 | ``` 378 | 379 | 3. 解码器 380 | 解码器的设计逻辑,仍然按照设计的协议来: 381 | 382 | 1.解析并验证协议开头标志位`0x58` 383 | 2.解析出泛型type 384 | 3.解析出类名长度len1和数据组长度len2 385 | 4.根据剩余可读位数和len1+len2+1大小处理粘包/拆包 386 | 5.读取出类名className 387 | 6.读取实际的数据字节组data 388 | 7.解析并验证结束标志位 389 | 8.根据泛型type和className去反编译出data,获得传输的实际java实体 390 | 391 | 解码器代码: 392 | 393 | 394 | ```java 395 | public class DecoderHandler_3_0 extends ByteToMessageDecoder { 396 | //最小的数据长度:开头标准位1字节 397 | private static int MIN_DATA_LEN=6+1+1+1; 398 | //数据解码协议的开始标志 399 | private static byte PROTOCOL_HEADER=0x58; 400 | //数据解码协议的结束标志 401 | private static byte PROTOCOL_TAIL=0x63; 402 | private Logger logger = Logger.getLogger(this.getClass()); 403 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 404 | 405 | if (in.readableBytes()>MIN_DATA_LEN){ 406 | logger.debug("开始解码数据……"); 407 | //标记读操作的指针 408 | in.markReaderIndex(); 409 | byte header=in.readByte(); 410 | if (header==PROTOCOL_HEADER){ 411 | logger.debug("数据开头格式正确"); 412 | //读取字节数据的长度 413 | byte type=in.readByte(); 414 | int typeLen=in.readByte()&255; 415 | int dataLen=in.readInt(); 416 | if (typeLen+dataLen Type = Class.forName(new String(fullClassName)); 424 | if (tail==PROTOCOL_TAIL){ 425 | logger.debug("数据解码成功"); 426 | logger.debug("开始封装数据……"); 427 | ObjectMapper objectMapper = ByteUtils.InstanceObjectMapper(); 428 | if (type==ProtocolUtils.OBJ_TYPE){ 429 | Object o = objectMapper.readValue(data, Type); 430 | out.add(o); 431 | }else if (type==ProtocolUtils.MAP_TYPE){ 432 | JavaType javaType= TypeFactory.defaultInstance().constructMapType(Map.class,String.class,Type); 433 | Object o = objectMapper.readValue(data, javaType); 434 | out.add(o); 435 | }else if (type==ProtocolUtils.LIST_TYPE){ 436 | JavaType javaType=TypeFactory.defaultInstance().constructCollectionType(List.class,Type); 437 | Object o = objectMapper.readValue(data, javaType); 438 | out.add(o); 439 | } 440 | //如果out有值,且in仍然可读,将继续调用decode方法再次解码in中的内容,以此解决粘包问题 441 | }else { 442 | logger.debug(String.format("数据解码协议结束标志位:%1$d [错误!],期待的结束标志位是:%2$d",tail,PROTOCOL_TAIL)); 443 | return; 444 | } 445 | }catch (ClassNotFoundException e){ 446 | logger.error(String.format("反序列化对象的类找不到,期待的全类名是:%1$s,注意包名匹配! ",fullClassName)); 447 | return; 448 | }catch (Exception e){ 449 | logger.error(e); 450 | return; 451 | } 452 | 453 | }else{ 454 | logger.debug(String.format("数据长度不够,数据协议len长度为:%1$d,数据包实际可读内容为:%2$d正在等待处理拆包……",dataLen+typeLen,in.readableBytes())); 455 | in.resetReaderIndex(); 456 | /* 457 | **结束解码,这种情况说明数据没有到齐,在父类ByteToMessageDecoder的callDecode中会对out和in进行判断 458 | * 如果in里面还有可读内容即in.isReadable位true,cumulation中的内容会进行保留,,直到下一次数据到来,将两帧的数据合并起来,再解码。 459 | * 以此解决拆包问题 460 | */ 461 | return; 462 | } 463 | }else { 464 | logger.debug("开头不对,可能不是期待的客服端发送的数,将自动略过这一个字节"); 465 | } 466 | }else { 467 | logger.debug("数据长度不符合要求,期待最小长度是:"+MIN_DATA_LEN+" 字节"); 468 | return; 469 | } 470 | 471 | } 472 | } 473 | ``` 474 | 475 | 4. 业务处理类的channelRead 476 | 477 | 478 | ```java 479 | public class BusinessHandler_3_0 extends ChannelInboundHandlerAdapter { 480 | private ObjectMapper objectMapper= ByteUtils.InstanceObjectMapper(); 481 | private Logger logger = Logger.getLogger(this.getClass()); 482 | @Override 483 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 484 | if (msg instanceof List){ 485 | logger.info("这是一个List:"+(List)msg); 486 | }else if (msg instanceof Map){ 487 | logger.info("这是一个Map:"+(Map)msg); 488 | }else{ 489 | logger.info("这是一个对象:"+msg.getClass().getName()); 490 | logger.info("这是一个对象:"+msg); 491 | } 492 | } 493 | } 494 | ``` 495 | 496 | 5. 客户端发送消息的处理器 497 | 498 | 499 | ```java 500 | public class EchoHandler_3_0 extends ChannelInboundHandlerAdapter { 501 | //连接成功后发送消息测试 502 | @Override 503 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 504 | User user = new User(); 505 | user.setBirthday(new Date()); 506 | user.setUID(UUID.randomUUID().toString()); 507 | user.setName("冉鹏峰"); 508 | user.setAge(24); 509 | Map map=new HashMap<>(); 510 | map.put("数据一",user); 511 | List users=new ArrayList<>(); 512 | users.add(user); 513 | TcpProtocol_3_0 protocol = ProtocolUtils.prtclInstance(map,user.getClass().getName()); 514 | //传map 515 | ctx.write(protocol);//由于设置了编码器,这里直接传入自定义的对象 516 | ctx.flush(); 517 | //传list 518 | ctx.write(ProtocolUtils.prtclInstance(users,user.getClass().getName())); 519 | ctx.flush(); 520 | //传单一实体 521 | ctx.write(ProtocolUtils.prtclInstance(user)); 522 | ctx.flush(); 523 | } 524 | } 525 | ``` 526 | 527 | 6. 测试运行结果 528 | 客户端运行情况: 529 | 530 | 531 | ``` 532 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.encoder.EncoderHandler_3_0] 数据编码成功:PooledUnsafeDirectByteBuf(ridx: 0, widx: 138, cap: 256) 533 | 534 | 2019-02-18 11:25:22 DEBUG [DEBUG] [id: 0x5b246c7d, L:/127.0.0.1:63155 - R:/127.0.0.1:8777] FLUSH 535 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.encoder.EncoderHandler_3_0] 数据编码成功:PooledUnsafeDirectByteBuf(ridx: 0, widx: 126, cap: 256) 536 | 537 | 2019-02-18 11:25:22 DEBUG [DEBUG] [id: 0x5b246c7d, L:/127.0.0.1:63155 - R:/127.0.0.1:8777] FLUSH 538 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.encoder.EncoderHandler_3_0] 数据编码成功:PooledUnsafeDirectByteBuf(ridx: 0, widx: 124, cap: 256) 539 | 540 | 2019-02-18 11:25:22 DEBUG [DEBUG] [id: 0x5b246c7d, L:/127.0.0.1:63155 - R:/127.0.0.1:8777] FLUSH 541 | ``` 542 | 服务端接收到的运行情况: 543 | 544 | ``` 545 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 开始解码数据…… 546 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 数据开头格式正确 547 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 数据解码成功 548 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 开始封装数据…… 549 | 2019-02-18 11:25:22 INFO [org.wisdom.server.business.BusinessHandler_3_0] 这是一个Map:{数据一=User{name='冉鹏峰', age=24, UID='3bb87b9f-a89c-4968-beec-a7b2a3b912b4', birthday=Mon Feb 18 11:25:00 CST 2019}} 550 | 551 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 开始解码数据…… 552 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 数据开头格式正确 553 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 数据解码成功 554 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 开始封装数据…… 555 | 2019-02-18 11:25:22 INFO [org.wisdom.server.business.BusinessHandler_3_0] 这是一个List:[User{name='冉鹏峰', age=24, UID='3bb87b9f-a89c-4968-beec-a7b2a3b912b4', birthday=Mon Feb 18 11:25:00 CST 2019}] 556 | 557 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 开始解码数据…… 558 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 数据开头格式正确 559 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 数据解码成功 560 | 2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 开始封装数据…… 561 | 2019-02-18 11:25:22 INFO [org.wisdom.server.business.BusinessHandler_3_0] 这是一个对象:pojo.User 562 | 2019-02-18 11:25:22 INFO [org.wisdom.server.business.BusinessHandler_3_0] 这是一个对象:User{name='冉鹏峰', age=24, UID='3bb87b9f-a89c-4968-beec-a7b2a3b912b4', birthday=Mon Feb 18 11:25:00 CST 2019} 563 | ``` 564 | **运行结果正常,可以混合传入单实体、泛型** 565 | 566 | > 代码下载地址:https://github.com/Siwash/netty_TCP 567 | 568 | 569 | 570 | 571 | 572 | 573 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 163 | 164 | 165 | 166 | pattern 167 | setDateFormat 168 | callDecode 169 | 170 | 171 | 172 | 203 | 204 | 205 | 206 | 207 | true 208 | DEFINITION_ORDER 209 | 210 | 211 | 216 | 221 | 222 | 223 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 |