- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
概念:在网络应用中,需要实现某种编解码器。将原始字节数据与自定义消息数据进行相互转换。网络中都是以字节码的形式传输的。
对Netty而言,编解码器由两部分组成:编码器、解码器
Netty的编解码器实现了ChannelHandlerAdapter,也是一种特殊的ChannelHandler,所以依赖与ChannelPipeline,可以将多个编解码器链接在一起,以实现复杂的转换逻辑。
解码器示例:
public class DemoDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
String msg = byteBuf.toString(CharsetUtil.UTF_8);
list.add(msg);
}
}
通道里加入解码器:
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new DemoDecoder());
socketChannel.pipeline().addLast(new DemoNettyServerHandle());
}
编码器示例:
public class DemoEncoder extends MessageToMessageEncoder<String> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, String s, List<Object> list) throws Exception {
list.add(Unpooled.copiedBuffer(s,CharsetUtil.UTF_8));
}
}
同时具备编码与解码功能
效果如图:
代码如下:
public class NettyHttpServer {
private int port;
public NettyHttpServer(int port) {
this.port = port;
}
public static void main(String[] args) {
new NettyHttpServer(8090).run();
}
public void run(){
EventLoopGroup bossGroup=null;
EventLoopGroup workerGroup=null;
try{
bossGroup=new NioEventLoopGroup(1);
workerGroup=new NioEventLoopGroup();
ServerBootstrap serverBootstrap=new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,Boolean.TRUE)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//添加编解码器
socketChannel.pipeline().addLast(new HttpServerCodec());
socketChannel.pipeline().addLast(new NettyHttpServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class NettyHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
if(httpObject instanceof HttpRequest){
DefaultHttpRequest request=(DefaultHttpRequest)httpObject;
if(request.uri().equals("/favicon.ico")){
//图标不响应
return;
}
System.out.println("接收到请求:"+request.uri());
ByteBuf byteBuf = Unpooled.copiedBuffer("你好,我是服务端", CharsetUtil.UTF_8);
DefaultFullHttpResponse response=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,byteBuf);
//设置响应头
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/html;charset=utf-8");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,byteBuf.readableBytes());
channelHandlerContext.writeAndFlush(response);
}
}
}
简介:粘包和拆包是TCP网络编程中不可避免的,无论客户端还是服务端,当我们读取或发送消息的时候都要考虑TCP底层的粘包/拆包机制。
粘包产生的原因:
拆包产生的原因:
粘包和拆包的解决方案
Netty中粘包和拆包的解决方案
Netty提供了4种解码器来解决:
DelimiterBasedFrameDecoder示例:
ByteBuf byteBuf =
Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8));
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(2048, byteBuf));
LengthFieldBasedFrameDecoder构造器参数讲解:
public LengthFieldBasedFrameDecoder(
ByteOrder byteOrder,
int lengthFieldOffset,
int lengthFieldLength,
int lengthAdjustment,
int initialBytesToStrip,
boolean failFast)
byteOrder是指明Length字段是大端序还是小端序,因为Netty要读取Length字段的值,所以大端小端要设置好,默认Netty是大端序ByteOrder.BIG_ENDIAN。
maxFrameLength是指最大包长度,如果Netty最终生成的数据包超过这个长度,Netty就会报错。
lengthFieldOffset是指明Length的偏移位
lengthFieldLength是Length字段长度
lengthAdjustment 这个参数很多时候设为负数,这是最让小伙伴们迷惑的。下面我用一整段话来解释这个参数
当Netty利用lengthFieldOffset(偏移位)和lengthFieldLength(Length字段长度)成功读出Length字段的值后,Netty认为这个值是指从Length字段之后,到包结束一共还有多少字节,如果这个值是13,那么Netty就会再等待13个Byte的数据到达后,拼接成一个完整的包。但是更多时候,Length字段的长度,是指整个包的长度,如果是这种情况,当Netty读出Length字段的时候,它已经读取了包的4个Byte的数据,所以,后续未到达的数据只有9个Byte,即13 - 4 = 9,这个时候,就要用lengthAdjustment来告诉Netty,后续的数据并没有13个Byte,要减掉4个Byte,所以lengthAdjustment要设为 -4!!!
initialBytesToStrip,跳过的个数。比如这里initialBytesToStrip设置为4,那么Netty就会跳过前4位解析后面的内容
failFast 参数一般设置为true,当这个参数为true时,netty一旦读到Length字段,并判断Length超过maxFrameLength,就立即抛出异常。
示例:
@Override
public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {
for (int i=0;i<100;i++){
byte[] bytes = "你好,我是客户端".getBytes(CharsetUtil.UTF_8);
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeInt(bytes.length);
byteBuf.writeBytes(bytes);
channelHandlerContext.writeAndFlush(byteBuf);
}
}
第2个参数和第三个参数表示:0-4个字节是内容长度字段,第五个参数的4代表跳过前4个字节。
socketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(60535,0,4,0,4));
最后输出的内容:
1. WebSocket简介
WebSocket是一种在单个TCP连接上进行全双工通信的协议。相比HTTP协议,WebSocket具备如下特点:
应用场景:
2. 服务端开发
基于SpringBoot环境
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加thymeleaf依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.72.Final</version>
</dependency>
@Component
public class NettyWebSocketServer implements Runnable {
@Autowired
private NettyConfig nettyConfig;
@Autowired
private WebSocketChannelInit webSocketChannelInit;
private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
private EventLoopGroup wokerGroup = new NioEventLoopGroup();
@PreDestroy
public void close(){
bossGroup.shutdownGracefully();
wokerGroup.shutdownGracefully();
}
@Override
public void run() {
try{
ServerBootstrap serverBootstrap=new ServerBootstrap();
serverBootstrap.group(bossGroup,wokerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(webSocketChannelInit);
ChannelFuture channelFuture = serverBootstrap.bind(nettyConfig.getPort()).sync();
System.out.println("Netty服务端启动成功");
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
wokerGroup.shutdownGracefully();
}
}
}
@Component
public class WebSocketChannelInit extends ChannelInitializer {
@Autowired
private NettyConfig nettyConfig;
@Autowired
private WebSocketHandler webSocketHandler;
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
//对http协议的支持
pipeline.addLast(new HttpServerCodec());
//对大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
//post请求分为3部分。request line、request header、body
//HttpObjectAggregator将多个信息转化为单一的request或者response对象
pipeline.addLast(new HttpObjectAggregator(8000));
//将http协议升级为ws协议,websocket的支持
pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getPath()));
pipeline.addLast(webSocketHandler);
}
}
@Component
@ChannelHandler.Sharable //设置通道共享
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private List<Channel> channels=new ArrayList<>();
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
channels.add(ctx.channel());
System.out.println("有新的连接了...");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
channels.remove(ctx.channel());
System.out.println("连接下线了");
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
String text = textWebSocketFrame.text();
Channel currentChannel = channelHandlerContext.channel();
for (Channel channel:channels){
//自己不给自己发消息
if(!channel.equals(currentChannel)){
channel.writeAndFlush(new TextWebSocketFrame(text));
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
Channel channel = ctx.channel();
channels.remove(channel);
}
}
3. 前端js代码
$(function () {
//这里需要注意的是,prompt有两个参数,前面是提示的话,后面是当对话框出来后,在对话框里的默认值
var username = "";
while (true) {
//弹出一个输入框,输入一段文字,可以提交
username = prompt("请输入您的名字", ""); //将输入的内容赋给变量 name ,
if (username.trim() === "")//如果返回的有内容
{
alert("名称不能输入空")
} else {
$("#username").text(username);
break;
}
}
var ws = new WebSocket("ws://localhost:8081/chatService");
ws.onopen = function () {
console.log("连接成功.")
};
ws.onmessage = function (evt) {
showMessage(evt.data);
};
ws.onclose = function (){
console.log("连接关闭")
};
ws.onerror = function (){
console.log("连接异常")
};
function showMessage(message) {
// 张三:你好
var str = message.split(":");
$("#msg_list").append('<li class="active"}>\n' +
' <div class="main">\n' +
' <img class="avatar" width="30" height="30" src="/img/user.png">\n' +
' <div>\n' +
' <div class="user_name">'+str[0]+'</div>\n' +
' <div class="text">'+str[1]+'</div>\n' +
' </div> \n' +
' </div>\n' +
' </li>');
// 置底
setBottom();
}
$('#my_test').bind({
focus: function (event) {
event.stopPropagation();
$('#my_test').val('');
$('.arrow_box').hide()
},
keydown: function (event) {
event.stopPropagation();
if (event.keyCode === 13) {
if ($('#my_test').val().trim() === '') {
this.blur();
$('.arrow_box').show();
setTimeout(this.focus(),1000);
} else {
$('.arrow_box').hide();
//发送消息
sendMsg();
this.blur();
setTimeout(this.focus())
}
}
}
});
$('#send').on('click', function (event) {
event.stopPropagation();
if ($('#my_test').val().trim() === '') {
$('.arrow_box').show()
} else {
sendMsg();
}
});
function sendMsg() {
var message = $("#my_test").val();
$("#msg_list").append('<li class="active"}>\n' +
' <div class="main self">\n' +
' <div class="text">'+message+'</div>\n' +
' </div>\n' +
' </li>');
$("#my_test").val('');
//发送消息
message = username + ":" + message;
ws.send(message);
// 置底
setBottom();
}
// 置底
function setBottom() {
// 发送消息后滚动到底部
var container = $('.m-message');
var scroll = $('#msg_list');
container.animate({
scrollTop: scroll[0].scrollHeight - container[0].clientHeight + container.scrollTop() + 100
});
}
});
书山有路勤为径,学海无涯苦作舟
我有聊天应用程序,可以一对一发送消息(fromId/toId)。我想升级它的聊天室。我怎样才能做到这一点? ChatingRoom 需要什么数据库结构?我还需要做什么? 我的 User.swift 模
本文实例讲述了python socket多线程通讯方法。分享给大家供大家参考,具体如下: ?
我正在使用简单的套接字连接创建一个聊天室。我有一个服务器和客户端程序。服务器在端口 225 上运行,然后当我在端口 225 上运行客户端以便它们可以读/写套接字时,客户端立即停止并显示错误消息 jav
我可以创建一个粘液室。但是如果我将房间名称指定为数据库中现有的房间名称,服务器会拒绝它。那么是否可以在 ejabberd 服务器(MYSQl 后端)中创建两个具有相同名称的房间? 如果可能的话,后果是
我正在尝试使用 PHP 创建一个聊天室(顺便说一句,它正在工作),但只显示消息,而不显示他们的用户名。我已经为他们创建了数据库、用户名和消息。我不知道为什么他们的用户名没有显示 ".$extract[
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我想在我的应用程序中实现群聊,为此我正在使用 MUC 聊天室来实现它。 在这里,我想向房间添加一个成员列表(我有 JID)。我想在内部将它们添加到列表中的所有成员。我如何在不邀请他们的情况下添加他们。
我正在尝试在 Rails 中创建类似 Whatsapp 的消息应用程序服务器端,并进行私有(private)对话。现在,我正在尝试实现应用程序的实时部分 - 我正在使用 websocket-rails
我正在尝试为我的网站制作一个 PHP/JavaScript 聊天系统。如何在不刷新页面的情况下做到这一点? JavaScript 可以:--动态添加文本到文本框。 PHP 可以:--将聊天内容保存到聊
我已经为多个客户端完全开发了一个聊天室,其多线程服务器可以完成这项工作,但仅限于我的本地计算机。我想超越这个,让这个聊天室通过互联网工作。到目前为止,我已经在路由器上对 TCP 协议(protocol
我是 Angular 的初学者,我正在尝试构建一个聊天室应用程序,以此自学如何在该框架中进行开发。 我在后端使用 PHP 和 Yii 将 RESTful 数据发送回 Angular 应用程序。我知道我
我正在寻找好的建议来制作这个聊天室: 使用 Java Swing。我已经尝试了所有 java swing 组件 3 天,但我仍然不知道该选择什么。 容器还必须插入 JprogressBars 以进行文
嗨,我正在努力让两件事同时工作...... channels2 chat room例子可以开始,但我想添加一个功能,知道房间里有多少人。我通过更新房间模型来做到这一点。 然后我想要一个仪表板来显示当前
我正在编写一个需要持久存储对话的多聊天室应用程序(即新用户应该能够看到旧消息)。如果有帮助,我正在使用 socket.io。 目前,当用户登录到一个房间时,我的 Node 应用程序会检查是否有人去过那
我试图扩展doc中关于TCP的代码来制作一个简单的聊天室,简单地说,几个客户端连接到服务器,一个客户端发送一个字符串,服务器将字符串广播给所有客户端......我写的代码显示下面,它不起作用,有人可以
如何在 Microsoft-Teams 中自动向聊天室发布消息?这是用于单向消息传递:即发布消息,而不是阅读消息。 这里的大局是我们正在评估不同的群聊解决方案,其中一项要求是从各种服务和程序向聊天室发
我创建了一个小型的 jquery 和 php 聊天室,其中包含一些 .get 和 .post 函数以及用于将数据读写到 sql server 的 php 文档。它工作正常,但小问题是当有人发布一些东西
我想实现的是自动下载附件功能,无需进入 ChatRoom,使用 QuickBlox SDK iOS 版本 2.0.12, 为此,我想在登录后启动用户所属的所有对话框(聊天室)。 使用当前的 API,用
所以我有聊天室工作所以如果收件人是 All 或 ALL 它广播我有这个工作所以如果你的名字是'Sam'它发送它与“私有(private)消息:”文本我不知道如何检查用户名的花名册并将其私下发送给该用户
我正在尝试创建基于移动设备的聊天应用。 基本上,我希望用户使用他们的 Facebook 帐户连接到 Facebook(使用 XMPPFramework) 我想要预先存在的房间供用户加入。我注意到 XM
我是一名优秀的程序员,十分优秀!