詳解UDP協(xié)議格式及在java中的使用
UDP是面向無(wú)連接的通訊協(xié)議,由于通訊不需要連接,所以可以實(shí)現(xiàn)廣播發(fā)送。UDP通訊時(shí)不需要接收方確認(rèn),屬于不可靠的傳輸,可能會(huì)出現(xiàn)丟包現(xiàn)象,實(shí)際應(yīng)用中要求程序員編程驗(yàn)證。
UDP適用于DNS、視頻音頻等多媒體通信、廣播通信(廣播、多播)。例如我們常用的QQ,就是一個(gè)以UDP為主,TCP為輔的通訊協(xié)議。
UDP報(bào)文格式如下:

UDP首部有8個(gè)字節(jié),由4個(gè)字段構(gòu)成,每個(gè)字段都是兩個(gè)字節(jié),
- 源端口:數(shù)據(jù)發(fā)送方的端口號(hào).
- 目的端口:數(shù)據(jù)接收方的端口號(hào)。
- 長(zhǎng)度:UDP數(shù)據(jù)報(bào)的整個(gè)長(zhǎng)度(包括首部和數(shù)據(jù)),其最小值為8(只有首部)。
- 校驗(yàn)和:檢測(cè)UDP數(shù)據(jù)報(bào)在傳輸中是否有錯(cuò),有錯(cuò)則丟棄。
可以使用nc發(fā)送UDP數(shù)據(jù)包:echo hello | nc -uv 127.0.0.1 9999。
用tcpdump抓取到的數(shù)據(jù)包如下(注意先運(yùn)行tcpdump,然后再執(zhí)行nc命令):
# tcpdump -i lo -X udp port 9999 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes 11:19:39.267912 IP localhost.45666 > localhost.distinct: UDP, length 6 0x0000: 4500 0022 5914 4000 4011 e3b4 7f00 0001 E.."Y.@.@....... 0x0010: 7f00 0001 b262 270f 000e fe21 6865 6c6c .....b'....!hell 0x0020: 6f0a o. ... ...
說(shuō)明:
- 源端口:0xb262,十進(jìn)制的45666。
- 目的端口:0x270f,十進(jìn)制的9999。
- 長(zhǎng)度:0x000e,14個(gè)字節(jié)的報(bào)文長(zhǎng)度。
- 校驗(yàn)和:0xfe21。
bio之單播
單播就是一對(duì)一通信。
服務(wù)器端代碼如下:
package com.morris.udp.bio.single;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket(9999);
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
datagramSocket.receive(datagramPacket);
System.out.println("receive from client: " + new String(bytes));
byte[] req = "hello client".getBytes();
DatagramPacket resp = new DatagramPacket(req, req.length, datagramPacket.getSocketAddress());
datagramSocket.send(resp);
}
}
客戶端代碼如下:
package com.morris.udp.bio.single;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class Client {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket();
byte[] req = "hello server".getBytes();
DatagramPacket datagramPacket = new DatagramPacket(req, req.length, new InetSocketAddress("127.0.0.1", 9999));
datagramSocket.send(datagramPacket);
datagramSocket.receive(datagramPacket);
System.out.println("receive from server: " + new String(datagramPacket.getData()));
}
}
客戶端和服務(wù)端的代碼幾乎一致,只不過(guò)接收和發(fā)送數(shù)據(jù)的順序不一致,receive和send都?xì)W式阻塞方法。
bio之廣播
廣播:同一網(wǎng)段所有主機(jī)都能接收,前提是端口要開(kāi)啟監(jiān)聽(tīng)。
只需要將單播的例子中客戶端發(fā)送數(shù)據(jù)的IP修改為255.255.255.255即可,具體修改如下:
DatagramPacket datagramPacket = new DatagramPacket(req, req.length, new InetSocketAddress("255.255.255.255", 9999));
bio之多播(組播)
多播數(shù)據(jù)報(bào)套接字類(lèi)用于發(fā)送和接收IP多播包。MulticastSocket是一種DatagramSocket,它具有加入Internet上其他多播主機(jī)的“組”的附加功能。
多播組通過(guò)D類(lèi)IP地址和標(biāo)準(zhǔn)UDP端口號(hào)指定。D類(lèi)IP地址在224.0.0.0和239.255.255.255的范圍內(nèi)。地址224.0.0.0被保留,不應(yīng)使用。
可以通過(guò)首先使用所需端口創(chuàng)建MulticastSocket,然后調(diào)用joinGroup(InetAddress groupAddr)方法來(lái)加入多播組。
服務(wù)器端代碼如下:
package com.morris.udp.bio.multicast;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class Server {
public static void main(String[] args) throws IOException {
InetAddress group = InetAddress.getByName("228.5.6.7");
MulticastSocket s = new MulticastSocket(6789);
s.joinGroup(group);
byte[] buf = new byte[1000];
DatagramPacket recv = new DatagramPacket(buf, buf.length);
s.receive(recv);
System.out.println("receive : " + new String(buf));
s.leaveGroup(group);
}
}
客戶端代碼如下:
package com.morris.udp.bio.multicast;
import java.io.IOException;
import java.net.*;
public class Client {
public static void main(String[] args) throws IOException {
String msg = "Hello";
InetAddress group = InetAddress.getByName("228.5.6.7");
MulticastSocket s = new MulticastSocket();
s.joinGroup(group);
DatagramPacket hi = new DatagramPacket(msg.getBytes(), msg.length(), group, 6789);
s.send(hi);
s.leaveGroup(group);
}
}
NIO實(shí)現(xiàn)單播
服務(wù)器端代碼如下:
package com.morris.udp.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
public class Server {
public static void main(String[] args) throws IOException {
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.bind(new InetSocketAddress(9999));
// datagramChannel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
SocketAddress receive = datagramChannel.receive(byteBuffer);
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
System.out.println("receive from client: " + new String(bytes));
byteBuffer.clear();
byteBuffer.put("hello client".getBytes());
datagramChannel.send(byteBuffer, receive);
}
}
客戶端代碼如下:
package com.morris.udp.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
public class Client {
public static void main(String[] args) throws IOException {
DatagramChannel datagramChannel = DatagramChannel.open();
// datagramChannel.configureBlocking(false);
String req = "hello server";
ByteBuffer byteBuffer = ByteBuffer.allocate(req.length());
byteBuffer.put(req.getBytes());
byteBuffer.flip();
datagramChannel.send(byteBuffer, new InetSocketAddress("127.0.0.1", 9999));
datagramChannel.receive(byteBuffer);
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
System.out.println("receive from server: " + new String(bytes));
}
}
Netty實(shí)現(xiàn)單播
服務(wù)器端代碼如下:
package com.morris.udp.netty.single;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.CharsetUtil;
public class Server {
private static final int port = 8899;
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioDatagramChannel.class)
.handler(new SimpleChannelInboundHandler<DatagramPacket>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
// 接收數(shù)據(jù)
System.out.println(msg.content().toString(CharsetUtil.UTF_8));
// 發(fā)送數(shù)據(jù)
ctx.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("hello client", CharsetUtil.UTF_8), msg.sender()));
ctx.close();
}
});
bootstrap.bind(port).sync().channel().closeFuture().await();
} finally {
group.shutdownGracefully();
}
}
}
客戶端代碼如下:
package com.morris.udp.netty.single;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.CharsetUtil;
import java.net.InetSocketAddress;
public class Client {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioDatagramChannel.class)
.handler(new SimpleChannelInboundHandler<DatagramPacket>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
// 接收數(shù)據(jù)
System.out.println(msg.content().toString(CharsetUtil.UTF_8));
ctx.close();
}
});
Channel channel = bootstrap.bind(0).sync().channel();
// 發(fā)送數(shù)據(jù)
channel.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("hello server", CharsetUtil.UTF_8), new InetSocketAddress("127.0.0.1", 8899)));
if (!channel.closeFuture().await(30 * 1000)) {
System.err.println("查詢超時(shí)");
}
} finally {
group.shutdownGracefully();
}
}
}
Netty實(shí)現(xiàn)廣播
只需要將netty實(shí)現(xiàn)的單播的客戶端代碼做如下修改:
1.增加option:
.option(ChannelOption.SO_BROADCAST, true)
2.將IP地址修改為廣播地址255.255.255.255:
channel.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("hello server", CharsetUtil.UTF_8), new InetSocketAddress("255.255.255.255", 8899)));
底層實(shí)現(xiàn)
recvfrom負(fù)責(zé)接收UDP數(shù)據(jù),其函數(shù)聲明如下:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
sendto負(fù)責(zé)發(fā)送UDP數(shù)據(jù),其函數(shù)聲明如下:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
下面通過(guò)對(duì)bio之單播的例子所產(chǎn)生的系統(tǒng)調(diào)用進(jìn)行跟蹤:
啟動(dòng)服務(wù)器端服務(wù)Server:
# strace -ff -o out java Server
然后使用nc命令充當(dāng)客戶端進(jìn)行連接:echo hello | nc -uv 127.0.0.1 9999。
產(chǎn)生的系統(tǒng)調(diào)用中關(guān)鍵信息如下:
socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP) = 4
bind(4, {sa_family=AF_INET6, sin6_port=htons(9999), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
recvfrom(4, "hello\n", 1024, 0, {sa_family=AF_INET6, sin6_port=htons(7361), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [28]) = 6
write(1, "receive from client: hello\n\0\0\0\0\0"..., 1045) = 1045
write(1, "\n", 1)
sendto(4, "hello client", 12, 0, {sa_family=AF_INET6, sin6_port=htons(7361), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 12
可見(jiàn)發(fā)送和接收數(shù)據(jù)確實(shí)使用了上面的系統(tǒng)調(diào)用,另外上面的系統(tǒng)調(diào)用中并沒(méi)有listen函數(shù),不需要監(jiān)聽(tīng)端口,再次驗(yàn)證UDP是面向無(wú)連接的。
到此這篇關(guān)于詳解UDP協(xié)議格式及在java中的使用的文章就介紹到這了,更多相關(guān)java中使用UDP協(xié)議內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java時(shí)間戳與日期相互轉(zhuǎn)換工具詳解
這篇文章主要為大家詳細(xì)介紹了java各種時(shí)間戳與日期之間相互轉(zhuǎn)換的工具,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
基于Spring的Maven項(xiàng)目實(shí)現(xiàn)發(fā)送郵件功能的示例
這篇文章主要介紹了基于Spring的Maven項(xiàng)目實(shí)現(xiàn)發(fā)送郵件功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Java中Socket實(shí)現(xiàn)數(shù)據(jù)通信的示例代碼
本文主要介紹了Java中Socket實(shí)現(xiàn)數(shù)據(jù)通信的示例代碼,Socket可以建立起客戶端和服務(wù)器之間的連接,實(shí)現(xiàn)數(shù)據(jù)的傳輸和交互,感興趣的可以了解一下2023-09-09
SpringBoot使用quartz,注入feignClient,client為null問(wèn)題
在SpringBoot中使用Quartz和FeignClient時(shí),如果通過(guò)@Autowired或構(gòu)造方法注入FeignClient導(dǎo)致為null,可以使用Spring提供的通過(guò)文件名獲取bean的方式解決,這種方法在Quartz啟動(dòng)時(shí)通過(guò)反射注入類(lèi),而類(lèi)還未初始化好,導(dǎo)致FeignClient為null的問(wèn)題2024-11-11
JAVA項(xiàng)目如何打包部署到Linux服務(wù)器上
本文詳細(xì)介紹了在服務(wù)器上部署環(huán)境包括JDK、MySQL、Tomcat的設(shè)置,以及使用Idea-Maven-SpringBoot進(jìn)行jar包打包部署的流程,內(nèi)容涵蓋了MySQL配置注意事項(xiàng)、pom.xml配置、打包命令等關(guān)鍵步驟,同時(shí),也提供了如何將jar包上傳到Linux服務(wù)器并運(yùn)行的具體方法2024-10-10
對(duì)SpringBoot項(xiàng)目Jar包進(jìn)行加密防止反編譯
最近項(xiàng)目要求部署到其他公司的服務(wù)器上,但是又不想將源碼泄露出去,要求對(duì)正式環(huán)境的啟動(dòng)包進(jìn)行安全性處理,防止客戶直接通過(guò)反編譯工具將代碼反編譯出來(lái),本文介紹了如何對(duì)SpringBoot項(xiàng)目Jar包進(jìn)行加密防止反編譯,需要的朋友可以參考下2023-10-10

