Netty очень удобна для связи между Java-программами, просто используйте клиент и сервер Netty напрямую. Но что, если клиент — это браузер (программы на Java обычно не разрабатывают веб-приложения), в этот раз клиент — это программа на JavaScript, а для взаимного общения используется протокол WebScoket.
Протокол веб-сокета
концепция
Те, кто знаком с сетевым программированием на Java, должны знать о программировании сокетов, технологии, используемой для связи между программами на Java (программирование сокетов также называется программированием сокетов, которое представляет собой абстракцию между транспортным уровнем и прикладным уровнем в TCP/IP). Протокол IP. Уровень сокетов, который предоставляет интерфейс для облегчения вызова прикладного уровня для реализации связи между сетями. Программирование сокетов далее делится на OIO и NIO, а Netty представляет собой коммуникационную структуру, инкапсулированную на основе NIO).
Тогда WebSocket легко понять: перед ним добавляется Web, чтобы указать, что этот протокол используется для связи в веб-приложениях (аналогично Socket, который также является двусторонним). Тогда в Интернете нет HTTP, так зачем вам WebSocket? Недостатком HTTP является то, что связь может быть инициирована только клиентом, а сервер не может активно инициировать связь с клиентом. Это приводит к тому, что если клиент хочет получить изменения или уведомления от сервера, он может только опрашивать или поддерживать длинное HTTP-соединение, что очень расточительно по ресурсам. WebSocket был создан, чтобы решить эту проблему.
Википедия:
WebSocket — это сетевой транспортный протокол, обеспечивающий полнодуплексную связь по одному TCP-соединению и расположенный на прикладном уровне модели OSI. WebSocket упрощает обмен данными между клиентом и сервером, позволяя серверу активно передавать данные клиенту. В API WebSocket браузеру и серверу нужно только выполнить рукопожатие, и между ними может быть создано постоянное соединение, и может быть выполнена двусторонняя передача данных.
Функции
Его основные особенности заключаются в следующем.
- На основе протокола TCP сервер может активно передавать информацию клиенту, а клиент также может активно отправлять информацию серверу.двусторонняя связь.
- Он имеет хорошую совместимость с протоколом HTTP. Порты по умолчанию также 80 и 443, иНа этапе рукопожатия используется протокол HTTP., поэтому во время рукопожатия защитить его непросто, и он может проходить через различные прокси-серверы HTTP.
- Формат данных относительно легкий, производительность невелика, а связь эффективна. Можно отправлять текст, а также двоичные данные.
- Ограничений по одному и тому же источнику нет, и клиенты могут взаимодействовать с любым сервером.
- Идентификатор протокола — ws (или wss, если используется шифрование SSL/TLS), а URL-адрес сервера — это URL-адрес.
Написание сервера WebSocket на основе Netty
Поскольку протокол WebSocket используется для связи веб-приложений, он по своей сути поддерживает запись серверов в Node. Таким образом, существует множество готовых серверов веб-сокетов на основе узлов. Сервер Java Websocket обычно инкапсулирует еще один уровень программирования Socket.Конечно, для его создания рекомендуется использовать Netty. Ведь Netty — это высокопроизводительный коммуникационный фреймворк, он тоже инкапсулирован на основе NIO, и работа с ним проще.
Основная программа сервера
Основная программа очень проста, как и обычная серверная часть Netty. Инкапсуляция Websocket в основном находится в процессоре.
public class WsServer {
public static void main(String[] args) {
// 一个主线程组(用于监听新连接并初始化通道),一个分发线程组(用于IO事件的处理)
EventLoopGroup mainGroup = new NioEventLoopGroup(1);
EventLoopGroup subGroup = new NioEventLoopGroup();
ServerBootstrap sb = new ServerBootstrap();
try {
sb.group(mainGroup, subGroup)
.channel(NioServerSocketChannel.class)
// 这里是一个自定义的通道初始化器,用来添加编解码器和处理器
.childHandler(new WsChannelInitializer());
// 绑定88端口,Websocket服务器的端口就是这个
ChannelFuture future = sb.bind(88).sync();
// 一直阻塞直到服务器关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放资源
mainGroup.shutdownGracefully();
subGroup.shutdownGracefully();
}
}
}
инициализатор канала
Поскольку протокол Websocket основан на протоколе HTTP (протокол HTTP используется на этапе рукопожатия), необходимо добавить кодек HTTP и процессор сообщений. Также добавьте обработчик Webscoket для обработки рукопожатия Webscoket и передачи данных и, наконец, добавьте собственный обработчик для обработки событий ввода-вывода для взаимодействия с клиентом.
public class WsChannelInitializer extends ChannelInitializer {
@Override
protected void initChannel(Channel ch) {
ChannelPipeline pipeline = ch.pipeline();
// websocket是基于http协议的,所以需要使用http编解码器
pipeline.addLast(new HttpServerCodec())
// 对写大数据流的支持
.addLast(new ChunkedWriteHandler())
// 对http消息的聚合,聚合成FullHttpRequest或FullHttpResponse
// 在Netty的编程中,几乎都会使用到这个handler
.addLast(new HttpObjectAggregator(1024 * 64));
// 以上三个处理器是对http协议的支持
// websocket 服务器处理的协议,并用于指定客户端连接的路由(这里指定的是 /ws)
// 这里的URL就是 ws://ip:port/ws
// 该处理器为运行websocket服务器承担了所有繁重的工作
// 它会负责websocket的握手以及处理控制帧
// websocket的数据传输都是以frames进行的
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
// 自定义的处理器
pipeline.addLast(new WsServerHandler());
}
}
пользовательский процессор
Этот процессор должен записывать и управлять всеми клиентскими каналами, получать сообщения от клиентов и обрабатывать соответствующие события ввода-вывода (можно привязать уникальный идентификатор пользователя клиента и канал, соответствующий клиенту, при инициализации соединения, а затем выполнить целевые push-сообщения).
// TextWebSocketFrame: 在Netty中,专门用于websocket处理文本消息的对象,frame是消息的载体
public class WsServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
/**
* 用于记录和管理所有客户端的channel
*/
private ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 获取客户端传输来的文本消息
String text = msg.text();
// 这个是自定义的日志工具类,可见其它文章
LogUtil.info("收到的文本消息:[{}]", text);
// 在这里可以判断消息类型(比如初始化连接、消息在客户端间传输等)
// 然后可以将客户端Channel与对应的唯一标识用Map关联起来,就可以做定向推送,而不是广播
// 写回客户端,这里是广播
clients.writeAndFlush(new TextWebSocketFrame("服务器收到消息: " + text));
}
/**
* 当客户端连接服务端(打开连接)后
* 获取客户端的channel,并放到ChannelGroup中进行管理
* @param ctx ChannelHandlerContext
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
// 不能在这里做关联,因为这里不能接受客户端的消息,是没法绑定的
clients.add(ctx.channel());
}
/**
* 当触发当前方法时,ChannelGroup会自动移除对应客户端的channel
* @param ctx ChannelHandlerContext
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
LogUtil.info("客户端断开连接,channel的长ID:[{}]", ctx.channel().id().asLongText());
LogUtil.info("客户端断开连接,channel的短ID:[{}]", ctx.channel().id().asShortText());
}
}