经过一段时间对Netty的学习,我们对Netty各版本以及像ProtocolBuffers等技术应用都有了不少相关的了解, 我们就用这段时间学到的只是做一个简单的聊天室的小项目来练习自己学到的技术。
 做这个小项目之前我们先大致了解下我们需要用到的技术点,netty3.x/4.x/5.x、swingbuilder、protobuf。这里我们先以Netty3为例构建项目,各位可以通过git上查找Netty5以及结合了ProtocolBuffers版本的聊天室项目。
Netty_Demo[git]
我们先稍微比较下Netty几个版本之间的应用差异

netty3.x netty4.x/5.x
ChannelBuffer ByteBuf
ChannelBuffers PooledByteBufAllocator(要注意使用完后释放buffer)或UnpooledByteBufAllocator或Unpooled
FrameDecoder ByteToMessageDecoder
OneToOneEncoder MessageToByteEncoder
messageReceive channelRead0(netty5中messageReceive)
Common部分
项目目录结构



首先我们需要导入依赖,这里结合spring、mybatis、netty、log4j

pom.xml
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>Netty_Demo</artifactId><groupId>com.ithzk</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>Netty3</artifactId><name>Netty3</name><!-- FIXME change it to the project's website --><url>http://www.example.com</url><properties><!-- spring版本号 --><spring.version>4.3.13.RELEASE</spring.version><!-- mybatis版本号 --><mybatis.version>3.2.6</mybatis.version><!-- log4j日志文件管理包版本 --><slf4j.version>1.7.7</slf4j.version><log4j.version>1.2.17</log4j.version><jedis.version>2.7.3</jedis.version><druid.version>1.1.6</druid.version></properties><dependencies><!-- https://mvnrepository.com/artifact/io.netty/netty --><dependency><groupId>io.netty</groupId><artifactId>netty</artifactId><version>3.10.5.Final</version></dependency><!-- log start --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>${slf4j.version}</version></dependency><dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>2.4.1</version></dependency><!-- mybatis核心包 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.2.6</version></dependency><!-- mybatis/spring包 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.2.2</version></dependency><!-- 导入dbcp的jar包,用来在applicationContext.xml中配置数据库 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.6</version></dependency><!-- spring核心包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-oxm</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version></dependency><!-- 导入java ee jar 包 --><dependency><groupId>javax</groupId><artifactId>javaee-api</artifactId><version>7.0</version></dependency><!-- 导入Mysql数据库链接jar包 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.30</version></dependency></dependencies>
</project>

这里两个自定义注解主要作用是为了之后Hadnler处理调用业务代码有序管理使用

SocketCommand.java
package com.chat.common.core.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 请求命令* @author hzk* @date 2018/10/22*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SocketCommand {/*** 请求命令号*/short cmd();
}
SocketModule .java
package com.chat.common.core.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 请求模块* @author hzk* @date 2018/10/22*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SocketModule {/*** 请求模块号*/short module();
}

这里请求和响应的自定义编码器和解码器和我们之前一起学习时候是大体一致的,基本没有改变

RequestDecoder.java
package com.chat.common.core.coder;import com.chat.common.core.constants.Constants;
import com.chat.common.core.model.Request;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;/*** 请求解码器* 数据包格式(根据需求定义)* 包头 模块号 命令号 长度 数据 * 包头4字节* 模块号2字节short* 命令号2字节short* 长度4字节(描述数据部分字节长度)* @author hzk* @date 2018/9/29*/
public class RequestDecoder extends FrameDecoder{@Overrideprotected Object decode(ChannelHandlerContext channelHandlerContext, Channel channel, ChannelBuffer channelBuffer) throws Exception {//可读长度必须大于基本长度if(channelBuffer.readableBytes() >= Constants.AbstractDataStructure.DATA_STRUCTURE_LENGTH){//防止socket字节流攻击if(channelBuffer.readableBytes() > 2048){channelBuffer.skipBytes(channelBuffer.readableBytes());}//记录包头开始偏移Index,即第一个可读数据的起始位置int beginIndex;while (true){beginIndex = channelBuffer.readerIndex();//标记读索引位置channelBuffer.markReaderIndex();int packHead = channelBuffer.readInt();if(Constants.AbstractDataStructure.PACKAGE_HEAD == packHead){break;}//未读取到包头,还原读索引位置,略过一个字节channelBuffer.resetReaderIndex();channelBuffer.readByte();if(channelBuffer.readableBytes() < Constants.AbstractDataStructure.DATA_STRUCTURE_LENGTH){//数据包不完整,需要等待后面的数据包return null;}}//模块号short module = channelBuffer.readShort();//命令号short cmd = channelBuffer.readShort();//数据长度int length = channelBuffer.readInt();if(length <0){channel.close();}//判断请求数据包 数据是否完整if(channelBuffer.readableBytes() < length){//还原读指针channelBuffer.readerIndex(beginIndex);return null;}//读取data数据byte[] data = new byte[length];channelBuffer.readBytes(data);//解析出消息对象,往下传递(handler)return new Request(module,cmd,data);}//数据包不完整,需要等待后面的数据包return null;}
}
RequestEncoder.java
package com.chat.common.core.coder;import com.chat.common.core.constants.Constants;
import com.chat.common.core.model.Request;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;/*** 请求编码器* 数据包格式(根据需求定义)* 包头 模块号 命令号 长度 数据 * 包头4字节* 模块号2字节short* 命令号2字节short* 长度4字节(描述数据部分字节长度)* @author hzk* @date 2018/9/29*/
public class RequestEncoder extends OneToOneEncoder{@Overrideprotected Object encode(ChannelHandlerContext channelHandlerContext, Channel channel, Object rs) throws Exception {Request request = (Request) rs;ChannelBuffer channelBuffer = ChannelBuffers.dynamicBuffer();//包头channelBuffer.writeInt(Constants.AbstractDataStructure.PACKAGE_HEAD);//模块ModulechannelBuffer.writeShort(request.getModule());//命令号cmdchannelBuffer.writeShort(request.getCmd());//数据长度channelBuffer.writeInt(request.getDataLength());//数据if(null != request.getData()){channelBuffer.writeBytes(request.getData());}return channelBuffer;}
}
ResponseDecoder.java
package com.chat.common.core.coder;import com.chat.common.core.constants.Constants;
import com.chat.common.core.model.Response;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;/*** 响应解码器* 数据包格式(根据需求定义)* 包头 模块号 命令号 长度 数据 * 包头4字节int* 模块号2字节short* 命令号2字节short* 响应码4字节int* 长度4字节(描述数据部分字节长度)* @author hzk* @date 2018/9/29*/
public class ResponseDecoder extends FrameDecoder{@Overrideprotected Object decode(ChannelHandlerContext channelHandlerContext, Channel channel, ChannelBuffer channelBuffer) throws Exception {//可读长度必须大于基本长度if(channelBuffer.readableBytes() >= Constants.AbstractDataStructure.DATA_STRUCTURE_LENGTH){//防止socket字节流攻击if(channelBuffer.readableBytes() > 2048){channelBuffer.skipBytes(channelBuffer.readableBytes());}//记录包头开始偏移Indexint beginIndex = channelBuffer.readerIndex();while (true){beginIndex = channelBuffer.readerIndex();//标记读索引位置channelBuffer.markReaderIndex();int packHead = channelBuffer.readInt();if(Constants.AbstractDataStructure.PACKAGE_HEAD == packHead){break;}//未读取到包头,还原读索引位置,略过一个字节channelBuffer.resetReaderIndex();channelBuffer.readByte();if(channelBuffer.readableBytes() < Constants.AbstractDataStructure.DATA_RESPONSE_STRUCTURE_LENGTH){//数据包不完整,需要等待后面的数据包return null;}}//模块号short module = channelBuffer.readShort();//命令号short cmd = channelBuffer.readShort();//状态码int code = channelBuffer.readInt();//数据长度int length = channelBuffer.readInt();if(length < 0){channel.close();}//判断请求数据包 数据是否完整if(channelBuffer.readableBytes() < length){//还原读指针channelBuffer.readerIndex(beginIndex);return null;}//读取data数据byte[] data = new byte[length];channelBuffer.readBytes(data);//解析出消息对象,往下传递(handler)return new Response(module,cmd,data,code);}//数据包不完整,需要等待后面的数据包return null;}}
ResponseEncoder.java
package com.chat.common.core.coder;import com.chat.common.core.constants.Constants;
import com.chat.common.core.model.Response;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;/*** 响应编码器* 数据包格式(根据需求定义)* 包头 模块号 命令号 长度 数据 * 包头4字节* 模块号2字节short* 命令号2字节short* 长度4字节(描述数据部分字节长度)* @author hzk* @date 2018/9/29*/
public class ResponseEncoder extends OneToOneEncoder{@Overrideprotected Object encode(ChannelHandlerContext channelHandlerContext, Channel channel, Object rs) throws Exception {Response response = (Response) rs;System.out.println("ResponseEncoder response:" + rs.toString());ChannelBuffer channelBuffer = ChannelBuffers.dynamicBuffer();//包头channelBuffer.writeInt(Constants.AbstractDataStructure.PACKAGE_HEAD);//模块ModulechannelBuffer.writeShort(response.getModule());//命令号cmdchannelBuffer.writeShort(response.getCmd());//状态channelBuffer.writeInt(response.getCode());//数据长度channelBuffer.writeInt(response.getDataLength());//数据if(null != response.getData()){channelBuffer.writeBytes(response.getData());}return channelBuffer;}
}

下面这几个是我们整个项目中需要用到的常量数据以及响应数据用到的枚举

Constants.java
package com.chat.common.core.constants;/*** 常量* @author hzk* @date 2018/9/29*/
public class Constants {/*** netty配置相关*/public abstract static class AbstractNettyConfig{/*** 端口*/public static final int PORT = 8888;/*** IP*/public static final String ADDRESS = "127.0.0.1";}/*** 自定义数据结构相关*/public abstract static class AbstractDataStructure{/*** 包头*/public static final int PACKAGE_HEAD = -37593513;/*** 数据包基本长度(Request)* 4 + 2 + 2 + 4*/public static final int DATA_STRUCTURE_LENGTH = 12;/*** 数据包基本长度(Response) 比request多一个响应码(int) 之前在ResponseDecoder内部写死传递,现通过接收上一级传递解(译)码* 4 + 2 + 4 + 2 + 4*/public static final int DATA_RESPONSE_STRUCTURE_LENGTH = 16;}/*** 模块相关*/public abstract static class AbstractModule{/*** 用户/玩家模块*/public static final short PLAYER = 1;/*** 聊天模块*/public static final short CHAT = 2;}/*** 用户/玩家命令相关*/public abstract static class AbstractCmdPlayer{/*** 创建并登陆*/public static final short REGISTER_AND_LOGIN = 1;/*** 登陆*/public static final short LOGIN = 2;}/*** 聊天命令相关*/public abstract static class AbstractCmdChat{/*** 广播消息*/public static final short PUBLIC_CHAT = 1;/*** 私人消息*/public static final short PRIVATE_CHAT = 2;/*** 推送消息*/public static final short PUSH_CHAT = 101;}/*** 聊天类型相关*/public abstract static class AbstractChatType{/*** 广播消息*/public static final byte PUBLIC_CHAT = 0;/*** 私人消息*/public static final byte PRIVATE_CHAT = 1;}}
ResultCodeEnum.java
package com.chat.common.core.constants;import org.apache.log4j.Logger;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;/*** 响应码枚举类* @author hzk*/
public enum ResultCodeEnum {/*** 成功*/SUCCESS(1000,"success"),/*** 找不到命令*/NO_INVOKER(2000,"no_invoker"),/*** 参数异常*/PARAM_ERROR(2001,"param_error"),/*** 未知异常*/UNKNOWN_EXCEPTION(2002,"unknown_exception"),/*** 用户名或密码不能为空*/PLAYER_NAME_NULL(2003,"player_name_null"),/*** 用户名已使用*/PLAYER_EXIST(2004,"player_exist"),/*** 用户不存在*/PLAYER_NO_EXIST(2005,"player_no_exist"),/*** 密码错误*/PASSWORD_ERROR(2006,"password_error"),/*** 已经登录*/ALREADY_LOGIN(2007,"already_login"),/*** 登录失败*/LOGIN_FAIL(2008,"login_fail"),/*** 用户不在线*/PLAYER_NO_ONLINE(2009,"player_not_online"),/*** 未登录*/NOT_LOGIN(2010,"not_login"),/*** 不能以自己为私聊对象*/CANNOT_CHAT_YOURSELF(2011,"can't chat with yourself"),/*** 参数检验失败*/PARAMETER_AUTHORIZATION_FAILED(4001,"parameter_authorization_failed"),/*** 运行时异常*/RuntimeException(4002,"runtime_exception"),/*** 空指针异常*/NullPointerException(4003,"null_pointer_exception"),/*** 类型转换异常*/ClassCastException(4004,"class_cast_exception"),/*** IO异常*/IOException(4005,"io_exception"),/*** 未知方法异常*/NoSuchMethodException(4006,"no_such_method_exception"),/*** 数组越界异常*/IndexOutOfBoundsException(4007,"index_out_of_bounds_exception"),/*** 400错误*/HttpMessageNotReadableException(4008,"http_message_not_readable_exception"),/*** 400错误*/MissingServletRequestParameterException(4009,"miss_servlet_request_parameter_exception"),/*** 405错误*/HttpRequestMethodNotSupportedException(4010,"http_request_method_not_supported_exception"),/*** 406错误*/HttpMediaTypeNotAcceptableException(4011,"http_media_type_not_acceptable_exception"),/*** 500错误*/ConversionNotSupportedException(4012,"conversion_not_supported_exception"),/*** 响应码未定义*/NO_CODE(4013,"no_code"),;private static Logger log = Logger.getLogger(ResultCodeEnum.class);private int resultCode;private String resultInfo;private static Map< Integer, ResultCodeEnum> resultCodeMap = new HashMap<Integer, ResultCodeEnum>();private static Semaphore semaphore = new Semaphore(1);public int getResultCode() {return resultCode;}public String getResultInfo() {return resultInfo;}private ResultCodeEnum(int resultCode, String resultInfo){this.resultCode = resultCode;this.resultInfo = resultInfo;}public static String getResultInfo(int resultCode){if(resultCodeMap.isEmpty()){try {semaphore.acquire();initCodeInfoMap();} catch (InterruptedException e) {log.error("getResultInfo,err:"+e.getMessage());}finally{semaphore.release();}}ResultCodeEnum resultCodeEnum = resultCodeMap.get(new Integer(resultCode));return resultCodeEnum!=null?resultCodeEnum.getResultInfo():NO_CODE.getResultInfo();}private static void initCodeInfoMap(){if(resultCodeMap.isEmpty()){for(ResultCodeEnum resultCodeEnum:ResultCodeEnum.values()){resultCodeMap.put(resultCodeEnum.getResultCode(), resultCodeEnum);}}}public static void main(String args[]){System.out.println(ResultCodeEnum.PLAYER_EXIST.getResultCode());System.out.println(ResultCodeEnum.PLAYER_EXIST.getResultInfo());for(ResultCodeEnum resultCodeEnum : ResultCodeEnum.values()){System.out.println(resultCodeEnum.getResultCode()+" "+resultCodeEnum.getResultInfo());}}}
SwingConstants.java
package com.chat.common.core.constants;/*** Swing常量* @author hzk* @date 2018/10/25*/
public class SwingConstants {/*** 按键命令*/public abstract static class AbstractButtonCommand{/*** 注册*/public static final String REGISTER = "REGISTER";/*** 登录*/public static final String LOGIN = "LOGIN";/*** 发送*/public static final String SEND = "SEND";}}

用于包装以及抛出错误码的异常

ErrorCodeException.java
package com.chat.common.core.exception;/*** @author hzk* @date 2018/10/22*/
public class ErrorCodeException extends RuntimeException{private final int errorCode;public int getErrorCode(){return errorCode;}public ErrorCodeException(int errorCode){this.errorCode = errorCode;}
}

Client客户端和Server服务端交互时用来传递数据的请求、响应结果对象

Request.java
package com.chat.common.core.model;/*** 请求对象* @author hzk* @date 2018/9/29*/
public class Request {/*** 请求模块号*/private short module;/*** 请求命令号*/private short cmd;/*** 数据部分*/private byte[] data;public Request(short module, short cmd, byte[] data) {this.module = module;this.cmd = cmd;this.data = data;}public short getModule() {return module;}public void setModule(short module) {this.module = module;}public short getCmd() {return cmd;}public void setCmd(short cmd) {this.cmd = cmd;}public byte[] getData() {return data;}public void setData(byte[] data) {this.data = data;}public int getDataLength(){if(null == data){return 0;}return data.length;}public static Request valueOf(short module,short cmd,byte[] data){return new Request(module,cmd,data);}}
Response.java
package com.chat.common.core.model;import java.util.Arrays;/*** 响应对象* @author hzk* @date 2018/9/29*/
public class Response {/*** 请求模块*/private short module;/*** 请求命令号*/private short cmd;/*** 数据部分*/private byte[] data;/*** 状态码*/private int code;public Response(short module, short cmd, byte[] data, int code) {this.module = module;this.cmd = cmd;this.data = data;this.code = code;}public Response(Request request){this.module = request.getModule();this.cmd = request.getCmd();}public short getModule() {return module;}public void setModule(short module) {this.module = module;}public short getCmd() {return cmd;}public void setCmd(short cmd) {this.cmd = cmd;}public byte[] getData() {return data;}public void setData(byte[] data) {this.data = data;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public int getDataLength(){if(null == data){return 0;}return data.length;}@Overridepublic String toString() {return "Response{" +"module=" + module +", cmd=" + cmd +", data=" + Arrays.toString(data) +", code=" + code +'}';}
}
Result.java
package com.chat.common.core.model;import com.chat.common.core.constants.ResultCodeEnum;
import com.chat.common.core.serializable.Serializable;/*** 结果对象* @author hzk* @date 2018/10/23*/
public class Result<T extends Serializable>{/*** 结果码*/private int resultCode;/*** 结果内容*/private T content;public static <T extends Serializable> Result<T> success(T content){Result<T> tResult = new Result<>();tResult.resultCode = ResultCodeEnum.SUCCESS.getResultCode();tResult.content = content;return tResult;}public static <T extends Serializable> Result<T> success(){Result<T> tResult = new Result<>();tResult.resultCode = ResultCodeEnum.SUCCESS.getResultCode();return tResult;}public static <T extends Serializable> Result<T> error(int resultCode){Result<T> tResult = new Result<>();tResult.setResultCode(resultCode);return tResult;}public static <T extends Serializable> Result<T> valueOf(int resultCode,T content){Result<T> tResult = new Result<>();tResult.resultCode = resultCode;tResult.content = content;return tResult;}public int getResultCode() {return resultCode;}public void setResultCode(int resultCode) {this.resultCode = resultCode;}public T getContent() {return content;}public void setContent(T content) {this.content = content;}/*** 判断状态码是否成功* @return*/public boolean isSuccess(){return this.resultCode == ResultCodeEnum.SUCCESS.getResultCode();}
}

下面这几个有助于我们通过扫描自定义注解更加有效地管理不同模块不同命令号的handler

Invoker.java
package com.chat.common.core.scanner;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** 命令执行器/调用器* @author hzk* @date 2018/10/25*/
public class Invoker {/*** 方法*/private Method method;/*** 目标对象*/private Object target;public Method getMethod() {return method;}public void setMethod(Method method) {this.method = method;}public Object getTarget() {return target;}public void setTarget(Object target) {this.target = target;}public Invoker(Method method, Object target) {this.method = method;this.target = target;}/*** 执行* @param paramValues* @return*/public Object invoke(Object... paramValues){try {return method.invoke(target,paramValues);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalArgumentException e){e.printStackTrace();}return null;}public static Invoker valueOf(Method method,Object target){return new Invoker(method,target);}}
InvokerHolder.java
package com.chat.common.core.scanner;import java.util.HashMap;
import java.util.Map;/*** 命令执行器管理者* @author hzk* @date 2018/10/25*/
public class InvokerHolder {/*** 命令调用器*/private static Map<Short,Map<Short,Invoker>> invokers = new HashMap<>();/*** 添加命令调用器* @param module 模块号* @param cmd 命令号* @param invoker 调用器*/public static void addInvoker(short module,short cmd,Invoker invoker){Map<Short, Invoker> invokerMap = invokers.get(module);if(null == invokerMap){invokerMap = new HashMap<>();invokers.put(module,invokerMap);}invokerMap.put(cmd,invoker);}/*** 获取命令调用器* @param module 模块号* @param cmd 命令号* @return {@link Invoker}*/public static Invoker getInvoker(short module,short cmd){Map<Short, Invoker> invokerMap = invokers.get(module);if(null == invokerMap){return null;}return invokerMap.get(cmd);}
}
HandlerScanner.java
package com.chat.common.core.scanner;import com.chat.common.core.annotation.SocketCommand;
import com.chat.common.core.annotation.SocketModule;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** Handler扫描器* @author hzk* @date 2018/10/25*/
@Component
public class HandlerScanner implements BeanPostProcessor{@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {Class<? extends Object> clazz = bean.getClass();Class<?>[] interfaces = clazz.getInterfaces();if(null != interfaces && interfaces.length >0){//扫描类的所有接口父类for (Class<?> anInterface : interfaces) {//判断是否为Handler接口类SocketModule moduleAnnotation = anInterface.getAnnotation(SocketModule.class);if(null == moduleAnnotation){continue;}//找出命令方法Method[] methods = anInterface.getMethods();if(null != methods && methods.length > 0){for (Method method : methods) {SocketCommand cmdAnnotation = method.getAnnotation(SocketCommand.class);if(null == cmdAnnotation){continue;}short module = moduleAnnotation.module();short cmd = cmdAnnotation.cmd();if(null == InvokerHolder.getInvoker(module,cmd)){InvokerHolder.addInvoker(module,cmd,Invoker.valueOf(method,bean));}else{System.out.println("Repeat Module:" + module + ",Cmd:" + cmd);}}}}}return bean;}
}

当然少不了我们传输数据过程中自定义序列化的工具,也是在之前大家就熟悉的

BufferFactory.java
package com.chat.common.core.serializable;import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;import java.nio.ByteOrder;/*** ChannelBuffers工具类* @author hzk* @date 2018/9/26*/
public class BufferFactory{public static ByteOrder BYTE_ORDER = ByteOrder.BIG_ENDIAN;/*** 获取一个ChannelBuffer* @return*/public static ChannelBuffer getBuffer(){ChannelBuffer channelBuffer = ChannelBuffers.dynamicBuffer();return channelBuffer;}/*** 获取一个ChannelBuffer 并写入数据* @param bytes* @return*/public static ChannelBuffer getBuffer(byte[] bytes){ChannelBuffer channelBuffer = ChannelBuffers.copiedBuffer(bytes);return channelBuffer;}}
Serializable.java
package com.chat.common.core.serializable;import org.jboss.netty.buffer.ChannelBuffer;import java.nio.charset.Charset;
import java.util.*;/*** 自定义序列化* @author hzk* @date 2018/9/26*/
public abstract class Serializable {public static final Charset CHARSET = Charset.forName("UTF-8");protected ChannelBuffer writeBuffer;protected ChannelBuffer readBuffer;/*** 反序列化具体实现*/protected abstract void read();/*** 序列化具体实现*/protected abstract void write();/*** 从bytes数组读取数据* @param bytes* @return*/public Serializable readFromBytes(byte[] bytes){readBuffer = BufferFactory.getBuffer(bytes);read();readBuffer.clear();return this;}/*** 从channelBuffer读取数据* @param channelBuffer*/public void readFromBuffer(ChannelBuffer channelBuffer){this.readBuffer = channelBuffer;read();}/*** 写入到本地channelBuffer* @return*/public ChannelBuffer writeToLocalBuffer(){this.writeBuffer = BufferFactory.getBuffer();write();return writeBuffer;}/*** 写入到目标channelBuffer* @param channelBuffer* @return*/public ChannelBuffer writeToTargetBuffer(ChannelBuffer channelBuffer){this.writeBuffer = channelBuffer;write();return writeBuffer;}public Serializable writeByte(Byte value){writeBuffer.writeByte(value);return this;}public Serializable writeInt(int value){writeBuffer.writeInt(value);return this;}public Serializable writeShort(short value){writeBuffer.writeShort(value);return this;}public Serializable writeLong(long value){writeBuffer.writeLong(value);return this;}public Serializable writeFloat(float value){writeBuffer.writeFloat(value);return this;}public Serializable writeDouble(double value){writeBuffer.writeDouble(value);return this;}public Serializable writeString(String value){if(null == value || value.isEmpty()){writeShort((short)0);return this;}byte[] bytes = value.getBytes(CHARSET);short size = (short) bytes.length;writeBuffer.writeShort(size);writeBuffer.writeBytes(bytes);return this;}public Serializable writeObject(Object object){if(null == object){writeByte((byte)0);}else{if(object instanceof Integer){writeInt((int)object);}else if(object instanceof Short){writeShort((short)object);}else if(object instanceof Byte){writeByte((byte)object);}else if(object instanceof Long){writeLong((long)object);}else if(object instanceof Float){writeFloat((float)object);}else if(object instanceof Double){writeDouble((double)object);}else if(object instanceof String){writeString((String) object);}else if(object instanceof Serializable){writeByte((byte)1);Serializable serializable = (Serializable) object;serializable.writeToTargetBuffer(writeBuffer);}else{throw new RuntimeException("不可序列化类型:[%s]"+object.getClass());}}return this;}public <T> Serializable writeList(List<T> list){if(isEmpty(list)){writeBuffer.writeShort((short)0);return this;}writeBuffer.writeShort((short)list.size());for(T t:list){writeObject(t);}return this;}public <K,V> Serializable writeMap(Map<K,V> map){if(isEmpty(map)){writeBuffer.writeShort((short)0);return this;}writeBuffer.writeShort((short)map.size());for (Map.Entry<K,V> entry:map.entrySet()) {writeObject(entry.getKey());writeObject(entry.getValue());}return this;}/*** 返回byte数组* @return*/public byte[] getBytes(){writeToLocalBuffer();byte[] bytes = null;if(writeBuffer.writerIndex() == 0){bytes = new byte[0];}else{bytes = new byte[writeBuffer.writerIndex()];writeBuffer.readBytes(bytes);}writeBuffer.clear();return bytes;}public byte readByte(){return readBuffer.readByte();}public short readShort(){return readBuffer.readShort();}public int readInt(){return readBuffer.readInt();}public long readLong(){return readBuffer.readLong();}public float readFloat(){return readBuffer.readFloat();}public double readDouble(){return readBuffer.readDouble();}public String readString(){short size = readBuffer.readShort();if(size <= 0){return "";}byte[] bytes = new byte[size];readBuffer.readBytes(bytes);return new String(bytes,CHARSET);}public <K> K readObject(Class<K> clz){Object k = null;if(clz == int.class || clz == Integer.class){k = readInt();}else if(clz == byte.class || clz == Byte.class){k = readByte();}else if(clz == short.class || clz == Short.class){k = readShort();}else if(clz == long.class || clz == Long.class){k = readLong();}else if(clz == float.class || clz == Float.class){k = readFloat();}else if(clz == double.class || clz == Double.class){k = readDouble();}else if(clz == String.class){k = readString();}else if(Serializable.class.isAssignableFrom(clz)){try {byte hasObject = readBuffer.readByte();if(hasObject == 1){Serializable temp = (Serializable) clz.newInstance();temp.readFromBuffer(readBuffer);k = temp;}else{k = null;}}catch (Exception e){e.printStackTrace();}}else{throw new RuntimeException(String.format("不支持类型:[%s]",clz));}return (K)k;}public <T> List<T> readList(Class<T> clz){ArrayList<T> list = new ArrayList<>();short size = readBuffer.readShort();for(int i=0;i<size;i++){list.add(readObject(clz));}return list;}public <K,V> Map<K,V> readMap(Class<K> keyClz,Class<V> valueClz){HashMap<K, V> map = new HashMap<>();short size = readBuffer.readShort();for (int i =0;i<size;i++){K key = readObject(keyClz);V value = readObject(valueClz);map.put(key,value);}return map;}private <T> boolean isEmpty(Collection<T> c) {return c == null || c.isEmpty();}public <K,V> boolean isEmpty(Map<K,V> c) {return c == null || c.isEmpty();}
}

每一个连接之间都会建立一个独有的channel,我们这里用自定义会话将每个用户和其对应的channel绑定并且记录起来,以供发送消息的时候可以更方便定位其他用户

Session.java
package com.chat.common.core.session;/*** 会话抽象接口* @author hzk* @date 2018/10/23*/
public interface Session {/*** 会话绑定对象* @return*/Object getAttachment();/*** 绑定会话对象*/void setAttachment(Object attachment);/*** 移除会话对象*/void removeAttachment();/*** 写入消息到会话*  @param msg 消息*/void write(Object msg);/*** 判断会话是否在连接中* @return*/boolean isConnected();/*** 关闭会话*/void close();}
SessionImpl.java
package com.chat.common.core.session;import org.jboss.netty.channel.Channel;/*** 会话实现封装类* @author hzk* @date 2018/10/23*/
public class SessionImpl implements Session{/*** 实际会话对象*/private Channel channel;public SessionImpl(Channel channel) {this.channel = channel;}@Overridepublic Object getAttachment() {return channel.getAttachment();}@Overridepublic void setAttachment(Object attachment) {channel.setAttachment(attachment);}@Overridepublic void removeAttachment() {channel.setAttachment(null);}@Overridepublic void write(Object msg) {channel.write(msg);}@Overridepublic boolean isConnected() {return channel.isConnected();}@Overridepublic void close() {channel.close();}
}
SessionManager.java
package com.chat.common.core.session;import com.chat.common.core.constants.ResultCodeEnum;
import com.chat.common.core.model.Response;
import com.chat.common.core.serializable.Serializable;
import com.google.protobuf.GeneratedMessage;
import org.jboss.netty.util.internal.ConcurrentHashMap;import java.util.Collections;
import java.util.Set;/*** 会话管理者* @author hzk* @date 2018/10/23*/
public class SessionManager {/*** 在线会话组*/private static final ConcurrentHashMap<Long,Session> onlineSessions = new ConcurrentHashMap<>();/*** 将当前用户加入在线会话组* @param playerId 用户ID* @param session 当前用户会话* @return 是否加入成功*/public static boolean putSession(long playerId,Session session){if(!onlineSessions.contains(playerId)){//如果传入key对应的value已经存在,就返回存在的value,不进行替换。如果不存在,就添加key和value,返回nullreturn onlineSessions.putIfAbsent(playerId, session) == null ? true : false;}return false;}/*** 移除当前用户* @param playerId 用户ID* @return 是否移除成功*/public static Session removeSession(long playerId){return onlineSessions.remove(playerId);}/*** 发送消息[自定义协议]* @param playerId 用户ID* @param module 模块号* @param cmd 命令号* @param message 发送消息* @param <T> 发送消息泛型*/public static <T extends Serializable> void sendMessage(long playerId,short module,short cmd,T message){Session session = onlineSessions.get(playerId);if(null != session && session.isConnected()){Response response = new Response(module, cmd, message.getBytes(), ResultCodeEnum.SUCCESS.getResultCode());session.write(response);}}/*** 发送消息[Protocol Buffers协议]* @param playerId 用户ID* @param module 模块号* @param cmd 命令号* @param message 发送消息* @param <T> 发送消息泛型*/public static <T extends GeneratedMessage> void sendMessage(long playerId,short module,short cmd,T message){Session session = onlineSessions.get(playerId);if(null != session && session.isConnected()){Response response = new Response(module, cmd, message.toByteArray(), ResultCodeEnum.SUCCESS.getResultCode());session.write(response);}}/*** 检测用户是否在线* @param playerId 用户ID* @return*/public static boolean isOnlinePlayer(long playerId){return onlineSessions.containsKey(playerId);}/*** 获取所有在线用户ID* @return*/public static Set<Long> getOnlinePlayers(){return Collections.unmodifiableSet(onlineSessions.keySet());}
}

接下来几个是和数据库交互相关的工具类以及操作类,这里我们建立的用户表仅仅也只有简单的几个字段数据,只为了走通整个流程,利用MybatisGenerator生成的部分就不贴出来了。

MapperBeanNameGenerator.java
package com.chat.common.core.utils;import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;public class MapperBeanNameGenerator implements BeanNameGenerator {@Overridepublic String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {String beanClass = definition.getBeanClassName();int w = beanClass.indexOf(".write.");int r = beanClass.indexOf(".read.");return (w > 0) ? beanClass.substring(w + 1) : beanClass.substring(r + 1);}
}
Player.java
package com.chat.common.entity;public class Player {/*** This field was generated by MyBatis Generator.* This field corresponds to the database column player.id** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/private Long id;/*** This field was generated by MyBatis Generator.* This field corresponds to the database column player.name** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/private String name;/*** This field was generated by MyBatis Generator.* This field corresponds to the database column player.password** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/private String password;/*** This field was generated by MyBatis Generator.* This field corresponds to the database column player.level** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/private Integer level;/*** This field was generated by MyBatis Generator.* This field corresponds to the database column player.exp** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/private Integer exp;/*** This method was generated by MyBatis Generator.* This method returns the value of the database column player.id** @return the value of player.id** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/public Long getId() {return id;}/*** This method was generated by MyBatis Generator.* This method sets the value of the database column player.id** @param id the value for player.id** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/public void setId(Long id) {this.id = id;}/*** This method was generated by MyBatis Generator.* This method returns the value of the database column player.name** @return the value of player.name** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/public String getName() {return name;}/*** This method was generated by MyBatis Generator.* This method sets the value of the database column player.name** @param name the value for player.name** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/public void setName(String name) {this.name = name == null ? null : name.trim();}/*** This method was generated by MyBatis Generator.* This method returns the value of the database column player.password** @return the value of player.password** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/public String getPassword() {return password;}/*** This method was generated by MyBatis Generator.* This method sets the value of the database column player.password** @param password the value for player.password** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/public void setPassword(String password) {this.password = password == null ? null : password.trim();}/*** This method was generated by MyBatis Generator.* This method returns the value of the database column player.level** @return the value of player.level** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/public Integer getLevel() {return level;}/*** This method was generated by MyBatis Generator.* This method sets the value of the database column player.level** @param level the value for player.level** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/public void setLevel(Integer level) {this.level = level;}/*** This method was generated by MyBatis Generator.* This method returns the value of the database column player.exp** @return the value of player.exp** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/public Integer getExp() {return exp;}/*** This method was generated by MyBatis Generator.* This method sets the value of the database column player.exp** @param exp the value for player.exp** @mbggenerated Tue Oct 23 17:55:52 CST 2018*/public void setExp(Integer exp) {this.exp = exp;}
}
PlayerMapper.java
package com.chat.common.dao.read;import com.chat.common.entity.Player;
import com.chat.common.entity.PlayerExample;import java.util.List;public interface PlayerMapper {int countByExample(PlayerExample example);List<Player> selectByExample(PlayerExample example);Player selectByPrimaryKey(Long id);}
PlayerMapper.java
package com.chat.common.dao.write;import com.chat.common.entity.Player;
import com.chat.common.entity.PlayerExample;
import org.apache.ibatis.annotations.Param;public interface PlayerMapper {int deleteByExample(PlayerExample example);int deleteByPrimaryKey(Long id);int insert(Player record);int insertSelective(Player record);int updateByExampleSelective(@Param("record") Player record, @Param("example") PlayerExample example);int updateByExample(@Param("record") Player record, @Param("example") PlayerExample example);int updateByPrimaryKeySelective(Player record);int updateByPrimaryKey(Player record);
}

上面贴出了请求响应结果数据的封装类,这里几个是我们执行特定模块号命令号所需要用到的具体数据

PrivateChatRequest.java
package com.chat.common.module.chat.request;import com.chat.common.core.serializable.Serializable;/*** 私聊消息请求* @author hzk* @date 2018/10/23*/
public class PrivateChatRequest extends Serializable{/*** 发送目标用户ID*/private long targetPlayerId;/*** 内容*/private String content;public long getTargetPlayerId() {return targetPlayerId;}public void setTargetPlayerId(long targetPlayerId) {this.targetPlayerId = targetPlayerId;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}@Overrideprotected void read() {this.targetPlayerId = readLong();this.content = readString();}@Overrideprotected void write() {writeLong(targetPlayerId);writeString(content);}
}
PublicChatRequest.java
package com.chat.common.module.chat.request;import com.chat.common.core.serializable.Serializable;/*** 广播信息请求* @author hzk* @date 2018/10/23*/
public class PublicChatRequest extends Serializable{/*** 内容*/private String content;public String getContent() {return content;}public void setContent(String content) {this.content = content;}@Overrideprotected void read() {this.content = readString();}@Overrideprotected void write() {writeString(content);}
}
ChatResponse.java
package com.chat.common.module.chat.response;import com.chat.common.core.serializable.Serializable;/*** 聊天消息响应* @author hzk* @date 2018/10/23*/
public class ChatResponse extends Serializable{/*** 发送用户ID*/private long sendPlayerId;/*** 发送用户名称*/private String sendPlayerName;/*** 目标用户名称*/private String targetPlayerName;/*** 消息类型* 0 广播类型* 1 私聊* {@link com.chat.common.core.constants.Constants.AbstractChatType}*/private byte chatType;/*** 消息*/private String message;public long getSendPlayerId() {return sendPlayerId;}public void setSendPlayerId(long sendPlayerId) {this.sendPlayerId = sendPlayerId;}public String getSendPlayerName() {return sendPlayerName;}public void setSendPlayerName(String sendPlayerName) {this.sendPlayerName = sendPlayerName;}public String getTargetPlayerName() {return targetPlayerName;}public void setTargetPlayerName(String targetPlayerName) {this.targetPlayerName = targetPlayerName;}public byte getChatType() {return chatType;}public void setChatType(byte chatType) {this.chatType = chatType;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overrideprotected void read() {this.sendPlayerId = readLong();this.sendPlayerName = readString();this.targetPlayerName = readString();this.chatType = readByte();this.message = readString();}@Overrideprotected void write() {writeLong(sendPlayerId);writeString(sendPlayerName);writeString(targetPlayerName);writeByte(chatType);writeString(message);}
}
LoginRequest.java
package com.chat.common.module.player.request;import com.chat.common.core.serializable.Serializable;/*** 登录请求* @author hzk* @date 2018/10/23*/
public class LoginRequest extends Serializable{/*** 用户名*/private String playerName;/*** 密码*/private String password;public String getPlayerName() {return playerName;}public void setPlayerName(String playerName) {this.playerName = playerName;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overrideprotected void read() {this.playerName = readString();this.password = readString();}@Overrideprotected void write() {writeString(playerName);writeString(password);}
}
RegisterRequest.java
package com.chat.common.module.player.request;import com.chat.common.core.serializable.Serializable;/*** 注册请求* @author hzk* @date 2018/10/23*/
public class RegisterRequest extends Serializable {/*** 用户名*/private String playerName;/*** 密码*/private String password;public String getPlayerName() {return playerName;}public void setPlayerName(String playerName) {this.playerName = playerName;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overrideprotected void read() {this.playerName = readString();this.password = readString();}@Overrideprotected void write() {writeString(playerName);writeString(password);}
}
PlayerResponse.java
package com.chat.common.module.player.response;import com.chat.common.core.serializable.Serializable;/*** 用户信息响应* @author hzk* @date 2018/10/23*/
public class PlayerResponse extends Serializable{/*** 用户ID*/private Long playerId;/*** 用户名*/private String playerName;/*** 等级*/private Integer level;/*** 经验值*/private Integer exp;public Long getPlayerId() {return playerId;}public void setPlayerId(Long playerId) {this.playerId = playerId;}public String getPlayerName() {return playerName;}public void setPlayerName(String playerName) {this.playerName = playerName;}public Integer getLevel() {return level;}public void setLevel(Integer level) {this.level = level;}public Integer getExp() {return exp;}public void setExp(Integer exp) {this.exp = exp;}@Overrideprotected void read() {this.playerId = readLong();this.playerName = readString();this.level = readInt();this.exp = readInt();}@Overrideprotected void write() {writeLong(playerId);writeString(playerName);writeInt(level);writeInt(exp);}
}

聊天以及用户行为操作的业务处理类

ChatService.java
package com.chat.common.service;/*** 聊天服务* @author hzk* @date 2018/10/24*/
public interface ChatService {/*** 广播消息(群发)* @param playerId 用户ID* @param content 消息内容*/public void publicChat(long playerId,String content);/*** 私聊* @param playerId 用户ID* @param targetPlayerId 目标用户ID* @param content 消息内容*/public void privateChat(long playerId,long targetPlayerId,String content);}
ChatServiceImpl.java
package com.chat.common.service.impl;import com.chat.common.core.constants.Constants;
import com.chat.common.core.constants.ResultCodeEnum;
import com.chat.common.core.exception.ErrorCodeException;
import com.chat.common.core.session.SessionManager;
import com.chat.common.entity.Player;
import com.chat.common.module.chat.response.ChatResponse;
import com.chat.common.service.ChatService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import java.util.Set;/*** 聊天服务* @author hzk* @date 2018/10/24*/
@Component
public class ChatServiceImpl implements ChatService{@Autowiredprivate com.chat.common.dao.read.PlayerMapper playerMapper_r;@Overridepublic void publicChat(long playerId, String content) {Player player = playerMapper_r.selectByPrimaryKey(playerId);//获取所有在线玩家Set<Long> onlinePlayers = SessionManager.getOnlinePlayers();//创建消息对象ChatResponse chatResponse = new ChatResponse();chatResponse.setSendPlayerId(playerId);chatResponse.setSendPlayerName(player.getName());chatResponse.setMessage(content);chatResponse.setChatType(Constants.AbstractChatType.PUBLIC_CHAT);for (Long onlinePlayer : onlinePlayers) {SessionManager.sendMessage(onlinePlayer,Constants.AbstractModule.CHAT,Constants.AbstractCmdChat.PUSH_CHAT,chatResponse);}}@Overridepublic void privateChat(long playerId, long targetPlayerId, String content) {//检测是否私聊对象为自己if(playerId == targetPlayerId){throw new ErrorCodeException(ResultCodeEnum.CANNOT_CHAT_YOURSELF.getResultCode());}Player player = playerMapper_r.selectByPrimaryKey(playerId);Player targetPlayer = playerMapper_r.selectByPrimaryKey(targetPlayerId);//检测目标用户是否存在if(null == targetPlayer){throw new ErrorCodeException(ResultCodeEnum.PLAYER_NO_EXIST.getResultCode());}//检测目标用户是否在线if(!SessionManager.isOnlinePlayer(targetPlayerId)){throw new ErrorCodeException(ResultCodeEnum.PLAYER_NO_ONLINE.getResultCode());}//创建消息对象ChatResponse chatResponse = new ChatResponse();chatResponse.setSendPlayerId(playerId);chatResponse.setSendPlayerName(player.getName());chatResponse.setTargetPlayerName(targetPlayer.getName());chatResponse.setMessage(content);chatResponse.setChatType(Constants.AbstractChatType.PRIVATE_CHAT);//给目标用户发送信息SessionManager.sendMessage(targetPlayerId,Constants.AbstractModule.CHAT,Constants.AbstractCmdChat.PUSH_CHAT,chatResponse);//给自己回一个信息SessionManager.sendMessage(playerId,Constants.AbstractModule.CHAT,Constants.AbstractCmdChat.PUSH_CHAT,chatResponse);}
}
PlayerService.java
package com.chat.common.service;import com.chat.common.core.session.Session;
import com.chat.common.module.player.response.PlayerResponse;/*** 玩家服务* @author hzk* @date 2018/10/24*/
public interface PlayerService {/*** 注册并登录* @param session* @param playerName 用户名* @param password 密码* @return*/public PlayerResponse registerAndLogin(Session session,String playerName,String password);/*** 登录* @param session* @param playerName 用户名* @param password 密码* @return*/public PlayerResponse login(Session session,String playerName,String password);
}
PlayerServiceImpl.java
package com.chat.common.service.impl;import com.chat.common.core.constants.ResultCodeEnum;
import com.chat.common.core.exception.ErrorCodeException;
import com.chat.common.core.session.Session;
import com.chat.common.core.session.SessionManager;
import com.chat.common.entity.Player;
import com.chat.common.entity.PlayerExample;
import com.chat.common.module.player.response.PlayerResponse;
import com.chat.common.service.PlayerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import java.util.List;/*** 用户服务* @author hzk* @date 2018/10/24*/
@Component
public class PlayerServiceImpl implements PlayerService{@Autowiredprivate com.chat.common.dao.read.PlayerMapper playerMapper_r;@Autowiredprivate com.chat.common.dao.write.PlayerMapper playerMapper_w;@Overridepublic PlayerResponse registerAndLogin(Session session, String playerName, String password) {PlayerExample playerExample = new PlayerExample();playerExample.createCriteria().andNameEqualTo(playerName);int exist = playerMapper_r.countByExample(playerExample);if(exist > 0){//注册用户名已存在throw new ErrorCodeException(ResultCodeEnum.PLAYER_EXIST.getResultCode());}Player player = new Player();player.setName(playerName);player.setPassword(password);player.setLevel(1);player.setExp(0);int registerFlag = playerMapper_w.insertSelective(player);if(registerFlag <= 0){//注册失败throw new ErrorCodeException(ResultCodeEnum.UNKNOWN_EXCEPTION.getResultCode());}return login(session,playerName,password);}@Overridepublic PlayerResponse login(Session session, String playerName, String password) {//检测当前会话是否已经登录if(session.getAttachment() != null){throw new ErrorCodeException(ResultCodeEnum.ALREADY_LOGIN.getResultCode());}//检测用户是否存在PlayerExample playerExample = new PlayerExample();playerExample.createCriteria().andNameEqualTo(playerName);List<Player> players = playerMapper_r.selectByExample(playerExample);if(null == players || players.isEmpty()){throw new ErrorCodeException(ResultCodeEnum.PLAYER_NO_EXIST.getResultCode());}//检测账户密码匹配Player player = players.get(0);if(!password.equals(player.getPassword())){throw new ErrorCodeException(ResultCodeEnum.PASSWORD_ERROR.getResultCode());}//检测是否在其他地方已经登录boolean onlinePlayer = SessionManager.isOnlinePlayer(player.getId());if(onlinePlayer){Session oldSession = SessionManager.removeSession(player.getId());oldSession.removeAttachment();//踢下线oldSession.close();}//加入在线玩家会话if(SessionManager.putSession(player.getId(),session)){session.setAttachment(player);}else{throw new ErrorCodeException(ResultCodeEnum.LOGIN_FAIL.getResultCode());}//创建Response传输对象返回PlayerResponse playerResponse = new PlayerResponse();playerResponse.setPlayerId(player.getId());playerResponse.setPlayerName(player.getName());playerResponse.setLevel(player.getLevel());playerResponse.setExp(player.getExp());return playerResponse;}
}
Server部分

首先是我们定义了模块号命令号用于业务的handler

ChatHandler.java
package com.chat.server.chat.handler;import com.chat.common.core.annotation.SocketCommand;
import com.chat.common.core.annotation.SocketModule;
import com.chat.common.core.constants.Constants;
import com.chat.common.core.model.Result;/*** 聊天处理* @author hzk* @date 2018/10/25*/
@SocketModule(module = Constants.AbstractModule.CHAT)
public interface ChatHandler {/***  广播消息(群发)* @param playerId 发送用户ID* @param data {@link com.chat.common.module.chat.request.PublicChatRequest}* @return*/@SocketCommand(cmd = Constants.AbstractCmdChat.PUBLIC_CHAT)public Result<?> publicChat(long playerId,byte[] data);/*** 私聊消息* @param playerId 发送用户ID* @param data {@link com.chat.common.module.chat.request.PublicChatRequest}* @return*/@SocketCommand(cmd = Constants.AbstractCmdChat.PRIVATE_CHAT)public Result<?> privateChat(long playerId,byte[] data);
}
ChatHandlerImpl.java
package com.chat.server.chat.handler.impl;import com.chat.common.core.constants.ResultCodeEnum;
import com.chat.common.core.exception.ErrorCodeException;
import com.chat.common.core.model.Result;
import com.chat.common.module.chat.request.PrivateChatRequest;
import com.chat.common.module.chat.request.PublicChatRequest;
import com.chat.common.service.ChatService;
import com.chat.server.chat.handler.ChatHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;/*** 消息处理实现类* @author hzk* @date 2018/10/25*/
@Component
public class ChatHandlerImpl implements ChatHandler{@Autowiredprivate ChatService chatService;@Overridepublic Result<?> publicChat(long playerId, byte[] data) {try {//反序列化PublicChatRequest publicChatRequest = new PublicChatRequest();publicChatRequest.readFromBytes(data);//参数校验if(StringUtils.isEmpty(publicChatRequest.getContent())){return Result.error(ResultCodeEnum.PARAM_ERROR.getResultCode());}//执行业务chatService.publicChat(playerId,publicChatRequest.getContent());}catch (ErrorCodeException e){return Result.error(e.getErrorCode());}catch (Exception e) {e.printStackTrace();return Result.error(ResultCodeEnum.UNKNOWN_EXCEPTION.getResultCode());}return Result.success();}@Overridepublic Result<?> privateChat(long playerId, byte[] data) {try {//反序列化PrivateChatRequest privateChatRequest = new PrivateChatRequest();privateChatRequest.readFromBytes(data);//参数校验if(StringUtils.isEmpty(privateChatRequest.getContent()) || privateChatRequest.getTargetPlayerId() <= 0){return Result.error(ResultCodeEnum.PARAM_ERROR.getResultCode());}//执行业务chatService.privateChat(playerId,privateChatRequest.getTargetPlayerId(),privateChatRequest.getContent());}catch (ErrorCodeException e){return Result.error(e.getErrorCode());}catch (Exception e) {e.printStackTrace();return Result.error(ResultCodeEnum.UNKNOWN_EXCEPTION.getResultCode());}return Result.success();}
}
PlayerHandler.java
package com.chat.server.player.handler;import com.chat.common.core.annotation.SocketCommand;
import com.chat.common.core.annotation.SocketModule;
import com.chat.common.core.constants.Constants;
import com.chat.common.core.model.Result;
import com.chat.common.core.session.Session;
import com.chat.common.module.player.response.PlayerResponse;/*** 用户处理* @author hzk* @date 2018/10/25*/
@SocketModule(module = Constants.AbstractModule.PLAYER)
public interface PlayerHandler {/***  创建并登录账号* @param session 会话* @param data {@link com.chat.common.module.player.request.RegisterRequest}* @return*/@SocketCommand(cmd = Constants.AbstractCmdPlayer.REGISTER_AND_LOGIN)public Result<PlayerResponse> registerAndLogin(Session session,byte[] data);/*** 登录账号* @param session 会话* @param data {@link com.chat.common.module.player.request.RegisterRequest}* @return*/@SocketCommand(cmd = Constants.AbstractCmdPlayer.LOGIN)public Result<PlayerResponse> login(Session session,byte[] data);
}
PlayerHandlerImpl.java
package com.chat.server.player.handler.impl;import com.chat.common.core.constants.ResultCodeEnum;
import com.chat.common.core.exception.ErrorCodeException;
import com.chat.common.core.model.Result;
import com.chat.common.core.session.Session;
import com.chat.common.module.player.request.LoginRequest;
import com.chat.common.module.player.request.RegisterRequest;
import com.chat.common.module.player.response.PlayerResponse;
import com.chat.common.service.PlayerService;
import com.chat.server.player.handler.PlayerHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;/*** @author hzk* @date 2018/10/25*/
@Component
public class PlayerHandlerImpl implements PlayerHandler{@Autowiredprivate PlayerService playerService;@Overridepublic Result<PlayerResponse> registerAndLogin(Session session, byte[] data) {PlayerResponse playerResponse = null;try {//反序列化RegisterRequest registerRequest = new RegisterRequest();registerRequest.readFromBytes(data);///参数校验if(StringUtils.isEmpty(registerRequest.getPlayerName()) || StringUtils.isEmpty(registerRequest.getPassword())){return Result.error(ResultCodeEnum.PARAM_ERROR.getResultCode());}//执行业务playerResponse = playerService.registerAndLogin(session, registerRequest.getPlayerName(), registerRequest.getPassword());}catch (ErrorCodeException e){return Result.error(e.getErrorCode());}catch (Exception e){return Result.error(ResultCodeEnum.UNKNOWN_EXCEPTION.getResultCode());}return Result.success(playerResponse);}@Overridepublic Result<PlayerResponse> login(Session session, byte[] data) {PlayerResponse playerResponse = null;try {//反序列化LoginRequest loginRequest = new LoginRequest();loginRequest.readFromBytes(data);///参数校验if(StringUtils.isEmpty(loginRequest.getPlayerName()) || StringUtils.isEmpty(loginRequest.getPassword())){return Result.error(ResultCodeEnum.PARAM_ERROR.getResultCode());}//执行业务playerResponse = playerService.login(session, loginRequest.getPlayerName(), loginRequest.getPassword());}catch (ErrorCodeException e){return Result.error(e.getErrorCode());}catch (Exception e){e.printStackTrace();return Result.error(ResultCodeEnum.UNKNOWN_EXCEPTION.getResultCode());}return Result.success(playerResponse);}
}

最后只需要确定命令调用器的serverhandler以及server启动类就完成了服务端部分

ServerHandler.java
package com.chat.server.boot;import com.chat.common.core.constants.Constants;
import com.chat.common.core.constants.ResultCodeEnum;
import com.chat.common.core.model.Request;
import com.chat.common.core.model.Response;
import com.chat.common.core.model.Result;
import com.chat.common.core.serializable.Serializable;
import com.chat.common.core.session.Session;
import com.chat.common.core.session.SessionImpl;
import com.chat.common.core.session.SessionManager;
import com.chat.common.entity.Player;
import com.chat.common.core.scanner.Invoker;
import com.chat.common.core.scanner.InvokerHolder;
import com.google.protobuf.GeneratedMessage;
import org.jboss.netty.channel.*;/*** 消息接收处理类* @author hzk* @date 2018/10/25*/
public class ServerHandler extends SimpleChannelHandler{/*** 接收消息* @param ctx* @param e* @throws Exception*/@Overridepublic void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {Request request = (Request) e.getMessage();handleMessage(new SessionImpl(ctx.getChannel()),request);}private void handleMessage(Session session,Request request) throws Exception {Response response = new Response(request);System.out.println("ServerHandler->handleMessage Module:" + response.getModule() + ",Cmd:" + response.getCmd());//获取命令调用器Invoker invoker = InvokerHolder.getInvoker(request.getModule(), request.getCmd());if(null != invoker){try {Result result = null;//检测模块 若为用户模块 传入session channel参数,否则为聊天模块传入用户IDif(Constants.AbstractModule.PLAYER == request.getModule()){result = (Result) invoker.invoke(session,request.getData());}else{Object attachment = session.getAttachment();if(null != attachment){Player player = (Player) attachment;result = (Result) invoker.invoke(player.getId(),request.getData());}else{//会话未登录 拒绝请求response.setCode(ResultCodeEnum.NOT_LOGIN.getResultCode());session.write(response);return;}}//检测请求状态if(ResultCodeEnum.SUCCESS.getResultCode() == result.getResultCode()){//回写数据Object content = result.getContent();if(null != content){if(content instanceof Serializable){Serializable contentT = (Serializable) content;response.setData(contentT.getBytes());}else if(content instanceof GeneratedMessage){GeneratedMessage contentT = (GeneratedMessage) content;response.setData(contentT.toByteArray());}else{System.out.println(String.format("无法识别的传输对象:",content));}}response.setCode(result.getResultCode());session.write(response);}else{//返回错误码response.setCode(result.getResultCode());session.write(response);return;}}catch (Exception e){e.printStackTrace();//系统异常response.setCode(ResultCodeEnum.UNKNOWN_EXCEPTION.getResultCode());session.write(response);}}else{//未找到执行者response.setCode(ResultCodeEnum.NO_INVOKER.getResultCode());session.write(response);return;}}/*** 断线移除会话* @param ctx* @param e* @throws Exception*/@Overridepublic void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {SessionImpl session = new SessionImpl(ctx.getChannel());Object attachment = session.getAttachment();if(null != attachment){Player player = (Player) attachment;SessionManager.removeSession(player.getId());}}
}
Server.java
package com.chat.server.boot;import com.chat.common.core.coder.RequestDecoder;
import com.chat.common.core.coder.ResponseEncoder;
import com.chat.common.core.constants.Constants;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.springframework.stereotype.Component;import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** netty服务端启动类* @author hzk* @date 2018/10/25*/
@Component
public class Server {/*** 服务启动*/public void start(){//服务引导程序ServerBootstrap serverBootstrap = new ServerBootstrap();//boss线程监听端口,worker线程负责数据读写ExecutorService boss = Executors.newCachedThreadPool();ExecutorService worker = Executors.newCachedThreadPool();//设置NioSocket工厂serverBootstrap.setFactory(new NioServerSocketChannelFactory(boss,worker));//设置管道serverBootstrap.setPipelineFactory(new ChannelPipelineFactory() {@Overridepublic ChannelPipeline getPipeline() throws Exception {ChannelPipeline pipeline = Channels.pipeline();pipeline.addLast("decoder",new RequestDecoder());pipeline.addLast("encoder",new ResponseEncoder());pipeline.addLast("serverHandler",new ServerHandler());return pipeline;}});serverBootstrap.setOption("backlog",1024);serverBootstrap.bind(new InetSocketAddress(Constants.AbstractNettyConfig.PORT));System.out.println("Server Start Success...");}}
ServerMain.java
package com.chat.server.boot;import org.springframework.context.support.ClassPathXmlApplicationContext;/*** Netty服务端启动类* @author hzk* @date 2018/10/25*/
public class ServerMain {public static void main(String[] args){ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("server/application.xml");Server server = classPathXmlApplicationContext.getBean(Server.class);server.start();}
}
Client部分

首先是和Server部分一样定义了模块号命令号用于业务的handler

ChatHandler.java
package com.chat.client.chat.handler;import com.chat.common.core.annotation.SocketCommand;
import com.chat.common.core.annotation.SocketModule;
import com.chat.common.core.constants.Constants;/*** 聊天处理* @author hzk* @date 2018/10/25*/
@SocketModule(module = Constants.AbstractModule.CHAT)
public interface ChatHandler {/*** 发送广播消息回调* @param resultCode 响应码* @param data*/@SocketCommand(cmd =  Constants.AbstractCmdChat.PUBLIC_CHAT)public void publicChat(int resultCode,byte[] data);/*** 私发消息* @param resultCode 响应码* @param data*/@SocketCommand(cmd = Constants.AbstractCmdChat.PRIVATE_CHAT)public void privateChat(int resultCode,byte[] data);/*** 接收推送聊天消息* @param resultCode 响应码* @param data {@link com.chat.common.module.chat.response.ChatResponse}*/@SocketCommand(cmd = Constants.AbstractCmdChat.PUSH_CHAT)public void receiveMessage(int resultCode,byte[] data);
}
ChatHandlerImpl.java
package com.chat.client.chat.handler.impl;import com.chat.client.chat.handler.ChatHandler;
import com.chat.client.swing.ResultCodeHint;
import com.chat.client.swing.SwingClient;
import com.chat.common.core.constants.Constants;
import com.chat.common.core.constants.ResultCodeEnum;
import com.chat.common.module.chat.response.ChatResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** @author hzk* @date 2018/10/25*/
@Component
public class ChatHandlerImpl implements ChatHandler{@Autowiredprivate SwingClient swingClient;@Autowiredprivate ResultCodeHint resultCodeHint;@Overridepublic void publicChat(int resultCode, byte[] data) {if(ResultCodeEnum.SUCCESS.getResultCode() == resultCode){swingClient.getHints().setText("发送成功!");}else{swingClient.getHints().setText(resultCodeHint.getHintContent(resultCode));}}@Overridepublic void privateChat(int resultCode, byte[] data) {if(ResultCodeEnum.SUCCESS.getResultCode() == resultCode){swingClient.getHints().setText("发送成功!");}else{swingClient.getHints().setText(resultCodeHint.getHintContent(resultCode));}}@Overridepublic void receiveMessage(int resultCode, byte[] data) {ChatResponse chatResponse = new ChatResponse();chatResponse.readFromBytes(data);StringBuilder stringBuilder = new StringBuilder();if(Constants.AbstractChatType.PUBLIC_CHAT == chatResponse.getChatType()){stringBuilder.append(chatResponse.getSendPlayerName());stringBuilder.append("[");stringBuilder.append(chatResponse.getSendPlayerId());stringBuilder.append("]");stringBuilder.append(" 说:\n\t");stringBuilder.append(chatResponse.getMessage());stringBuilder.append("\n\n");}else if(Constants.AbstractChatType.PRIVATE_CHAT == chatResponse.getChatType()){if(swingClient.getPlayerResponse().getPlayerId() == chatResponse.getSendPlayerId()){stringBuilder.append("您悄悄对 ");stringBuilder.append("[");stringBuilder.append(chatResponse.getTargetPlayerName());stringBuilder.append("]");stringBuilder.append(" 说:\n\t");stringBuilder.append(chatResponse.getMessage());stringBuilder.append("\n\n");}else{stringBuilder.append(chatResponse.getSendPlayerName());stringBuilder.append("[");stringBuilder.append(chatResponse.getSendPlayerId());stringBuilder.append("]");stringBuilder.append(" 悄悄对你说:\n\t");stringBuilder.append(chatResponse.getMessage());stringBuilder.append("\n\n");}}swingClient.getChatContent().append(stringBuilder.toString());}
}
PlayerHandler.java
package com.chat.client.player.handler;import com.chat.common.core.annotation.SocketCommand;
import com.chat.common.core.annotation.SocketModule;
import com.chat.common.core.constants.Constants;/*** 用户处理* @author hzk* @date 2018/10/26*/
@SocketModule(module = Constants.AbstractModule.PLAYER)
public interface PlayerHandler {/*** 注册并登录* @param resultCode* @param data*/@SocketCommand(cmd = Constants.AbstractCmdPlayer.REGISTER_AND_LOGIN)public void registerAndLogin(int resultCode,byte[] data);/*** 登录账号* @param resultCode* @param data*/@SocketCommand(cmd = Constants.AbstractCmdPlayer.LOGIN)public void login(int resultCode,byte[] data);
}
PlayerHandlerImpl.java
package com.chat.client.player.handler.impl;import com.chat.client.player.handler.PlayerHandler;
import com.chat.client.swing.ResultCodeHint;
import com.chat.client.swing.SwingClient;
import com.chat.common.core.constants.ResultCodeEnum;
import com.chat.common.module.player.response.PlayerResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** @author hzk* @date 2018/10/26*/
@Component
public class PlayerHandlerImpl implements PlayerHandler {@Autowiredprivate SwingClient swingClient;@Autowiredprivate ResultCodeHint resultCodeHint;@Overridepublic void registerAndLogin(int resultCode, byte[] data) {if(ResultCodeEnum.SUCCESS.getResultCode() == resultCode){PlayerResponse playerResponse = new PlayerResponse();playerResponse.readFromBytes(data);swingClient.setPlayerResponse(playerResponse);swingClient.getHints().setText("注册并登录成功!");}else{swingClient.getHints().setText(resultCodeHint.getHintContent(resultCode));}}@Overridepublic void login(int resultCode, byte[] data) {if(ResultCodeEnum.SUCCESS.getResultCode() == resultCode){PlayerResponse playerResponse = new PlayerResponse();playerResponse.readFromBytes(data);swingClient.setPlayerResponse(playerResponse);swingClient.getHints().setText("登录成功!");}else{swingClient.getHints().setText(resultCodeHint.getHintContent(resultCode));}}
}

最后也是只需要确定命令调用器的clienthandler以及client启动类就完成了客户端部分,这里不一样的是我们利用了swing去生成可视化界面便于操作

ResultCodeHint.java
package com.chat.client.swing;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;/*** 消息提示* @author hzk* @date 2018/10/25*/
@Component
public class ResultCodeHint {private Properties properties= new Properties();/*** 初始化读取配置文件* @throws IOException*/@PostConstructpublic void init() throws IOException {InputStream in = getClass().getResourceAsStream("/client/code.properties");BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));properties.load(bufferedReader);}/*** 错误内容提示* @param code 错误码* @return*/public String getHintContent(int code){Object object = properties.get(code+"");if(null == object){return "错误码:" + code;}return object.toString();}}
SwingClient.java
package com.chat.client.swing;import com.chat.client.boot.Client;
import com.chat.common.core.constants.*;
import com.chat.common.core.constants.SwingConstants;
import com.chat.common.core.model.Request;
import com.chat.common.module.chat.request.PrivateChatRequest;
import com.chat.common.module.chat.request.PublicChatRequest;
import com.chat.common.module.player.request.LoginRequest;
import com.chat.common.module.player.request.RegisterRequest;
import com.chat.common.module.player.response.PlayerResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;/*** swing客户端* @author hzk* @date 2018/10/25*/
@Component
public class SwingClient extends JFrame implements ActionListener{@Autowiredprivate Client client;/*** 用户信息*/private PlayerResponse playerResponse;/*** 用户名*/private JTextField playerName;/*** 密码*/private JTextField password;/*** 登录按钮*/private JButton loginButton;/*** 注册按钮*/private JButton registerButton;/*** 聊天内容*/private JTextArea chatContent;/*** 发送消息*/private JTextField message;/*** 目标用户*/private JTextField targetPlayer;/*** 发送按钮*/private JButton sendButton;/*** 操作提示*/private JLabel hints;public SwingClient(){getContentPane().setLayout(null);//登录部分JLabel playerNameLab = new JLabel("用户名");playerNameLab.setFont(new Font("宋体",Font.PLAIN,12));playerNameLab.setBounds(76,40,54,15);getContentPane().add(playerNameLab);playerName = new JTextField();playerName.setBounds(139,37,154,21);getContentPane().add(playerName);playerName.setColumns(10);JLabel passwordLab = new JLabel("密  码");passwordLab.setFont(new Font("宋体", Font.PLAIN, 12));passwordLab.setBounds(76, 71, 54, 15);getContentPane().add(passwordLab);password = new JTextField();password.setColumns(10);password.setBounds(139, 68, 154, 21);getContentPane().add(password);//登录loginButton = new JButton("登录");loginButton.setFont(new Font("宋体", Font.PLAIN, 12));loginButton.setActionCommand(SwingConstants.AbstractButtonCommand.LOGIN);loginButton.addActionListener(this);loginButton.setBounds(315, 37, 93, 23);getContentPane().add(loginButton);//注册registerButton = new JButton("注册");registerButton.setFont(new Font("宋体", Font.PLAIN, 12));registerButton.setActionCommand(SwingConstants.AbstractButtonCommand.REGISTER);registerButton.addActionListener(this);registerButton.setBounds(315, 67, 93, 23);getContentPane().add(registerButton);//聊天内容框chatContent = new JTextArea();chatContent.setLineWrap(true);JScrollPane scrollBar = new JScrollPane(chatContent);scrollBar.setBounds(76, 96, 93, 403);scrollBar.setSize(336, 300);getContentPane().add(scrollBar);//发送消息部分JLabel targetLab = new JLabel("私聊用户");targetLab.setFont(new Font("宋体", Font.PLAIN, 12));targetLab.setBounds(76, 436, 63, 24);getContentPane().add(targetLab);targetPlayer = new JTextField();targetPlayer.setBounds(139, 438, 133, 21);getContentPane().add(targetPlayer);targetPlayer.setColumns(10);JLabel messageLab = new JLabel("消息");messageLab.setFont(new Font("宋体", Font.PLAIN, 12));messageLab.setBounds(76, 411, 54, 15);getContentPane().add(messageLab);message = new JTextField();message.setBounds(139, 408, 222, 21);getContentPane().add(message);message.setColumns(10);sendButton = new JButton("发送");sendButton.setFont(new Font("宋体", Font.PLAIN, 12));sendButton.setBounds(382, 407, 67, 23);sendButton.setActionCommand(SwingConstants.AbstractButtonCommand.SEND);sendButton.addActionListener(this);getContentPane().add(sendButton);//错误提示区域hints = new JLabel();hints.setForeground(Color.red);hints.setFont(new Font("宋体", Font.PLAIN, 14));hints.setBounds(76, 488, 200, 15);getContentPane().add(hints);int weigh = 500;int high = 600;int w = (Toolkit.getDefaultToolkit().getScreenSize().width - weigh) / 2;int h = (Toolkit.getDefaultToolkit().getScreenSize().height - high) / 2;this.setLocation(w, h);this.setTitle("Try Chat");this.setSize(weigh, high);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);this.setResizable(true);}@Overridepublic void actionPerformed(ActionEvent event) {switch (event.getActionCommand()){//登录case SwingConstants.AbstractButtonCommand.LOGIN:try {LoginRequest loginRequest = new LoginRequest();loginRequest.setPlayerName(playerName.getText());loginRequest.setPassword(password.getText());//构建请求Request request = Request.valueOf(Constants.AbstractModule.PLAYER,Constants.AbstractCmdPlayer.LOGIN,loginRequest.getBytes());client.sendMessage(request);}catch (Exception e){e.printStackTrace();hints.setText("无法连接服务器");}break;//注册case SwingConstants.AbstractButtonCommand.REGISTER:try {RegisterRequest registerRequest = new RegisterRequest();registerRequest.setPlayerName(playerName.getText());registerRequest.setPassword(password.getText());//构建请求Request request = Request.valueOf(Constants.AbstractModule.PLAYER,Constants.AbstractCmdPlayer.REGISTER_AND_LOGIN,registerRequest.getBytes());client.sendMessage(request);}catch (Exception e){e.printStackTrace();hints.setText("无法连接服务器");}break;//发送消息case SwingConstants.AbstractButtonCommand.SEND:try {if(StringUtils.isEmpty(targetPlayer.getText()) && !StringUtils.isEmpty(message.getText())){PublicChatRequest publicChatRequest = new PublicChatRequest();publicChatRequest.setContent(message.getText());//构建请求Request request = Request.valueOf(Constants.AbstractModule.CHAT,Constants.AbstractCmdChat.PUBLIC_CHAT,publicChatRequest.getBytes());client.sendMessage(request);}else{if(StringUtils.isEmpty(message.getText())){hints.setText("发送内容不能为空!");return;}long playerId = 0;try {playerId = Long.parseLong(targetPlayer.getText());}catch (NumberFormatException e){e.printStackTrace();hints.setText("用户ID为数字!");return;}PrivateChatRequest privateChatRequest = new PrivateChatRequest();privateChatRequest.setContent(message.getText());privateChatRequest.setTargetPlayerId(playerId);//构建请求Request request = Request.valueOf(Constants.AbstractModule.CHAT,Constants.AbstractCmdChat.PRIVATE_CHAT,privateChatRequest.getBytes());client.sendMessage(request);}}catch (Exception e){e.printStackTrace();hints.setText("无法连接服务器");}default:break;}}@Overrideprotected void processWindowStateEvent(WindowEvent e) {if(WindowEvent.WINDOW_CLOSING == e.getID()){client.shutdown();}super.processWindowStateEvent(e);}public PlayerResponse getPlayerResponse() {return playerResponse;}public void setPlayerResponse(PlayerResponse playerResponse) {this.playerResponse = playerResponse;}public JTextArea getChatContent() {return chatContent;}public JLabel getHints() {return hints;}}
ClientHandler.java
package com.chat.client.boot;import com.chat.client.swing.SwingClient;
import com.chat.common.core.model.Response;
import com.chat.common.core.scanner.Invoker;
import com.chat.common.core.scanner.InvokerHolder;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;/*** 消息接收处理类* @author hzk* @date 2018/10/25*/
public class ClientHandler extends SimpleChannelHandler{/*** 界面客户端*/private SwingClient swingClient;public ClientHandler(SwingClient swingClient) {this.swingClient = swingClient;}/*** 接收消息* @param ctx* @param e* @throws Exception*/@Overridepublic void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {Response response = (Response) e.getMessage();handleMessage(response);}/*** 消息处理* @param response {@link Response}*/private void handleMessage(Response response){System.out.println("ServerHandler->handleMessage Module:" + response.getModule() + ",Cmd:" + response.getCmd());//获取命令执行器Invoker invoker = InvokerHolder.getInvoker(response.getModule(), response.getCmd());if(null != invoker){try {invoker.invoke(response.getCode(),response.getData());}catch (Exception e){e.printStackTrace();}}else{//未找到执行器System.out.println("未找到执行器!");}}/*** 断开连接* @param ctx* @param e* @throws Exception*/@Overridepublic void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {swingClient.getHints().setText("与服务器断开连接!");}
}
Client.java
package com.chat.client.boot;import com.chat.client.swing.SwingClient;
import com.chat.common.core.coder.RequestEncoder;
import com.chat.common.core.coder.ResponseDecoder;
import com.chat.common.core.constants.Constants;
import com.chat.common.core.model.Request;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** netty客户端* @author hzk* @date 2018/10/25*/
@Component
public class Client {/*** 界面客户端*/@Autowiredprivate SwingClient swingClient;/*** 客户端引导程序*/ClientBootstrap clientBootstrap = new ClientBootstrap();/*** 会话通道*/private Channel channel;/*** 线程池*/private ExecutorService boss = Executors.newCachedThreadPool();private ExecutorService worker = Executors.newCachedThreadPool();/*** 初始化客户端* 服务器加载Servlet的时候运行,并且只会被服务器执行一次*/@PostConstructpublic void init(){//设置ClientSocket工厂clientBootstrap.setFactory(new NioClientSocketChannelFactory(boss,worker));//设置管道clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() {@Overridepublic ChannelPipeline getPipeline() throws Exception {ChannelPipeline pipeline = Channels.pipeline();pipeline.addLast("decoder",new ResponseDecoder());pipeline.addLast("encoder",new RequestEncoder());pipeline.addLast("clientHandler",new ClientHandler(swingClient));return pipeline;}});}/*** 连接服务端* @throws InterruptedException*/public void connect() throws InterruptedException {//连接服务器ChannelFuture connect = clientBootstrap.connect(new InetSocketAddress(Constants.AbstractNettyConfig.ADDRESS, Constants.AbstractNettyConfig.PORT));connect.sync();channel = connect.getChannel();}/*** 关闭连接*/public void shutdown(){channel.close();}/*** 获取会话通道* @return {@link Channel}*/public Channel getChannel(){return channel;}/*** 发送消息* @param request {@link Request}* @throws InterruptedException*/public void sendMessage(Request request) throws InterruptedException {if(null == channel || !channel.isConnected()){connect();}channel.write(request);}
}
ClientMain.java
package com.chat.client.boot;import com.chat.client.swing.SwingClient;
import com.chat.server.boot.Server;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** Netty服务端启动类* @author hzk* @date 2018/10/25*/
public class ClientMain {public static void main(String[] args){ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("client/application.xml");SwingClient swingClient = classPathXmlApplicationContext.getBean(SwingClient.class);swingClient.setVisible(true);}
}
配置文件部分

上面这些就是我们这个简单的聊天室小项目的所有Java代码了,但是我们还有一些不能遗漏的东西,就是配置文件。

目录部分

client.applicatiion.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 引入配置 --><!-- 配置文件 --><bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="locations"><list><value>classpath:server/jdbc.properties</value><value>classpath:server/log4j.properties</value></list></property></bean><!-- 包扫描 --><context:annotation-config /><context:component-scan base-package="com.chat.client"/><context:component-scan base-package="com.chat.common"/><bean id="mapperBeanNameGenerator" class="com.chat.common.core.utils.MapperBeanNameGenerator"/><!-- ==========================netty_chat数据的连接 Begin========================== --><bean id="dataSourceWrite_netty" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><!-- 基本属性driverClassName、 url、user、password --><property name="driverClassName" value="${db.driver}" /><property name="url" value="${db.netty.master.url}" /><property name="username" value="${db.netty.master.username}" /><property name="password" value="${db.netty.master.password}" /><!-- 配置初始化大小、最小、最大 --><!-- 通常来说,只需要修改initialSize、minIdle、maxActive --><property name="initialSize" value="${db.master.initialSize}" /><property name="minIdle" value="${db.master.minIdle}" /><property name="maxActive" value="${db.master.maxActive}" /><!-- 配置获取连接等待超时的时间 --><property name="maxWait" value="${db.master.maxWait}" /><!-- 默认值是 true ,当从连接池取连接时,验证这个连接是否有效 --><property name="testOnBorrow" value="true" /><!-- 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串 --><property name="testWhileIdle" value="true" /><!-- 默认值是 flase, 当从把该连接放回到连接池的时,验证这个连接是否有效 --><property name="testOnReturn" value="false" /><!--用来验证从连接池取出的连接,在将连接返回给调用者之前.如果指定,则查询必须是一个SQL SELECT并且必须返回至少一行记录--><property name="validationQuery" value="SELECT 'x'" /><!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --><property name="minEvictableIdleTimeMillis" value="30000" /><property name="removeAbandoned" value="true" /><property name="removeAbandonedTimeout" value="180" /><!-- 关闭abanded连接时输出错误日志 --><property name="logAbandoned" value="${db.master.logAbandoned}" /><!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --><property name="timeBetweenEvictionRunsMillis" value="60000" /><property name="poolPreparedStatements" value="true" /><property name="maxPoolPreparedStatementPerConnectionSize" value="50" /><property name="filters" value="stat" /><property name="proxyFilters"><list><ref bean="logFilter" /></list></property></bean><bean id="dataSourceRead_netty" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><!-- 基本属性driverClassName、 url、user、password --><property name="driverClassName" value="${db.driver}" /><property name="url" value="${db.netty.slave.url}" /><property name="username" value="${db.netty.slave.username}" /><property name="password" value="${db.netty.slave.password}" /><!-- 配置初始化大小、最小、最大 --><!-- 通常来说,只需要修改initialSize、minIdle、maxActive --><property name="initialSize" value="${db.slave.initialSize}" /><property name="minIdle" value="${db.slave.minIdle}" /><property name="maxActive" value="${db.slave.maxActive}" /><!-- 配置获取连接等待超时的时间 --><property name="maxWait" value="${db.slave.maxWait}" /><!-- 默认值是 true ,当从连接池取连接时,验证这个连接是否有效 --><property name="testOnBorrow" value="true" /><!-- 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串 --><property name="testWhileIdle" value="true" /><!-- 默认值是 flase, 当从把该连接放回到连接池的时,验证这个连接是否有效 --><property name="testOnReturn" value="false" /><!--用来验证从连接池取出的连接,在将连接返回给调用者之前.如果指定,则查询必须是一个SQL SELECT并且必须返回至少一行记录--><property name="validationQuery" value="SELECT 1 " /><!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --><property name="minEvictableIdleTimeMillis" value="30000" /><!-- 超过时间限制是否回收 --><property name="removeAbandoned" value="true" /><!-- 超时时间;单位为秒。180秒=3分钟 --><property name="removeAbandonedTimeout" value="180" /><!-- 关闭abanded连接时输出错误日志 --><property name="logAbandoned" value="${db.slave.logAbandoned}" /><!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --><property name="timeBetweenEvictionRunsMillis" value="60000" /><property name="poolPreparedStatements" value="true" /><property name="maxPoolPreparedStatementPerConnectionSize" value="50" /><property name="filters" value="stat" /><property name="proxyFilters"><list><ref bean="logFilter" /></list></property></bean><bean id="logFilter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter"><property name="statementExecutableSqlLogEnable" value="false" /></bean><!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --><bean id="sqlSessionFactoryWrite_netty" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSourceWrite_netty" /><!-- 自动扫描mapping.xml文件 --><property name="mapperLocations" value="classpath:_mapper/write/**/*.xml"/></bean><bean id="sqlSessionFactoryRead_netty" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSourceRead_netty" /><!-- 自动扫描mapping.xml文件 --><property name="mapperLocations" value="classpath:_mapper/read/**/*.xml"/></bean><!-- DAO接口所在包名,Spring会自动查找其下的类 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.chat.common.dao.write" /><property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryWrite_netty"/><property name="nameGenerator" ref="mapperBeanNameGenerator"/></bean><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.chat.common.dao.read" /><property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryRead_netty"/><property name="nameGenerator" ref="mapperBeanNameGenerator"/></bean><!-- ==========================netty_chat数据的连接 Finish========================== -->
</beans>
client.code.properties
2000=找不到命令
2001=参数异常
2002=未知异常
2003=用户名或密码不能为空
2004=用户名已使用
2005=用户不存在
2006=密码错误
2007=已经登录
2008=登录失败
2009=用户不在线
2010=未登录
2011=不能以自己为私聊对象
server.application.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 引入配置 --><!-- 配置文件 --><bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="locations"><list><value>classpath:server/jdbc.properties</value><value>classpath:server/log4j.properties</value></list></property></bean><!-- 包扫描 --><context:annotation-config /><context:component-scan base-package="com.chat.server"/><context:component-scan base-package="com.chat.common"/><bean id="mapperBeanNameGenerator" class="com.chat.common.core.utils.MapperBeanNameGenerator"/><!-- ==========================netty_chat数据的连接 Begin========================== --><bean id="dataSourceWrite_netty" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><!-- 基本属性driverClassName、 url、user、password --><property name="driverClassName" value="${db.driver}" /><property name="url" value="${db.netty.master.url}" /><property name="username" value="${db.netty.master.username}" /><property name="password" value="${db.netty.master.password}" /><!-- 配置初始化大小、最小、最大 --><!-- 通常来说,只需要修改initialSize、minIdle、maxActive --><property name="initialSize" value="${db.master.initialSize}" /><property name="minIdle" value="${db.master.minIdle}" /><property name="maxActive" value="${db.master.maxActive}" /><!-- 配置获取连接等待超时的时间 --><property name="maxWait" value="${db.master.maxWait}" /><!-- 默认值是 true ,当从连接池取连接时,验证这个连接是否有效 --><property name="testOnBorrow" value="true" /><!-- 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串 --><property name="testWhileIdle" value="true" /><!-- 默认值是 flase, 当从把该连接放回到连接池的时,验证这个连接是否有效 --><property name="testOnReturn" value="false" /><!--用来验证从连接池取出的连接,在将连接返回给调用者之前.如果指定,则查询必须是一个SQL SELECT并且必须返回至少一行记录--><property name="validationQuery" value="SELECT 'x'" /><!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --><property name="minEvictableIdleTimeMillis" value="30000" /><property name="removeAbandoned" value="true" /><property name="removeAbandonedTimeout" value="180" /><!-- 关闭abanded连接时输出错误日志 --><property name="logAbandoned" value="${db.master.logAbandoned}" /><!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --><property name="timeBetweenEvictionRunsMillis" value="60000" /><property name="poolPreparedStatements" value="true" /><property name="maxPoolPreparedStatementPerConnectionSize" value="50" /><property name="filters" value="stat" /><property name="proxyFilters"><list><ref bean="logFilter" /></list></property></bean><bean id="dataSourceRead_netty" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><!-- 基本属性driverClassName、 url、user、password --><property name="driverClassName" value="${db.driver}" /><property name="url" value="${db.netty.slave.url}" /><property name="username" value="${db.netty.slave.username}" /><property name="password" value="${db.netty.slave.password}" /><!-- 配置初始化大小、最小、最大 --><!-- 通常来说,只需要修改initialSize、minIdle、maxActive --><property name="initialSize" value="${db.slave.initialSize}" /><property name="minIdle" value="${db.slave.minIdle}" /><property name="maxActive" value="${db.slave.maxActive}" /><!-- 配置获取连接等待超时的时间 --><property name="maxWait" value="${db.slave.maxWait}" /><!-- 默认值是 true ,当从连接池取连接时,验证这个连接是否有效 --><property name="testOnBorrow" value="true" /><!-- 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串 --><property name="testWhileIdle" value="true" /><!-- 默认值是 flase, 当从把该连接放回到连接池的时,验证这个连接是否有效 --><property name="testOnReturn" value="false" /><!--用来验证从连接池取出的连接,在将连接返回给调用者之前.如果指定,则查询必须是一个SQL SELECT并且必须返回至少一行记录--><property name="validationQuery" value="SELECT 1 " /><!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --><property name="minEvictableIdleTimeMillis" value="30000" /><!-- 超过时间限制是否回收 --><property name="removeAbandoned" value="true" /><!-- 超时时间;单位为秒。180秒=3分钟 --><property name="removeAbandonedTimeout" value="180" /><!-- 关闭abanded连接时输出错误日志 --><property name="logAbandoned" value="${db.slave.logAbandoned}" /><!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --><property name="timeBetweenEvictionRunsMillis" value="60000" /><property name="poolPreparedStatements" value="true" /><property name="maxPoolPreparedStatementPerConnectionSize" value="50" /><property name="filters" value="stat" /><property name="proxyFilters"><list><ref bean="logFilter" /></list></property></bean><bean id="logFilter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter"><property name="statementExecutableSqlLogEnable" value="false" /></bean><!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --><bean id="sqlSessionFactoryWrite_netty" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSourceWrite_netty" /><!-- 自动扫描mapping.xml文件 --><property name="mapperLocations" value="classpath:_mapper/write/**/*.xml"/></bean><bean id="sqlSessionFactoryRead_netty" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSourceRead_netty" /><!-- 自动扫描mapping.xml文件 --><property name="mapperLocations" value="classpath:_mapper/read/**/*.xml"/></bean><!-- DAO接口所在包名,Spring会自动查找其下的类 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.chat.common.dao.write" /><property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryWrite_netty"/><property name="nameGenerator" ref="mapperBeanNameGenerator"/></bean><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.chat.common.dao.read" /><property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryRead_netty"/><property name="nameGenerator" ref="mapperBeanNameGenerator"/></bean><!-- ==========================netty_chat数据的连接 Finish========================== -->
</beans>
server.jdbc.properties
db.driver=com.mysql.jdbc.Driver###################################netty_chat####################################
db.netty.master.url=jdbc:mysql://127.0.0.1:3306/netty_chat?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
db.netty.master.username=root
db.netty.master.password=123db.netty.slave.url=jdbc:mysql://127.0.0.1:3306/netty_chat?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
db.netty.slave.username=root
db.netty.slave.password=123###################################common####################################
#initialSize
db.master.initialSize=0
#maxActive
db.master.maxActive=30
#minIdle
db.master.minIdle=0
#maxWait
db.master.maxWait=5000
db.master.logAbandoned=true#initialSize
db.slave.initialSize=1
#maxActive
db.slave.maxActive=100
#minIdle
db.slave.minIdle=10
#maxWait
db.slave.maxWait=5000
db.slave.logAbandoned=true
client.log4j.properties
#\u5b9a\u4e49LOG\u8f93\u51fa\u7ea7\u522b
log4j.rootLogger=info,dailyFile,debug,Console
#log4j.rootLogger=WARN,dailyFile,Console
#\u5b9a\u4e49\u65e5\u5fd7\u8f93\u51fa\u76ee\u7684\u5730\u4e3a\u63a7\u5236\u53f0
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
#\u53ef\u4ee5\u7075\u6d3b\u5730\u6307\u5b9a\u65e5\u5fd7\u8f93\u51fa\u683c\u5f0f\uff0c\u4e0b\u9762\u4e00\u884c\u662f\u6307\u5b9a\u5177\u4f53\u7684\u683c\u5f0f
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] [%d] [%t] [%C.%M:%L] - %m%n#\u6587\u4ef6\u5927\u5c0f\u5230\u8fbe\u6307\u5b9a\u5c3a\u5bf8\u7684\u65f6\u5019\u4ea7\u751f\u4e00\u4e2a\u65b0\u7684\u6587\u4ef6
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.Threshold=debug
log4j.appender.dailyFile.ImmediateFlush=true
log4j.appender.dailyFile.Append=true
log4j.appender.dailyFile.File=/home/logs/apps/netty/netty-server.log
log4j.appender.dailyFile.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.ConversionPattern =[%p] [%d{yyyy-MM-dd HH\:mm\:ss}][%c]%m%n
log4j.appender.dailyFile.encoding=UTF-8log4j.logger.com.pptv.ott.epglive.dao=debug
log4j.logger.org.apache.http=WARN
log4j.logger.org.apache.zookeeper=WARN

所有东西都准备好了之后,我们来走一遍流程,先启动服务端,然后开启多个客户端来验证下是否和我们设计的一样。

我们注册三个用户同时登陆,去模拟了群发以及私聊等功能,虽然项目很简洁但是功能基本都完全实现了,通过这段时间大家一起学习,对netty这个框架应该有了一个大致的了解,并且还能去了解一些序列化和socket相关的知识,博客中会涉及一些其他途径的内容,在这里借鉴也是希望大家可以坚持学习,对大家有一点点帮助心里也很高兴,因为也没有标明出处所以不好标明引用,希望涉及到的同学能理解。

Netty - 一个简单的聊天室小项目相关推荐

  1. python开发一个简单的聊天室

    使用python的twisted框架编写一个简单的聊天室 下面是基本架构 基本架构图 -- coding:utf-8 -- from twisted.internet.protocol import ...

  2. 用ServletContext做一个简单的聊天室

    这里主要是ServletContext的一个特性:ServletContext是一个公共的空间,可以被所有的客户访问.由此可见ServletContext比cookie和session的作用范围要大[ ...

  3. 前端+node实现一个简单的聊天室功能

    简单的实现一个聊天室功能 目录 前言 一.了解一下WebSocket 我们有了http协议,为什么会出现ws呢? ws出现之前,我们是怎么实现双向通信的呢? 二.使用到的websocket库 三.聊天 ...

  4. 使用Unity制作一个简单的聊天室

    使用控制台作为一个服务器,在Unity中开发客户端,制作一个简易的聊天室,无论哪个客户端发送消息,其他的客户端都会实时的显示出来. 服务器代码 using System; using System.C ...

  5. 使用netty搭建一个简单的聊天室

    1.导入maven依赖 <dependency><groupId>io.netty</groupId><artifactId>netty-all< ...

  6. C语言实现QQ聊天室小项目 [完整源码]

    聊天小项目用于练习Windows下的 tcp socket编程和线程同步,其中send 和 recv 使用,对tcp数据传输时经常遇到的中文乱码.数据残缺等问题有示范和纠正作用. 项目效果图 客户端代 ...

  7. Go 实现一个简单的聊天室

    创建客户端 先在main方法中创建一个sorcket服务端,这个服务端就像一个酒店 func main() {listen, err := net.Listen("tcp", &q ...

  8. 利用EasyDL制作一个简单的图片识别小项目

    主要是利用EasyDL制作一个简单的傻瓜式猫狗图片识别,利用EasyDL,只需要几步简单的点击即可 *主要的步骤: 1.准备数据 2.训练模型 3.部署 4.H5 * 1.首先创建两个文件夹cat和d ...

  9. python 土拨鼠库_为了应对某人的需求,写了一个简单的聊天室内容

    Python聊天室 背景 这是一篇水文,同时也是更换markdown后的第一篇,主要是为了测试markdown的情况. 服务器程序 #!/usr/bin/env python # -*- coding ...

最新文章

  1. Xcode 调试的正确打开方式——Debugging
  2. IBM苏中:怎样利用深度学习、增强学习等方法提高信息处理效率
  3. python程序间通信,python 实现 socket 进程间通信
  4. Centos部署YApi
  5. OpenCV——素描
  6. html多选框 jquery,jQuery Select多选
  7. python中 12_python编程中常用的12种基础知识总结
  8. 简单公司主页HTML5模板
  9. 后缀的形容词_玩转英语词汇-词汇策略之形容词后缀
  10. cip协议服务器,控制及信息协议(CIP)
  11. Bailian4106 出现两次的字符-Characters Appearing twice【计数统计】
  12. HP server ILO
  13. 在React项目中,如何优雅的优化长列表
  14. 《C语言入门经典》读后感(一)
  15. 酷睿i3 10105参数 i3 10105功耗 i310105怎么样
  16. 关联规则:一款在策略挖掘中必不可少的算法
  17. 数理基础(概率论)------离散型和连续型分布期望方差公式
  18. python视觉识别线条_简单车道线识别
  19. 理解vue ssr原理,自己搭建简单的ssr框架
  20. 自定义NDK交叉编译链(toolchain)

热门文章

  1. java mht 转换 html_Word单网页mht文件,汉字被html转义解决办法
  2. [ubuntu14.04 amd64 ]搜狗拼音輸入法安裝
  3. jsp自定义标签的问题Unable to load tag handler class
  4. NCL计算混合比或比湿的函数
  5. 关于Java中抽象类和接口的一点思索
  6. java哨片红盒 绿盒的区别_海淘维骨力怎么区分红盒,绿盒,蓝盒版本之间的区别...
  7. OWASP TOP 10 漏洞指南(2021)
  8. 2022年安全员-A证考试题库及安全员-A证免费试题
  9. 苏嵌学习日志03 07.13
  10. 细细品味C#——重构的艺术