注:源代碼來自享學課堂,略有修改,學習之後所做筆記,方便回顧,也給大家一個參考
目錄
單播
單播沒有服務端和客戶端概念
迴應端ansoweHandler
和平常的handler沒兩樣,只是writeAndFlush的時候,不是byteBuf,而是DatagramPacket了
public class MyAnswerHandler extends
SimpleChannelInboundHandler<DatagramPacket> {
/**
* 應答的具體內容從常量字符串數組中取得,由nextQuote方法隨機獲取
*/
private static final String[] DICTIONARY = {
"只要功夫深,鐵棒磨成針。",
"舊時王謝堂前燕,飛入尋常百姓家。",
"洛陽親友如相問,一片冰心在玉壺。",
"一寸光陰一寸金,寸金難買寸光陰。",
"老驥伏櫪,志在千里,烈士暮年,壯心不已"};
private static Random r = new Random();
private String nextQuote() {
return DICTIONARY[r.nextInt(DICTIONARY.length - 1)];
}
@Override
protected void channelRead0(ChannelHandlerContext ctx,
DatagramPacket packet)
throws Exception {
//獲得請求
String req = packet.content().toString(CharsetUtil.UTF_8);
System.out.println(req);
if (MyUdpQuestion.QUESTION.equals(req)) {
/**
* 重新 new一個DatagramPacket對象,我們通過packet.sender()
* 來獲取發送者的地址。
* 重新發送出去!
*/
ctx.writeAndFlush(
new DatagramPacket(
Unpooled.copiedBuffer(
MyUdpAnswer.ANSWER + nextQuote(),
CharsetUtil.UTF_8),
packet.sender()));
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
cause.printStackTrace();
}
}
迴應端主函數
只是channel變成了.channel(NioDatagramChannel.class),其他沒有變化
public class MyUdpAnswer {
public final static String ANSWER = "古詩來了:";
public void run(int port) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
/*和tcp的不同,udp沒有接受連接的說法,所以即使是接收端,也使用Bootstrap*/
Bootstrap b = new Bootstrap();
/*由於我們用的是UDP協議,所以要用NioDatagramChannel來創建*/
b.group(group)
.channel(NioDatagramChannel.class)
.handler(new MyAnswerHandler());
//沒有接受客戶端連接的過程,監聽本地端口即可
ChannelFuture f = b.bind(port).sync();
System.out.println("應答服務已啓動.....");
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new MyUdpAnswer().run(port);
}
}
發送端
讀寫數據都是使用DatagramPacket
public class MyQuestionHandler extends
SimpleChannelInboundHandler<DatagramPacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg)
throws Exception {
//獲得應答,DatagramPacket提供了content()方法取得報文的實際內容
String response = msg.content().toString(CharsetUtil.UTF_8);
if (response.startsWith(MyUdpAnswer.ANSWER)) {
System.out.println(response);
ctx.close();
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//將UDP請求的報文以DatagramPacket打包發送給接受端
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer("告訴我一句古詩", CharsetUtil.UTF_8),
new InetSocketAddress("127.0.0.1", 8080)
));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
發送端主函數
ublic class MyUdpQuestion {
public final static String QUESTION = "告訴我一句古詩";
public void run(int port) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
/*由於我們用的是UDP協議,所以要用NioDatagramChannel來創建*/
.channel(NioDatagramChannel.class)
.handler(new MyQuestionHandler());
//不需要建立連接
Channel ch = b.bind(0).sync().channel();
//不知道接收端能否收到報文,也不知道能否收到接收端的應答報文
// 所以等待15秒後,不再等待,關閉通信
if (!ch.closeFuture().await(15000)) {
System.out.println("查詢超時!");
}
} catch (Exception e) {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int answerPort = 8080;
new MyUdpQuestion().run(answerPort);
}
}
執行結果
服務端
應答服務已啓動.....
告訴我一句古詩
發送端
古詩來了:舊時王謝堂前燕,飛入尋常百姓家。
廣播:模擬日誌發送:
廣播端:
.option(ChannelOption.SO_BROADCAST,true)
接收端:
//設置套接字選項 SO_BROADCAST
.option(ChannelOption.SO_BROADCAST, true)
//允許端口重用
.option(ChannelOption.SO_REUSEADDR,true)
日誌發送對象
@Data
public class LogMsg {
public static final byte SEPARATOR = (byte) ':';
/**
* 源的 InetSocketAddress
*/
private final InetSocketAddress source;
/**
* 消息內容
*/
private final String msg;
/**
* 消息id
*/
private final long msgId;
/**
* 消息發送或者接受的時間
*/
private final long time;
/**
* 用於傳入消息的構造函數
*/
public LogMsg(String msg) {
this(null, msg, -1, System.currentTimeMillis());
}
/**
* 用於傳出消息的構造函數
*/
public LogMsg(InetSocketAddress source, long msgId,
String msg) {
this(source, msg, msgId, System.currentTimeMillis());
}
public LogMsg(InetSocketAddress source, String msg, long msgId, long time) {
this.source = source;
this.msg = msg;
this.msgId = msgId;
this.time = time;
}
}
日誌發送工具
public class LogConst {
public final static int MONITOR_SIDE_PORT = 9998;
private static final String[] LOG_INFOS = {
"20180912:mark-machine:Send sms to 10001",
"20180912:lison-machine:Send email to james@enjoyedu",
"20180912:james-machine:Happen Exception",
"20180912:peter-machine:人生不能象做菜,把所有的料都準備好了才下鍋",
"20180912:deer-machine:牽着你的手,就象左手牽右手沒感覺,但砍下去也會痛!",
"20180912:king-machine:我聽別人說這世界上有一種鳥是沒有腳的," +
"它只能一直飛呀飛呀,飛累了就在風裏面睡覺,這種鳥一輩子只能下地一次," +
"那一次就是它死亡的時候.",
"20180912:mark-machine:多年以後我有個綽號叫西毒,任何人都可以變得狠毒," +
"只要你嘗試過什麼叫妒嫉.我不介意其他人怎麼看我," +
"我只不過不想別人比我更開心.我以爲有一些人永遠不會妒嫉," +
"因爲他太驕傲 . 在我出道的時候,我認識了一個人," +
"因爲他喜歡在東邊出沒,所以很多年以後,他有個綽號叫東邪.",
"20180912:lison-machine:做人如果沒有夢想,那和鹹魚有什麼區別",
"20180912:james-machine:恐懼讓你淪爲囚犯,希望讓你重獲自由," +
"堅強的人只能救贖自己,偉大的人才能拯救別人." +
"記着,希望是件好東西,而且從沒有一樣好東西會消逝." +
"忙活,或者等死.",
"20180912:peter-machine:世界上最遠的距離不是生和死," +
"而是我站在你的面前卻不能說:我愛你",
"20180912:deer-machine:成功的含義不在於得到什麼," +
"而是在於你從那個奮鬥的起點走了多遠.",
"20180912:king-machine:一個人殺了一個人,他是殺人犯.是壞人," +
"當一個人殺了成千上萬人後,他是英雄,是大好人",
"20180912:mark-machine:世界在我掌握中,我卻掌握不住對你的感情",
"20180912:lison-machine:我害怕前面的路,但是一想到你,就有能力向前走了。",
"20180912:james-machine:早就勸你別吸菸,可是煙霧中的你是那麼的美," +
"叫我怎麼勸得下口。",
"20180912:peter-machine:如果你只做自己能力範圍之內的事情,就永遠無法進步。" +
"昨天已成爲歷史,明天是未知的,而今天是上天賜予我們的禮物," +
"這就是爲什麼我們把它叫做現在!",
"20180912:deer-machine:年輕的時候有賊心沒賊膽,等到了老了吧," +
"賊心賊膽都有了,可賊又沒了。",
"20180912:king-machine:別看現在鬧得歡,小心將來拉清單。"};
private final static Random r = new Random();
public static String getLogInfo(){
return LOG_INFOS[r.nextInt(LOG_INFOS.length-1)];
}
}
廣播主函數
public class LogEventBroadcaster {
private final EventLoopGroup group;
private final Bootstrap bootstrap;
public LogEventBroadcaster(InetSocketAddress remoteAddress) {
group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
//引導該 NioDatagramChannel(無連接的)
bootstrap.group(group).channel(NioDatagramChannel.class)
//設置 SO_BROADCAST 套接字選項
.option(ChannelOption.SO_BROADCAST,true)
.handler(new LogEventEncoder(remoteAddress));
}
public void run() throws Exception {
//綁定 Channel
Channel ch = bootstrap.bind(0).sync().channel();
long count = 0;
//啓動主處理循環,模擬日誌發送
for (;;) {
ch.writeAndFlush(new LogMsg(null, ++count,
LogConst.getLogInfo()));
try {
//休眠 2 秒,如果被中斷,則退出循環;
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.interrupted();
break;
}
}
}
public void stop() {
group.shutdownGracefully();
}
public static void main(String[] args) throws Exception {
//創建並啓動一個新的 UdpQuestionSide 的實例
LogEventBroadcaster broadcaster = new LogEventBroadcaster(
//表明本應用發送的報文並沒有一個確定的目的地,也就是進行廣播
new InetSocketAddress("255.255.255.255",
LogConst.MONITOR_SIDE_PORT));
try {
broadcaster.run();
}
finally {
broadcaster.stop();
}
}
}
廣播主函數編碼
public class LogEventEncoder extends MessageToMessageEncoder<LogMsg> {
private final InetSocketAddress remoteAddress;
/**
* LogEventEncoder 創建了即將被髮送到指定的 InetSocketAddress的 DatagramPacket 消息
*/
public LogEventEncoder(InetSocketAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
@Override
protected void encode(ChannelHandlerContext channelHandlerContext,
LogMsg logMsg, List<Object> out) throws Exception {
byte[] msg = logMsg.getMsg().getBytes(CharsetUtil.UTF_8);
//容量的計算:兩個long型+消息的內容+分割符
ByteBuf buf = channelHandlerContext.alloc()
.buffer(8*2 + msg.length + 1);
//將發送時間寫入到 ByteBuf中
buf.writeLong(logMsg.getTime());
//將消息id寫入到 ByteBuf中
buf.writeLong(logMsg.getMsgId());
//添加一個 SEPARATOR
buf.writeByte(LogMsg.SEPARATOR);
//將日誌消息寫入 ByteBuf中
buf.writeBytes(msg);
//將一個擁有數據和目的地地址的新 DatagramPacket 添加到出站的消息列表中
out.add(new DatagramPacket(buf, remoteAddress));
}
}
廣播接收者
public class LogEventMonitor {
private final EventLoopGroup group;
private final Bootstrap bootstrap;
public LogEventMonitor(InetSocketAddress address) {
group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
//引導該 NioDatagramChannel
bootstrap.group(group)
.channel(NioDatagramChannel.class)
//設置套接字選項 SO_BROADCAST
.option(ChannelOption.SO_BROADCAST, true)
//允許端口重用
.option(ChannelOption.SO_REUSEADDR,true)
.handler( new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel)
throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new LogEventDecoder());
pipeline.addLast(new LogEventHandler());
}
} )
.localAddress(address);
}
public Channel bind() {
//綁定 Channel。注意,DatagramChannel 是無連接的
return bootstrap.bind().syncUninterruptibly().channel();
}
public void stop() {
group.shutdownGracefully();
}
public static void main(String[] args) throws Exception {
//構造一個新的 UdpAnswerSide並指明監聽端口
LogEventMonitor monitor = new LogEventMonitor(
new InetSocketAddress(LogConst.MONITOR_SIDE_PORT));
try {
//綁定本地監聽端口
Channel channel = monitor.bind();
System.out.println("UdpAnswerSide running");
channel.closeFuture().sync();
} finally {
monitor.stop();
}
}
}
廣播接受者handler
public class LogEventHandler
extends SimpleChannelInboundHandler<LogMsg> {
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) throws Exception {
//當異常發生時,打印棧跟蹤信息,並關閉對應的 Channel
cause.printStackTrace();
ctx.close();
}
@Override
public void channelRead0(ChannelHandlerContext ctx,
LogMsg event) throws Exception {
//創建 StringBuilder,並且構建輸出的字符串
StringBuilder builder = new StringBuilder();
builder.append(event.getTime());
builder.append(" [");
builder.append(event.getSource().toString());
builder.append("] :[");
builder.append(event.getMsgId());
builder.append("] :");
builder.append(event.getMsg());
//打印 LogMsg 的數據
System.out.println(builder.toString());
}
}
接受者解碼
public class LogEventDecoder extends MessageToMessageDecoder<DatagramPacket> {
@Override
protected void decode(ChannelHandlerContext ctx,
DatagramPacket datagramPacket, List<Object> out)
throws Exception {
//獲取對 DatagramPacket 中的數據(ByteBuf)的引用
ByteBuf data = datagramPacket.content();
//獲得發送時間
long sendTime = data.readLong();
System.out.println("接受到"+sendTime+"發送的消息");
//獲得消息的id
long msgId = data.readLong();
//獲得分隔符SEPARATOR
byte sepa = data.readByte();
//獲取讀索引的當前位置,就是分隔符的索引+1
int idx = data.readerIndex();
//提取日誌消息,從讀索引開始,到最後爲日誌的信息
String sendMsg = data.slice(idx ,
data.readableBytes()).toString(CharsetUtil.UTF_8);
//構建一個新的 LogMsg 對象,並且將它添加到(已經解碼的消息的)列表中
LogMsg event = new LogMsg(datagramPacket.sender(),
msgId, sendMsg);
//作爲本handler的處理結果,交給後面的handler進行處理
out.add(event);
}
}
結果
接受者接收到的日誌
接受到1590052211879發送的消息
1590052211962 [/172.17.4.102:61921] :[1] :20180912:lison-machine:做人如果沒有夢想,那和鹹魚有什麼區別
接受到1590052213884發送的消息
1590052213884 [/172.17.4.102:61921] :[2] :20180912:lison-machine:Send email to james@enjoyedu