詳解UDP協議格式及在java中的使用
UDP是面向無連接的通訊協議,由于通訊不需要連接,所以可以實現廣播發送。UDP通訊時不需要接收方確認,屬于不可靠的傳輸,可能會出現丟包現象,實際應用中要求程序員編程驗證。
UDP適用于DNS、視頻音頻等多媒體通信、廣播通信(廣播、多播)。例如我們常用的QQ,就是一個以UDP為主,TCP為輔的通訊協議。
UDP報文格式如下:
UDP首部有8個字節,由4個字段構成,每個字段都是兩個字節,
源端口:數據發送方的端口號. 目的端口:數據接收方的端口號。 長度:UDP數據報的整個長度(包括首部和數據),其最小值為8(只有首部)。 校驗和:檢測UDP數據報在傳輸中是否有錯,有錯則丟棄。可以使用nc發送UDP數據包:echo hello | nc -uv 127.0.0.1 9999。
用tcpdump抓取到的數據包如下(注意先運行tcpdump,然后再執行nc命令):
# tcpdump -i lo -X udp port 9999tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes11:19:39.267912 IP localhost.45666 > localhost.distinct: UDP, length 60x0000: 4500 0022 5914 4000 4011 e3b4 7f00 0001 E..'Y.@.@.......0x0010: 7f00 0001 b262 270f 000e fe21 6865 6c6c .....b’....!hell0x0020: 6f0a o.... ...
說明:
源端口:0xb262,十進制的45666。 目的端口:0x270f,十進制的9999。 長度:0x000e,14個字節的報文長度。 校驗和:0xfe21。bio之單播單播就是一對一通信。
服務器端代碼如下:
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())); }}
客戶端和服務端的代碼幾乎一致,只不過接收和發送數據的順序不一致,receive和send都歐式阻塞方法。
bio之廣播廣播:同一網段所有主機都能接收,前提是端口要開啟監聽。
只需要將單播的例子中客戶端發送數據的IP修改為255.255.255.255即可,具體修改如下:
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)方法來加入多播組。
服務器端代碼如下:
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實現單播
服務器端代碼如下:
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實現單播
服務器端代碼如下:
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(); } }}
客戶端代碼如下:
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:
.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)));底層實現
recvfrom負責接收UDP數據,其函數聲明如下:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
sendto負責發送UDP數據,其函數聲明如下:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
下面通過對bio之單播的例子所產生的系統調用進行跟蹤:
啟動服務器端服務Server:
# strace -ff -o out java Server
然后使用nc命令充當客戶端進行連接:echo hello | nc -uv 127.0.0.1 9999。
產生的系統調用中關鍵信息如下:
socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP) = 4bind(4, {sa_family=AF_INET6, sin6_port=htons(9999), inet_pton(AF_INET6, '::', &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0recvfrom(4, 'hellon', 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]) = 6write(1, 'receive from client: hellon00000'..., 1045) = 1045write(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協議內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!
相關文章:
1. phpstudy apache開啟ssi使用詳解2. CentOS郵件服務器搭建系列—— POP / IMAP 服務器的構建( Dovecot )3. .NET SkiaSharp 生成二維碼驗證碼及指定區域截取方法實現4. IntelliJ IDEA創建web項目的方法5. 存儲于xml中需要的HTML轉義代碼6. docker容器調用yum報錯的解決辦法7. django創建css文件夾的具體方法8. MyBatis JdbcType 與Oracle、MySql數據類型對應關系說明9. ASP中實現字符部位類似.NET里String對象的PadLeft和PadRight函數10. javascript xml xsl取值及數據修改第1/2頁
