• netty(六) websocket开发应用


    package com.lance.net.server.common;

    import java.net.InetSocketAddress;

    import org.springframework.stereotype.Component;

    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelOption;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.group.ChannelGroup;
    import io.netty.channel.group.DefaultChannelGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.util.concurrent.ImmediateEventExecutor;

    @Component
    public class ChatServer {
    private final ChannelGroup channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
    private final EventLoopGroup bossGroup = new NioEventLoopGroup();
    private final EventLoopGroup workGroup = new NioEventLoopGroup();
    private Channel channel;

    public ChannelFuture start(InetSocketAddress address) {
    ServerBootstrap bootstrap = new ServerBootstrap();
    bootstrap.group(bossGroup, workGroup)
    .channel(NioServerSocketChannel.class)
    .childHandler(new ChatServerInitializer(channelGroup))
    .option(ChannelOption.SO_BACKLOG, 128)
    .childOption(ChannelOption.SO_KEEPALIVE, true);

    ChannelFuture future = bootstrap.bind(address).syncUninterruptibly();
    channel = future.channel();
    return future;
    }

    public void destroy() {
    if(channel != null) {
    channel.close();
    }

    channelGroup.close();
    workGroup.shutdownGracefully();
    bossGroup.shutdownGracefully();
    }

    public static void main(String[] args) {
    ChatServer server = new ChatServer();
    InetSocketAddress address = new InetSocketAddress("127.0.0.1", 9090);
    ChannelFuture future = server.start(address);

    Runtime.getRuntime().addShutdownHook(new Thread(){
    @Override
    public void run() {
    server.destroy();
    }
    });

    future.channel().closeFuture().syncUninterruptibly();
    }
    }

    package com.lance.net.server.common;

    import java.util.concurrent.TimeUnit;

    import io.netty.channel.Channel;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.group.ChannelGroup;
    import io.netty.handler.codec.http.HttpObjectAggregator;
    import io.netty.handler.codec.http.HttpServerCodec;
    import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    import io.netty.handler.stream.ChunkedWriteHandler;
    import io.netty.handler.timeout.IdleStateHandler;

    public class ChatServerInitializer extends ChannelInitializer<Channel>{
    private final ChannelGroup group;

    public ChatServerInitializer(ChannelGroup group) {
    this.group = group;
    }

    @Override
    protected void initChannel(Channel ch) throws Exception {
    ChannelPipeline pipeline = ch.pipeline();
    //处理日志
    pipeline.addLast(new LoggingHandler(LogLevel.INFO));

    //处理心跳
    pipeline.addLast(new IdleStateHandler(0, 0, 1800, TimeUnit.SECONDS));
    pipeline.addLast(new ChatHeartbeatHandler());

    pipeline.addLast(new HttpServerCodec());
    pipeline.addLast(new ChunkedWriteHandler());
    pipeline.addLast(new HttpObjectAggregator(64 * 1024));
    pipeline.addLast(new HttpRequestHandler("/ws"));
    pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
    pipeline.addLast(new TextWebSocketFrameHandler(group));
    }
    }

    package com.lance.net.server.common;

    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;

    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.handler.timeout.IdleStateEvent;
    import io.netty.util.CharsetUtil;

    public class ChatHeartbeatHandler extends ChannelInboundHandlerAdapter{
    private Logger logger = LogManager.getLogger();
    private final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("HB",CharsetUtil.UTF_8));

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if(evt instanceof IdleStateEvent) {
    logger.info("====>Heartbeat: greater than {}", 180);
    ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
    }else {
    super.userEventTriggered(ctx, evt);
    }
    }
    }

    package com.lance.net.server.common;

    import java.io.RandomAccessFile;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;

    import io.netty.channel.*;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;

    import com.lance.net.server.module.UserInfo;

    import io.netty.handler.codec.http.DefaultFullHttpResponse;
    import io.netty.handler.codec.http.DefaultHttpResponse;
    import io.netty.handler.codec.http.FullHttpRequest;
    import io.netty.handler.codec.http.FullHttpResponse;
    import io.netty.handler.codec.http.HttpHeaderNames;
    import io.netty.handler.codec.http.HttpHeaderValues;
    import io.netty.handler.codec.http.HttpResponse;
    import io.netty.handler.codec.http.HttpResponseStatus;
    import io.netty.handler.codec.http.HttpUtil;
    import io.netty.handler.codec.http.HttpVersion;
    import io.netty.handler.codec.http.LastHttpContent;
    import io.netty.handler.codec.http.QueryStringDecoder;
    import io.netty.handler.ssl.SslHandler;
    import io.netty.handler.stream.ChunkedNioFile;
    import org.springframework.util.CollectionUtils;

    public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    private Logger loger = LogManager.getLogger();
    private final String webUri;
    private final String INDEX = "E:\workspace\test\src\main\webapp\index.html";
    /**用来保存channel*/
    public static Map<String, Channel> onlineChannels = new ConcurrentHashMap<>();

    public HttpRequestHandler(String webUri) {
    this.webUri = webUri;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
    loger.info("===========> {}, {}", webUri, request.uri());

    String uri = StringUtils.substringBefore(request.uri(), "?");
    if(webUri.equalsIgnoreCase(uri)) {//获取webSocket参数
    QueryStringDecoder query = new QueryStringDecoder(request.uri());
    Map<String, List<String>> map = query.parameters();
    List<String> tokens = map.get("token");
    List<String> userIds = map.get("userId");

    //根据参数保存当前登录对象, 并把该token加入到channel中
    if(tokens != null && !tokens.isEmpty()) {
    String token = tokens.get(0);
    ChatConstants.addOnlines(token, new UserInfo(token));
    ctx.channel().attr(ChatConstants.CHANNEL_TOKEN_KEY).getAndSet(token);
    }
    //将channel放入map中,key为用户id,value为channel
    if(!CollectionUtils.isEmpty(userIds)){
    onlineChannels.put(userIds.get(0),ctx.channel());
    }

    request.setUri(uri);
    ctx.fireChannelRead(request.retain());
    }else {
    if(HttpUtil.is100ContinueExpected(request)) {
    send100ContinueExpected(ctx);
    }

    RandomAccessFile file = new RandomAccessFile(INDEX, "r");
    HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), HttpResponseStatus.OK);
    response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");

    boolean keepAlive = HttpUtil.isKeepAlive(request);
    if(keepAlive) {
    response.headers().set(HttpHeaderNames.CONTENT_LENGTH, file.length());
    response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
    }
    ctx.write(response);

    if(ctx.pipeline().get(SslHandler.class) == null) {
    ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
    }else {
    ctx.write(new ChunkedNioFile(file.getChannel()));
    }

    ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
    if(!keepAlive) {
    future.addListener(ChannelFutureListener.CLOSE);
    }

    file.close();
    }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    cause.printStackTrace();
    ctx.close();
    }

    private void send100ContinueExpected(ChannelHandlerContext ctx) {
    FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONFLICT);
    ctx.writeAndFlush(response);
    }
    }

    package com.lance.net.server.common;

    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;

    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import com.lance.net.server.module.ChatMessage;
    import com.lance.net.server.module.UserInfo;

    import io.netty.channel.Channel;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.channel.group.ChannelGroup;
    import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
    import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;

    public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
    private Logger loger = LogManager.getLogger();
    private final ChannelGroup group;

    public TextWebSocketFrameHandler(ChannelGroup group) {
    this.group = group;
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    loger.info("Event====>{}", evt);

    if(evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
    ctx.pipeline().remove(HttpRequestHandler.class);

    //加入当前, 上线人员推送前端,显示用户列表中去
    Channel channel = ctx.channel();
    ChatMessage message = new ChatMessage(null, "上线了");
    group.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message,SerializerFeature.DisableCircularReferenceDetect)));
    group.add(channel);
    }else {
    super.userEventTriggered(ctx, evt);
    }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
    Channel channel = ctx.channel();
    String token = channel.attr(ChatConstants.CHANNEL_TOKEN_KEY).get();
    UserInfo from = ChatConstants.onlines.get(token);
    if(from == null) {
    group.writeAndFlush("OK");
    }else {
    sendToAll(msg,from);
    }
    }

    /**
    * 广播
    * @param msg
    * @param from
    */
    private void sendToAll(TextWebSocketFrame msg,UserInfo from){
    ChatMessage message = new ChatMessage(from, msg.text());
    group.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message,SerializerFeature.DisableCircularReferenceDetect)));
    }

    /**
    * 为指定用户发消息
    * @param msg
    * @param from
    * @param userId
    */
    public void sendToUser(TextWebSocketFrame msg,UserInfo from,String userId){
    ChatMessage message = new ChatMessage();
    message.setFrom(from);
    message.setMessage(msg.text());
    Channel channel = HttpRequestHandler.onlineChannels.get(userId);
    channel.writeAndFlush(JSON.toJSONString(message,SerializerFeature.DisableCircularReferenceDetect));
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    loger.info("Current channel channelInactive");
    offlines(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    loger.info("Current channel handlerRemoved");
    offlines(ctx);
    }

    private void offlines(ChannelHandlerContext ctx) {
    Channel channel = ctx.channel();
    String token = channel.attr(ChatConstants.CHANNEL_TOKEN_KEY).get();
    ChatConstants.removeOnlines(token);

    group.remove(channel);
    ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    loger.error("=====> {}", cause.getMessage());
    offlines(ctx);
    }
    }

    package com.lance.net.server.common;

    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;

    import org.apache.commons.lang3.RandomUtils;
    import org.apache.commons.lang3.StringUtils;

    import com.lance.net.server.module.UserInfo;

    import io.netty.util.AttributeKey;

    public class ChatConstants {
    public static final AttributeKey<String> CHANNEL_TOKEN_KEY = AttributeKey.valueOf("netty.channel.token");
    /**用来保存当前在线人员*/
    public static Map<String, UserInfo> onlines = new ConcurrentHashMap<>();

    public static void addOnlines(String sessionId, UserInfo val) {
    onlines.putIfAbsent(sessionId, val);
    }

    public static void removeOnlines(String sessionId) {
    if(StringUtils.isNotBlank(sessionId) && onlines.containsKey(sessionId)){
    onlines.remove(sessionId);
    }
    }

    private static char[]prefix = {'A','B','C','D','E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y'};
    private static int[]imgPrefix = {1,2,3,4,5,6,7,8,9,10,11};

    public static String headImg() {
    int index = RandomUtils.nextInt(0, imgPrefix.length);
    return "/resources/img/head/"+imgPrefix[index]+".jpg";
    }

    public static String code() {
    int index = RandomUtils.nextInt(0, prefix.length);
    char prf = prefix[index];
    String len = (onlines.size()+1)+"";
    if(len.length() < 4) {
    len = StringUtils.leftPad(len, 4, '0');
    }
    return prf+len;
    }
    }

    package com.lance.net.server.module;

    import java.util.Date;
    import java.util.Map;

    import com.alibaba.fastjson.annotation.JSONField;
    import com.lance.net.server.common.ChatConstants;

    public class ChatMessage {
    //发送消息则
    private UserInfo from;

    //发送内容
    private String message;

    //接收者列表
    private Map<String, UserInfo> to;

    //发送时间
    @JSONField(format="yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    public ChatMessage() {

    }

    public ChatMessage(UserInfo from,String message) {
    this.from = from;
    this.message = message;
    this.to = ChatConstants.onlines;
    this.createTime = new Date();
    }

    public String getMessage() {
    return message;
    }

    public void setMessage(String message) {
    this.message = message;
    }

    public UserInfo getFrom() {
    return from;
    }

    public void setFrom(UserInfo from) {
    this.from = from;
    }

    public Map<String, UserInfo> getTo() {
    return to;
    }

    public void setTo(Map<String, UserInfo> to) {
    this.to = to;
    }

    public Date getCreateTime() {
    return createTime;
    }

    public void setCreateTime(Date createTime) {
    this.createTime = createTime;
    }
    }

    package com.lance.net.server.module;

    import java.io.Serializable;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;

    import com.lance.net.server.common.ChatConstants;

    public class UserInfo implements Serializable{
    private static final long serialVersionUID = 3562768188264006800L;
    public static Map<String, UserInfo> map = new ConcurrentHashMap<>();

    private Long id;

    private String phone;

    private String password;

    private String code;

    private String headImg;

    public UserInfo() {

    }

    public UserInfo(String phone) {
    this.phone = phone;
    this.headImg = ChatConstants.headImg();
    this.code = ChatConstants.code();
    this.id = System.currentTimeMillis();
    }

    public String getHeadImg() {
    return headImg;
    }

    public void setHeadImg(String headImg) {
    this.headImg = headImg;
    }

    public Long getId() {
    return id;
    }

    public void setId(Long id) {
    this.id = id;
    }

    public String getPassword() {
    return password;
    }

    public void setPassword(String password) {
    this.password = password;
    }

    public String getPhone() {
    return phone;
    }

    public void setPhone(String phone) {
    this.phone = phone;
    }

    public String getCode() {
    return code;
    }

    public void setCode(String code) {
    this.code = code;
    }
    }
  • 相关阅读:
    ubuntu 下常用的命令(仅做记录)
    mysql5.7二进制包安装方法
    mysql5.6编译安装方法
    将yum下载的安装包保存到本地
    bash-completion--好用又强力的bash补全工具
    shell--破解RANDOM随机数
    shell--使用for循环统计一个网段内的在线主机
    shell--写一个脚本,批量创建10个用户用户名为userAdd1-10,并给他们随机密码
    shell--使用for循环批量创建10个随机小写字字母加固定字符的.txt文件,并写另一个脚本批量修改为.html文件
    如何选择开源许可证
  • 原文地址:https://www.cnblogs.com/luizw/p/10637117.html
Copyright © 2020-2023  润新知