详解UDP协议格式及在java中的使用

2025-05-29 0 80

UDP是面向无连接的通讯协议,由于通讯不需要连接,所以可以实现广播发送。UDP通讯时不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证。

UDP适用于DNS、视频音频等多媒体通信、广播通信(广播、多播)。例如我们常用的QQ,就是一个以UDP为主,TCP为辅的通讯协议。

UDP报文格式如下:

详解UDP协议格式及在java中的使用

UDP首部有8个字节,由4个字段构成,每个字段都是两个字节,

  • 源端口:数据发送方的端口号.
  • 目的端口:数据接收方的端口号。
  • 长度:UDP数据报的整个长度(包括首部和数据),其最小值为8(只有首部)。
  • 校验和:检测UDP数据报在传输中是否有错,有错则丢弃。

可以使用nc发送UDP数据包:echo hello | nc -uv 127.0.0.1 9999

用tcpdump抓取到的数据包如下(注意先运行tcpdump,然后再执行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.

... ...

说明:

  1. 源端口:0xb262,十进制的45666。
  2. 目的端口:0x270f,十进制的9999。
  3. 长度:0x000e,14个字节的报文长度。
  4. 校验和:0xfe21。

bio之单播

单播就是一对一通信。

服务器端代码如下:

?

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()));

}

}

客户端和服务端的代码几乎一致,只不过接收和发送数据的顺序不一致,receive和send都欧式阻塞方法。

bio之广播

广播:同一网段所有主机都能接收,前提是端口要开启监听。

只需要将单播的例子中客户端发送数据的IP修改为255.255.255.255即可,具体修改如下:

?

1
DatagramPacket datagramPacket = new DatagramPacket(req, req.length, new InetSocketAddress("255.255.255.255", 9999));

bio之多播(组播)

多播数据报套接字类用于发送和接收IP多播包。MulticastSocket是一种DatagramSocket,它具有加入Internet上其他多播主机的“组”的附加功能。

多播组通过D类IP地址和标准UDP端口号指定。D类IP地址在224.0.0.0和239.255.255.255的范围内。地址224.0.0.0被保留,不应使用。

可以通过首先使用所需端口创建MulticastSocket,然后调用joinGroup(InetAddress groupAddr)方法来加入多播组。

服务器端代码如下:

?

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实现单播

服务器端代码如下:

?

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实现单播

服务器端代码如下:

?

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 {

// 接收数据

System.out.println(msg.content().toString(CharsetUtil.UTF_8));

// 发送数据

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 {

// 接收数据

System.out.println(msg.content().toString(CharsetUtil.UTF_8));

ctx.close();

}

});

Channel channel = bootstrap.bind(0).sync().channel();

// 发送数据

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("查询超时");

}

} finally {

group.shutdownGracefully();

}

}

}

Netty实现广播

只需要将netty实现的单播的客户端代码做如下修改:

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)));

底层实现

recvfrom负责接收UDP数据,其函数声明如下:

?

1
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

sendto负责发送UDP数据,其函数声明如下:

?

1
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

下面通过对bio之单播的例子所产生的系统调用进行跟踪:

启动服务器端服务Server:

?

1
# strace -ff -o out java Server

然后使用nc命令充当客户端进行连接:echo hello | nc -uv 127.0.0.1 9999

产生的系统调用中关键信息如下:

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

可见发送和接收数据确实使用了上面的系统调用,另外上面的系统调用中并没有listen函数,不需要监听端口,再次验证UDP是面向无连接的。

到此这篇关于详解UDP协议格式及在java中的使用的文章就介绍到这了,更多相关java中使用UDP协议内容请搜索快网idc以前的文章或继续浏览下面的相关文章希望大家以后多多支持快网idc!

原文链接:https://blog.csdn.net/u022812849/article/details/109843694

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 详解UDP协议格式及在java中的使用 https://www.kuaiidc.com/108545.html

相关文章

发表评论
暂无评论