定义:在 HTTP 服务端和 HTTP 客户端(通常是浏览器)之间实现全双工 Socket 连接。
服务端启动WebSocket:
//配置webSocket处理器
server.webSocketHandler(webSocket -> {});
//配置握手
server.webSocketHandshakeHandler(handshake -> {
if (handshake.path().equals("/myapi")) {
//拒绝握手
handshake.reject();
}else{
//接受连接,并结束握手
Future<ServerWebSocket> accept = handshake.accept();
}
});
客户端启动WebSocketClient:
WebSocketClientOptions options = new WebSocketClientOptions()
.setDefaultHost("localhost")
.setDefaultPort(8080);
WebSocketClient webSocketClient = vertx.createWebSocketClient();
webSocketClient.connect("/ws", res -> {
if(res.succeeded()){
WebSocket webSocket = res.result();
}
else{
log.error(res.cause());
}
});
写入二进制帧
多帧写入
//二进制帧
WebSocketFrame frame1 = WebSocketFrame.binaryFrame(buffer1, false);
webSocket.writeFrame(frame1);
//continuation frame:发送剩余的二进制帧
WebSocketFrame frame2 = WebSocketFrame.continuationFrame(buffer2, false);
webSocket.writeFrame(frame2);
// 写最终帧,标记为final
WebSocketFrame frame3 = WebSocketFrame.continuationFrame(buffer2, true);
webSocket.writeFrame(frame3);
单帧写入:webSocket.writeFinalBinaryFrame(buff);
读取二进制帧:webSocket.frameHandler(frame -> {});
处理完记得使用close方法关闭连接
管道传输
FileSystem fs = vertx.fileSystem();
AsyncFile asyncFile = fs.openBlocking("/file.txt", new OpenOptions());
asyncFile.pipeTo(webSocket);
定义:在应用程序的不同部分之间、同一 Vert.x 实例中的不同应用程序之间、Vert.x 集群中的不同实例之间 安全地共享数据
Local maps:
允许在同一Vertx实例中的不同Event Loop之间共享
可作为键值和值的类型:不可变(String,int)和实现Shareable接口的类型(Buffer,JSON对象或自定义实现);确保在Vertx在不同Event Loop线程不共享可变的数据
//获取共享数据
SharedData sharedData = vertx.sharedData();
//获取名称自定义的LocalMap
LocalMap<String, String> map1 = sharedData.getLocalMap("mymap1");
//存放数据
map1.put("foo", "bar");
LocalMap<String, Buffer> map2 = sharedData.getLocalMap("mymap2");
//Buffer将会在添加到Map之前拷贝,因为不是不可变的数据类型
map2.put("eek", Buffer.buffer().appendInt(123));
// 之后,在其他应用中获取该LocalMap
map1 = sharedData.getLocalMap("mymap1");
String val = map1.get("foo");
map2 = sharedData.getLocalMap("mymap2");
Buffer buff = map2.get("eek");
System.out.println(val);
Async Map:
允许在集群中的多个vertx节点中共享
可作为键值和值的类型:不可变(String,int)、实现 ClusterSerializable
接口的类型(Buffer、json对象)、实现 java.io.Serializable
接口的类型
SharedData sharedData = vertx.sharedData();
//获取与其他节点的共享数据
sharedData.<String, String>getAsyncMap("mymap", res -> {
if (res.succeeded()) {
AsyncMap<String, String> map = res.result();
} else {
// 发生错误
}
});
//如果应用不需要和其它vertx节点共享数据,可以只获取一个本地的 map
sharedData.<String, String>getLocalAsyncMap(...)
异步锁:
定义:异步锁
允许在集群中获取独占锁。 适用于:同一时刻仅在一个节点上执行某些操作或访问某个资源。
SharedData sharedData = vertx.sharedData();
//获取锁时得到通知
sharedData.getLock("mylock", res -> {
if (res.succeeded()) {
// 获得锁
Lock lock = res.result();
// 5秒后我们释放该锁
vertx.setTimer(5000, tid -> lock.release());
} else {
// 发生错误
}
});
//如果应用不需要和其它vertx节点共享数据,可以只获取一个本地的异步锁
sharedData.getLocalLock()
异步的原子计数器:
SharedData sharedData = vertx.sharedData();
sharedData.getCounter("mycounter", res -> {
if (res.succeeded()) {
Counter counter = res.result();
} else {
// 发生错误
}
});
//如果应用不需要和其它vertx节点共享数据,可以只获取一个本地的原子计数器
sharedData.getLocalCounter(...)
UDP以数据报为单位进行传输,通常情况不能发送大于MTU的数据报(数据链路层中的单次传输数据的最大尺寸)
发送数据报
//创建 DatagramSocke
DatagramSocket socket = vertx.createDatagramSocket(new DatagramSocketOptions());
Buffer buffer = Buffer.buffer("content");
//发送数据报
socket.send(buffer, 1234, "127.0.0.1", asyncResult -> {
System.out.println("Send succeeded? " + asyncResult.succeeded());
});
接收数据报
DatagramSocket socket = vertx.createDatagramSocket(new DatagramSocketOptions());
socket.listen(1234, "0.0.0.0", asyncResult -> {
if (asyncResult.succeeded()) {
socket.handler(packet -> {
// 对包进行处理
});
}
});
发送多播数据报:通过加入到同一个可发送数据包的多播组,允许多个Socket接收相同的数据包。
Buffer buffer = Buffer.buffer("content");
// 发送Buffer到多播地址
socket.send(buffer, 1234, "230.0.0.1", asyncResult -> {});
接收多播数据报
DatagramSocket socket = vertx.createDatagramSocket(new DatagramSocketOptions());
socket.listen(1234, "0.0.0.0", asyncResult -> {
if (asyncResult.succeeded()) {
socket.handler(packet -> {
// 对数据包进行处理
});
// 加入多播组
socket.listenMulticastGroup("230.0.0.1", asyncResult2 -> {
if (asyncResult2.succeeded()) {
// 现在将接收组的数据包
// 接收完后可取消监听
socket.unlistenMulticastGroup("230.0.0.1", asyncResult3 -> {});
}
});
}
});
作用:在 Vert.x 中,异步的写调用是立即返回的,而写操作实际是在内部队列中排队写入。若写入对象的速度比实际写入底层数据资源速度快, 那么写入队列就会无限增长, 最终导致内存耗尽。所以vertx提供了控制流式传输的接口(类比管道):ReadStream(输入流)、WriteStream(输出流)。
流式传输的过程:
节点 A (数据源/生产者):节点 A 从某个地方获取数据(读取本地文件、接收网络数据等)。然后将这些数据写入到它的输出流 (Output Stream)。这个输出流代表了数据离开节点 A 的通道。
传输通道:节点 A 的输出流通过某种机制连接到节点 B 的输入流。这可以是一个网络连接(如 TCP/IP socket)、一个操作系统管道 (pipe)、一个内存缓冲等。
节点 B (数据接收端/消费者):节点 B 从它的输入流 (Input Stream) 中读取数据。这个输入流代表了数据进入节点 B 的通道。
代码:
方式一
NetServer server = vertx.createNetServer(
new NetServerOptions().setPort(1234).setHost("localhost")
);
server.connectHandler(sock -> {
sock.handler(buffer -> {
//传输数据
sock.write(buffer);
//如果 WriteStream 已满
if (sock.writeQueueFull()) {
//暂停读取
sock.pause();
//当写队列准备接收更多的数据时,drainHandler事件处理器将被调用
sock.drainHandler(done -> {
//恢复NetSocket 的状态
sock.resume();
});
}
});
}).listen();
方式二:
server.connectHandler(sock -> {
sock.pipeTo(sock);
}).listen();
方式三:
server.connectHandler(sock -> {
// 创建异步操作管道
Pipe<Buffer> pipe = sock.pipe();
// 打开目标文件
fs.open("/path/to/file", new OpenOptions(), ar -> {
if (ar.succeeded()) {
AsyncFile file = ar.result();
// 用管道传输socket当中的信息到文件中,并最终关闭文件
pipe.to(file);
} else {
//关闭管道
sock.close();
}
});
}).listen();
ReadStream
ReadStream
(可读流) 接口的实现类包括: HttpClientResponse
, DatagramSocket
, HttpClientRequest
, HttpServerFileUpload
, HttpServerRequest
, MessageConsumer
, NetSocket
, WebSocket
, TimeoutStream
, AsyncFile
。
handler
: 设置一个处理器,从 ReadStream
读取对象pause
: 暂停处理器,暂停时,处理器中将不会收到任何对象fetch
: 指定从 ReadStream
中抓取多大字节的数据,即在下一次handle中会收到对应字节数的数据。resume
: 恢复处理器,若任何对象到达目的地则handler将被触发;等价于 fetch(Long.MAX_VALUE)
exceptionHandler
: ReadStream发生异常时被调用endHandler
: 当数据读取完毕时被调用。ReadStream
有 flowing 和 fetch 两个模式:
ReadStream
是 flowing 模式ReadStream
处于 flowing 模式, ReadStream
中的元素被传输到handlerReadStream
处于 fetch 模式,只会将指定数量的元素传输到handlerpause
, resume
和 fetch
会改变 ReadStream
的模式
resume()
设置 ReadStream
为 flowing 模式pause()
设置 ReadStream
为 fetch 模式 并设置demand值为0fetch(long)
请求指定数量的 ReadStream
元素并将该数量加到目前的demand值当中WriteStream
WriteStream
(可写流)接口的实现类包括:HttpClientRequest
,HttpServerResponse
WebSocket
,NetSocket
和 AsyncFile
。
write
: 往WriteStream
写入一个对象,该方法将永远不会阻塞, 内部是排队写入。setWriteQueueMaxSize
: 设置写入队列容量—— writeQueueFull
在队列 写满 时返回 true
。writeQueueFull
: 若写队列被认为已满,则返回 true
。exceptionHandler
: WriteStream
发生异常时调用。drainHandler
: 判定 WriteStream
有剩余空间时调用。定义:一个自由分割、读取指定数据大小的工具
每次读取分割符之前的数据
//初始化分隔符
RecordParser parser = RecordParser.newDelimited("\n", h -> {
//总共返回两次
//第一次返回HELLO、第二次返回HOW ARE YOU
System.out.println(h.toString());
});
//手动指定分隔符
parser.delimitedMode();
parser.handle(Buffer.buffer("HELLO\nHOW ARE Y"));
parser.handle(Buffer.buffer("OU?\nI AM"));
每次读取固定长度的数据
//初始化长度
RecordParser parser = RecordParser.newFixed(4, h -> {
System.out.println(h.toString());
});
//手动设置长度
parser.fixedSizeMode();
定义:非阻塞解析器,用来处理体积非常大的JSON
代码:
JsonParser parser = JsonParser.newParser();
parser.handler(event -> {
switch (event.type()) {
case START_ARRAY:
log.info("[");
break;
case START_OBJECT:
log.info("{");
// 切换到 value-mode: 后续 VALUE 事件中直接返回字段的完整嵌套对象/数组值 (JsonObject/JsonArray),
// 并跳过这些嵌套结构内部的 START/END/VALUE 事件。适用于直接提取嵌套体。
parser.objectValueMode();
break;
//每处理一个字段,都会触发该事件
case VALUE:
//直接提前整个JSON对象
if(event.fieldName().equals("address")){
Address address = event.mapTo(Address.class);
log.info(address);
}
log.info(event.fieldName() + ":"+ event.value());
break;
case END_OBJECT:
log.info("}");
// 设置为 event-mode,所以解析器重新触发 start-end 事件,如果在之前的事件中设置过objectValueMode,则必须调用
parser.objectEventMode();
break;
case END_ARRAY:
log.info("]");
break;
}
});
Buffer jsonBuffer = Buffer.buffer(
"[" +
"{" +
"\"firstName\": \"Bob\"," +
//嵌套对象Address
"\"address\": {" +
"\"city\": \"Paris\"," +
"\"country\": \"France\"" +
"}" +
"}," +
"{" +
"\"firstName\": \"Luke\"," +
"\"address\": {" +
"\"city\": \"Daisy Town\"," +
"\"country\": \"USA\"" +
"}" +
"}" +
"]"
);
parser.handle(jsonBuffer);
parser.end();
为避免在Event Loop 中直接调用阻塞式操作,通过以下几种方式进行调用
executeBlocking
//返回Future对象
vertx.executeBlocking(() -> {
System.out.println("执行阻塞任务");
return null;
},false) //如果 executeBlocking 在同一个上下文环境中(如:同一个 Verticle 实例)被调用了多次, 那么这些不同的 executeBlocking 代码块会顺序执行,此时不关心执行顺序,可以设置为false,这时会并行处理
Worker Executor
WorkerExecutor executor = vertx.createSharedWorkerExecutor("my-worker-pool");
executor.executeBlocking(()-> {
System.out.println("执行阻塞任务");
return null;
});
//Worker Executor 在不需要的时候必须被关闭:
executor.close();
功能:
使用:Vert.x默认使用Hazelcast集群管理器, 但它可以简单被替换成其他实现类。 Vert.x在运行时使用Java的服务加载器ServiceLoader
功能在类路径中查找 ClusterManager
的实例,从而定位集群管理器。所以在 Maven/Gradle 项目中,只需引入集群管理器的依赖即可。或者以编程的方式:
Vertx vertx = Vertx.builder().withClusterManager(...).build()
由于异步的Event Loop线程无法输出堆栈信息(System.out)和debug调试,所以应经常用异常处理器和vertx提供的日志工具来打印信息
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;