Socket 与TCP、UDP
Socket就是IP地址与端口的结合协议(RFC 793),一种地址与端口的结合描述协议。
Socket的作用与组成
在网络传输中用于唯一标识两个端点之间的链接
端点: 包括IP和Port
UDP
UDP最大长度: UDP Header
中2Bytes
来存储长度信息,所以UDP的长度应该是
$$ 2^{16}-1 = 65535 Bytes $$
自身协议(头部信息)占用 8 Bytes (2Bytes × 4种信息 )
$$ 65535-8 = 65507 Bytes $$
也就是当信息长度大于65507 Bytes
时,用UDP传输会出现问题,需要做一次分包传输
UDP核心API
API-DatagramSocket
用于接收与发送UDP的类
负责发送某一个UDP包,或者接收UDP包
不同于TCP,UDP并没有合并到Socket API中
1 2 3 4 5 6 7 DatagramSocket()// 创建简单实例,不指定端口和IP DatagramSocket(int port)// 创建监听固定端口的实例 DatagramSocket(int port ,InetAddress localAddr) // 创建固定端口指定IP的实例 receive(DatagramPacket d) // 接收 send(DatagramPacket d) // 发送 setSoTimeout(int timeout) // 设置超时,毫秒 close() // 关闭资源
API-DatagramPacket
用于处理报文
将byte数组、目标地址、目标端口等数据包装成报文或者将报文拆卸成byte数组
是UDP发送实体,也是接收实体
1 2 3 4 5 6 DatagramPacket(byte[] buf ,int offset ,int length ,InetAddress address ,int port)// 前三个参数指定buf的使用区间,后面是目标机器地址与端口 DatagramPacket(byte[] buf ,int length ,SocketAddress address )// 创建监听固定端口的实例,SocketAddress相当于InetAddress+Port setData(byte[] buf ,int offset ,int length) // 指定buf中哪一部分信息是有效的 setData(byte[] buf) // 整个buf均被指定 setLength(int length) // 用于单独设置buf有效区间的长度 setAddress(InetAddress iaddr),setPort(int iport),setSocketAddress(SocketAddress address) // 设定IP地址和端口
UDP单播、广播、多播
单播:点对点
多播:点对多个点(局域网内部分点)
广播:点对所有设备
多播是在广播之后出现的,广播会导致大量的带宽被浪费。
广播地址运算:
IP地址 : 192.168.124.7
子网掩码 : 255.255.255.192 -> 11111111.11111111.11111111.11000000
可划分网段: 2^2 = 4个
网段:0-63、64-127、128-191、192-255
广播地址: 192.168.124.63(所在网段的最高位)
两台主机广播地址不同,是不能进行广播通信的
UDP局域网搜索 假设一个场景,手机(Searcher)与其他互联设备(Provider)同处局域网内,Provider与Searcher共用一套交互协议(同时监听某一端口,遵循相同的传输协议等),Searcher希望通过广播找到某一特定的Provider,整套流程就可以基于UDP局域网搜索实现。
需要注意的点:
Searcher要先开启Port:30000
监听。因为启动Searcher后,再开启端口的话,是有可能错过信息的,Searcher和Provider会立刻广播、立刻返回SN
。
Provider和Searcher在构建类后,必须要持续监听,同时可以随时停止,所以最好用线程实现。
要先开启Provider,再启动Searcher。
1. MessageCreator实现 MessageCreator用来封装消息、解析消息。
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 public class MessageCreator { private static final String SN_HEADER = "收到暗号,我是(SN):" ; private static final String PORT_HEADER = "这是暗号,请回电端口(Port):" ; public static String buildWithPort (int port) { return PORT_HEADER + port; } public static int parsePort (String data) { if (data.startsWith(PORT_HEADER)) { return Integer.parseInt(data.substring(PORT_HEADER.length())); } return -1 ; } public static String buildWithSn (String sn) { return SN_HEADER + sn; } public static String parseSn (String data) { if (data.startsWith(SN_HEADER)) { return data.substring(SN_HEADER.length()); } return null ; } }
2. UDPProvider实现
UDPProvider类启动
1 2 3 4 5 6 7 8 9 10 11 String sn = UUID.randomUUID().toString(); Provider provider = new Provider(sn); provider.start(); System.in.read(); provider.exit();
Provider线程基于内部类实现
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 private static class Provider extends Thread { private final String sn; private boolean done = false ; private DatagramSocket ds = null ; public Provider (String sn) { super (); this .sn = sn; } @Override public void run () { super .run(); System.out.println("UDPProvider Started." ); try { ds = new DatagramSocket(20000 ); while (!done) { final byte [] buf = new byte [512 ]; DatagramPacket receivePack = new DatagramPacket(buf, buf.length); ds.receive(receivePack); String ip = receivePack.getAddress().getHostAddress(); int port = receivePack.getPort(); int dataLen = receivePack.getLength(); String data = new String(receivePack.getData(), 0 , dataLen); System.out.println("UDPProvider receive form ip:" + ip + "\tport:" + port + "\tdata:" + data); int responsePort = MessageCreator.parsePort(data); if (responsePort != -1 ) { String responseData = MessageCreator.buildWithSn(sn); byte [] responseDataBytes = responseData.getBytes(); DatagramPacket responsePacket = new DatagramPacket(responseDataBytes, responseDataBytes.length, receivePack.getAddress(), responsePort); ds.send(responsePacket); } } } catch (Exception ignored) { } finally { close(); } System.out.println("UDPProvider Finished." ); } private void close () { if (ds != null ) { ds.close(); ds = null ; } } void exit () { done = true ; close(); } }
2. UDPSearcher实现
UDPSearcher类启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static final int LISTEN_PORT = 30000 ;Listener listener = listen(); sendBroadcast(); System.in.read(); List<Device> devices = listener.getDevicesAndClose(); for (Device device : devices) { System.out.println("Device:" + device.toString()); }
listen()方法实现
1 2 3 4 5 6 7 8 9 private static Listener listen () throws InterruptedException { System.out.println("UDPSearcher start listen." ); CountDownLatch countDownLatch = new CountDownLatch(1 ); Listener listener = new Listener(LISTEN_PORT,countDownLatch); listener.start(); countDownLatch.await(); return listener; }
sendBroadcast()广播方法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private static void sendBroadcast () throws IOException { System.out.println("UDPSearcher sendBroadcast started." ); DatagramSocket ds = new DatagramSocket(); String requestData = MessageCreator.buildWithPort(LISTEN_PORT); byte [] requestDataBytes = requestData.getBytes(); DatagramPacket requestPacket = new DatagramPacket(requestDataBytes,requestDataBytes.length); requestPacket.setAddress(InetAddress.getByName("255.255.255.255" )); requestPacket.setPort(20000 ); ds.send(requestPacket); ds.close(); System.out.println("UDPSearcher sendBroadcast finished." ); }
Listener线程基于内部类实现
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 private static class Listener extends Thread { private final int listenPort; private boolean done = false ; private final CountDownLatch countDownLatch; private final List<Device> devices = new ArrayList<>(); private DatagramSocket ds = null ; public Listener (int listenPort ,CountDownLatch countDownLatch) { super (); this .listenPort = listenPort; this .countDownLatch = countDownLatch; } @Override public void run () { super .run(); countDownLatch.countDown(); try { ds = new DatagramSocket(listenPort); while (!done){ final byte [] buf = new byte [512 ]; DatagramPacket receivePack = new DatagramPacket(buf ,buf.length); ds.receive(receivePack); String ip = receivePack.getAddress().getHostAddress(); int port = receivePack.getPort(); int dataLen = receivePack.getLength(); String data = new String(receivePack.getData(),0 ,dataLen); System.out.println("UDPProvider receive from ip:" +ip + "\tPort:" +port + "\tdata:" +data); String sn = MessageCreator.parseSN(data); if (sn!=null ){ Device device = new Device(port ,ip ,sn); devices.add(device); } } }catch (Exception ignored ){ }finally { close(); } System.out.println("UDPProvider listener Finished" ); } private void close () { if (ds!=null ){ ds.close(); ds = null ; } } List<Device> getDevicesAndClose () { done = true ; close(); return devices; } }
Device类基于内部类实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private static class Device { final int port; final String ip; final String sn; private Device (int port, String ip, String sn) { this .port = port; this .ip = ip; this .sn = sn; } @Override public String toString () { return "Device{" + "port=" + port + ", ip='" + ip + '\'' + ", sn='" + sn + '\'' + '}' ; } }
完整代码SocketDemo_UDP_02