UDP是面向無連接的通訊協(xié)議,由于通訊不需要連接,所以可以實(shí)現(xiàn)廣播發(fā)送。UDP通訊時(shí)不需要接收方確認(rèn),屬于不可靠的傳輸,可能會出現(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ā)送方的端口號.
- 目的端口:數(shù)據(jù)接收方的端口號。
- 長度:UDP數(shù)據(jù)報(bào)的整個(gè)長度(包括首部和數(shù)據(jù)),其最小值為8(只有首部)。
- 校驗(yàn)和:檢測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命令):
1
2
3
4
5
6
7
8
|
# 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. ... ... |
說明:
- 源端口:0xb262,十進(jìn)制的45666。
- 目的端口:0x270f,十進(jìn)制的9999。
- 長度:0x000e,14個(gè)字節(jié)的報(bào)文長度。
- 校驗(yàn)和:0xfe21。
bio之單播
單播就是一對一通信。
服務(wù)器端代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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); } } |
客戶端代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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ù)端的代碼幾乎一致,只不過接收和發(fā)送數(shù)據(jù)的順序不一致,receive和send都?xì)W式阻塞方法。
bio之廣播
廣播:同一網(wǎng)段所有主機(jī)都能接收,前提是端口要開啟監(jiān)聽。
只需要將單播的例子中客戶端發(fā)送數(shù)據(jù)的IP修改為255.255.255.255
即可,具體修改如下:
1
|
DatagramPacket datagramPacket = new DatagramPacket(req, req.length, new InetSocketAddress( "255.255.255.255" , 9999 )); |
bio之多播(組播)
多播數(shù)據(jù)報(bào)套接字類用于發(fā)送和接收IP多播包。MulticastSocket是一種DatagramSocket,它具有加入Internet上其他多播主機(jī)的“組”的附加功能。
多播組通過D類IP地址和標(biāo)準(zhǔn)UDP端口號指定。D類IP地址在224.0.0.0和239.255.255.255的范圍內(nèi)。地址224.0.0.0被保留,不應(yīng)使用。
可以通過首先使用所需端口創(chuàng)建MulticastSocket,然后調(diào)用joinGroup(InetAddress groupAddr)方法來加入多播組。
服務(wù)器端代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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); } } |
客戶端代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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ù)器端代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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); } } |
客戶端代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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ù)器端代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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(); } } } |
客戶端代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
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:
1
|
.option(ChannelOption.SO_BROADCAST, true ) |
2.將IP地址修改為廣播地址255.255.255.255
:
1
|
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ù)聲明如下:
1
|
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ù)聲明如下:
1
|
ssize_t sendto( int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); |
下面通過對bio之單播的例子所產(chǎn)生的系統(tǒng)調(diào)用進(jìn)行跟蹤:
啟動服務(wù)器端服務(wù)Server:
1
|
# 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
可見發(fā)送和接收數(shù)據(jù)確實(shí)使用了上面的系統(tǒng)調(diào)用,另外上面的系統(tǒng)調(diào)用中并沒有listen
函數(shù),不需要監(jiān)聽端口,再次驗(yàn)證UDP是面向無連接的。
到此這篇關(guān)于詳解UDP協(xié)議格式及在java中的使用的文章就介紹到這了,更多相關(guān)java中使用UDP協(xié)議內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://blog.csdn.net/u022812849/article/details/109843694