Netty 实现socket 连接和WebSocket 同时连接,并相互通信

1、maven 依赖

<dependency>
   <groupId>io.netty</groupId>
   <artifactId>netty-all</artifactId>
   <version>4.1.65.Final</version>
</dependency>

2、Server 端

public class HttpServer {
    private int port;

    public HttpServer(int port) {
        this.port = port;
    }

    public void  bind() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new NettyServerChannelInitializer())
                    .option(ChannelOption.SO_BACKLOG, 500)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 绑定端口,开始接收进来的连接
            ChannelFuture future = bootstrap.bind(port).sync();
            //关闭channel和块,直到它被关闭
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

NettyServerChannelInitializer

public class NettyServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        channel.pipeline().addLast("socketChoose",new SocketChooseHandle());
        channel.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
        channel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
        channel.pipeline().addLast("commonhandler",new WebSocketHandler());
    }
}

SocketChooseHandle

public class SocketChooseHandle extends ByteToMessageDecoder {
    /** 默认暗号长度为23 */
    private static final int MAX_LENGTH = 23;
    /** WebSocket握手的协议前缀 */
    private static final String WEBSOCKET_PREFIX = "GET /";

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        String protocol = getBufStart(in);
        if (protocol.startsWith(WEBSOCKET_PREFIX)) {
            ctx.pipeline().addBefore("commonhandler","http-codec",new HttpServerCodec());
            // HttpObjectAggregator:将HTTP消息的多个部分合成一条完整的HTTP消息
            ctx.pipeline().addBefore("commonhandler","aggregator",new HttpObjectAggregator(65535));

            // ChunkedWriteHandler:向客户端发送HTML5文件,文件过大会将内存撑爆
            ctx.pipeline().addBefore("commonhandler","http-chunked",new ChunkedWriteHandler());
            ctx.pipeline().addBefore("commonhandler","WebSocketAggregator",new WebSocketFrameAggregator(65535));
            //用于处理websocket, /ws为访问websocket时的uri
            ctx.pipeline().addBefore("commonhandler","ProtocolHandler", new WebSocketServerProtocolHandler("/ws"));
            // 此次要移除socket 相关的编码
            ctx.pipeline().remove(StringDecoder.class);
            ctx.pipeline().remove(StringEncoder.class);
        }

        in.resetReaderIndex();
        ctx.pipeline().remove(this.getClass());
    }
    private String getBufStart(ByteBuf in){
        int length = in.readableBytes();
        if (length > MAX_LENGTH) {
            length = MAX_LENGTH;
        }

        // 标记读位置
        in.markReaderIndex();
        byte[] content = new byte[length];
        in.readBytes(content);
        return new String(content);
    }
}
WebSocketHandler
public class WebSocketHandler extends SimpleChannelInboundHandler<Object> {

    private static Map<String, Channel> map = new ConcurrentHashMap<>();
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object object) throws Exception {
        String msg = "";
        if (object instanceof TextWebSocketFrame){
            TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame)object;
            msg = textWebSocketFrame.text();
        }else {
            msg = String.valueOf(object);
        }
        System.out.println("msg:"+msg);

        for (String key: map.keySet()) {
            if (key.equals(ctx.channel().id().toString())){
                continue;
            }
            Channel channel = map.get(key);
            ChannelFuture channelFuture = channel.writeAndFlush(msg);
            if (!channelFuture.isSuccess()) {
                channel.writeAndFlush(new TextWebSocketFrame(msg));
            }
        }

    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded::"+ctx.channel().id().asLongText());
        InetSocketAddress socketAddress = (InetSocketAddress)ctx.channel().remoteAddress();
        String hostAddress = socketAddress.getAddress().getHostAddress();
        logger.info("IP:{}",hostAddress);
        String clientId = ctx.channel().id().toString();
        map.put(clientId,ctx.channel());
        logger.info("map:{}",map.toString());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved::"+ctx.channel().id().asLongText());
        String clientId = ctx.channel().id().toString();
        map.remove(clientId);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("exceptionCaught::"+cause.getMessage());
        String clientId = ctx.channel().id().toString();
        map.remove(clientId);
        ctx.close();
    }
}

最后通过spring 的CommandLineRunner 实现服务启动

@Component
@Order(1)
public class TaskRunner implements CommandLineRunner {

    private HttpServer httpServer = new HttpServer(10050);

    @Override
    public void run(String... args) throws Exception {
        httpServer.bind();
    }
}

3、Client 端

        3.1 websocket client

                hello.html代码如下 直接游览器允许

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    var webSocket;
    if (window.WebSocket) {
        webSocket = new WebSocket("ws://localhost:10050/ws");

        webSocket.onmessage = function (ev) {
            console.log("=====");
            var res = document.getElementById("response");
            res.value = res.value + "\n" + ev.data;
        }

        webSocket.onopen = function (ev) {
            var req = document.getElementById("response");
            req.value = "链接开启...."+"\n";
        }

        webSocket.onclose = function (ev) {
            var req = document.getElementById("response");
            req.value += "链接关闭...."+"\n";
        }

        function sendMessage(msg) {
            //alert("send?");
            if (!window.WebSocket){
                return;
            }
            if (webSocket.readyState === WebSocket.OPEN) {
                //alert("send!");
                webSocket.send(msg);
                document.getElementById("requestComment").value='';
            }else {
                alert("链接未开启!");
            }
        }

    } else {
        alert("浏览器不支持websocket")
    }
</script>
<form οnsubmit="false">
    <textarea style="height: 300px;width: 300px" id="requestComment" name="reqestcom"></textarea>
    <button type="button" οnclick="sendMessage(this.form.reqestcom.value)">发送</button>
    <textarea style="height: 300px;width: 300px" id="response"></textarea>
    <button type="button" οnclick="document.getElementById('response').value=''">清空</button>
</form>
</body>
</html>

        3.2 socket client

public class TestClient {
    private final String host;
    private final int port;

    public TestClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void run() throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap().group(bossGroup).channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline().addLast("decoder",new StringDecoder(CharsetUtil.UTF_8));
                            channel.pipeline().addLast("encoder",new StringEncoder(CharsetUtil.UTF_8));
                            channel.pipeline().addLast(new ClientHandler());
                        }
                    });

            ChannelFuture future = bootstrap.connect(host, port).sync();
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNext()){
                String msg = scanner.nextLine();
                future.channel().writeAndFlush(msg);
            }
        }finally {
            bossGroup.shutdownGracefully();
        }

    }

    public static void main(String[] args)throws Exception {
        new TestClient("127.0.0.1",10050).run();
    }
}

ClientHandler

/**
 * @author xiaocg
 */
public class ClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
        System.out.println(s.trim());
    }
}

4、最终测试截图

websocket 端发送

socket client 接收

socket client 发送

websocket 接收

代码工程详见

https://gitee.com/xiaochangg/study.git 下的data-transfer 工程

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Spring Boot是一个开源的Java框架,用于开发基于Java的企业级应用程序。它提供了简化的配置和一些内置功能,使开发人员可以更快速地构建可靠的应用程序。 Netty是一个基于Java的异步事件驱动的网络应用程序框架,它被广泛应用于开发高性能的网络服务器和客户端。通过使用Netty,开发人员可以轻松地编写可扩展的高性能的网络应用程序。 WebSocket是一种在Web浏览器和服务器之间进行全双工通信的协议。它允许服务器主动向客户端发送消息,并且这些消息可以是文本或二进制数据。在开发实时应用程序时,WebSocket是一个非常有用的协议。 Socket是一种用于在计算机网络上进行通信的编程接口。它允许不同的计算机之间建立连接,并通过互相发送数据来进行交互。Socket在开发网络应用程序时起到了关键的作用,它使得应用程序能够通过网络传输数据。 UniApp是一个基于Vue.js的跨平台开发框架,开发人员可以使用UniApp编写一套代码,然后通过编译器将其转换为iOS、Android和Web等不同平台的应用程序。UniApp的目标是提供一种简单和高效的方式,让开发人员可以同时开发多个平台的应用程序。 综上所述,Spring Boot是一个Java框架,Netty是一个用于开发高性能网络应用程序的框架,WebSocket是一种实现全双工通信的协议,Socket是一种用于网络通信的编程接口,UniApp是一个跨平台开发框架。这些技术和工具可以一起使用,开发人员可以利用它们构建高效、可扩展的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值