JAVA-UDP辅助TCP实现点对点传输
public class ServerInfo { private String sn; private int port; private String address; public ServerInfo(int port, String ip, String sn) { this.port = port; this.address = ip; this.sn = sn; } public String getSn() { return sn; } public void setSn(String sn) { this.sn = sn; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "ServerInfo{" + "sn='" + sn + '\'' + ", port=" + port + ", address='" + address + '\'' + '}'; } }
客户端:
public class Client { public static void main(String[] args) { ServerInfo info = ClientSearcher.searchServer(10000); System.out.println("Server:" + info); } }
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class ClientSearcher { private static final int LISTEN_PORT = UDPConstants.PORT_CLIENT_RESPONSE; public static ServerInfo searchServer(int timeout) { System.out.println("UDPSearcher Started."); // 成功收到回送的栅栏 CountDownLatch receiveLatch = new CountDownLatch(1); Listener listener = null; try { listener = listen(receiveLatch); sendBroadcast(); receiveLatch.await(timeout, TimeUnit.MILLISECONDS); } catch (Exception e) { e.printStackTrace(); } // 完成 System.out.println("UDPSearcher Finished."); if (listener == null) { return null; } List<ServerInfo> devices = listener.getServerAndClose(); if (devices.size() > 0) { return devices.get(0); } return null; } private static Listener listen(CountDownLatch receiveLatch) throws InterruptedException { System.out.println("UDPSearcher start listen."); CountDownLatch startDownLatch = new CountDownLatch(1); Listener listener = new Listener(LISTEN_PORT, startDownLatch, receiveLatch); listener.start(); startDownLatch.await(); return listener; } private static void sendBroadcast() throws IOException { System.out.println("UDPSearcher sendBroadcast started."); // 作为搜索方,让系统自动分配端口 DatagramSocket ds = new DatagramSocket(); // 构建一份请求数据 ByteBuffer byteBuffer = ByteBuffer.allocate(128); // 头部 byteBuffer.put(UDPConstants.HEADER); // CMD命名 byteBuffer.putShort((short) 1); // 回送端口信息 byteBuffer.putInt(LISTEN_PORT); // 直接构建packet DatagramPacket requestPacket = new DatagramPacket(byteBuffer.array(), byteBuffer.position() + 1); // 广播地址 requestPacket.setAddress(InetAddress.getByName("255.255.255.255")); // 设置服务器端口 requestPacket.setPort(UDPConstants.PORT_SERVER); // 发送 ds.send(requestPacket); ds.close(); // 完成 System.out.println("UDPSearcher sendBroadcast finished."); } private static class Listener extends Thread { private final int listenPort; private final CountDownLatch startDownLatch; private final CountDownLatch receiveDownLatch; private final List<ServerInfo> serverInfoList = new ArrayList<>(); private final byte[] buffer = new byte[128]; private final int minLen = UDPConstants.HEADER.length + 2 + 4; private boolean done = false; private DatagramSocket ds = null; private Listener(int listenPort, CountDownLatch startDownLatch, CountDownLatch receiveDownLatch) { super(); this.listenPort = listenPort; this.startDownLatch = startDownLatch; this.receiveDownLatch = receiveDownLatch; } @Override public void run() { super.run(); // 通知已启动 startDownLatch.countDown(); try { // 监听回送端口 ds = new DatagramSocket(listenPort); // 构建接收实体 DatagramPacket receivePack = new DatagramPacket(buffer, buffer.length); while (!done) { // 接收 ds.receive(receivePack); // 打印接收到的信息与发送者的信息 // 发送者的IP地址 String ip = receivePack.getAddress().getHostAddress(); int port = receivePack.getPort(); int dataLen = receivePack.getLength(); byte[] data = receivePack.getData(); boolean isValid = dataLen >= minLen && ByteUtils.startsWith(data, UDPConstants.HEADER); System.out.println("UDPSearcher receive form ip:" + ip + "\tport:" + port + "\tdataValid:" + isValid); if (!isValid) { // 无效继续 continue; } ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, UDPConstants.HEADER.length, dataLen); final short cmd = byteBuffer.getShort(); final int serverPort = byteBuffer.getInt(); if (cmd != 2 || serverPort <= 0) { System.out.println("UDPSearcher receive cmd:" + cmd + "\tserverPort:" + serverPort); continue; } String sn = new String(buffer, minLen, dataLen - minLen); ServerInfo info = new ServerInfo(serverPort, ip, sn); serverInfoList.add(info); // 成功接收到一份 receiveDownLatch.countDown(); } } catch (Exception ignored) { } finally { close(); } System.out.println("UDPSearcher listener finished."); } private void close() { if (ds != null) { ds.close(); ds = null; } } List<ServerInfo> getServerAndClose() { done = true; close(); return serverInfoList; } } }
帮助类:
public class ByteUtils { /** * Does this byte array begin with match array content? * * @param source Byte array to examine * @param match Byte array to locate in <code>source</code> * @return true If the starting bytes are equal */ public static boolean startsWith(byte[] source, byte[] match) { return startsWith(source, 0, match); } /** * Does this byte array begin with match array content? * * @param source Byte array to examine * @param offset An offset into the <code>source</code> array * @param match Byte array to locate in <code>source</code> * @return true If the starting bytes are equal */ public static boolean startsWith(byte[] source, int offset, byte[] match) { if (match.length > (source.length - offset)) { return false; } for (int i = 0; i < match.length; i++) { if (source[offset + i] != match[i]) { return false; } } return true; } /** * Does the source array equal the match array? * * @param source Byte array to examine * @param match Byte array to locate in <code>source</code> * @return true If the two arrays are equal */ public static boolean equals(byte[] source, byte[] match) { if (match.length != source.length) { return false; } return startsWith(source, 0, match); } /** * Copies bytes from the source byte array to the destination array * * @param source The source array * @param srcBegin Index of the first source byte to copy * @param srcEnd Index after the last source byte to copy * @param destination The destination array * @param dstBegin The starting offset in the destination array */ public static void getBytes(byte[] source, int srcBegin, int srcEnd, byte[] destination, int dstBegin) { System.arraycopy(source, srcBegin, destination, dstBegin, srcEnd - srcBegin); } /** * Return a new byte array containing a sub-portion of the source array * * @param srcBegin The beginning index (inclusive) * @param srcEnd The ending index (exclusive) * @return The new, populated byte array */ public static byte[] subbytes(byte[] source, int srcBegin, int srcEnd) { byte destination[]; destination = new byte[srcEnd - srcBegin]; getBytes(source, srcBegin, srcEnd, destination, 0); return destination; } /** * Return a new byte array containing a sub-portion of the source array * * @param srcBegin The beginning index (inclusive) * @return The new, populated byte array */ public static byte[] subbytes(byte[] source, int srcBegin) { return subbytes(source, srcBegin, source.length); } }
public class TCPConstants { // 服务器固化UDP接收端口 public static int PORT_SERVER = 30401; }
public class UDPConstants { // 公用头部 public static byte[] HEADER = new byte[]{7, 7, 7, 7, 7, 7, 7, 7}; // 服务器固化UDP接收端口 public static int PORT_SERVER=30201; // 客户端回送端口 public static int PORT_CLIENT_RESPONSE = 30202; }
import java.io.IOException; public class Server { public static void main(String[] args) { ServerProvider.start(TCPConstants.PORT_SERVER); try { System.in.read(); }catch (IOException e) { e.printStackTrace(); } ServerProvider.stop(); } }
import java.net.DatagramPacket; import java.net.DatagramSocket; import java.nio.ByteBuffer; import java.util.UUID; public class ServerProvider { private static Provider PROVIDER_INSTANCE; static void start(int port) { stop(); String sn = UUID.randomUUID().toString(); Provider provider = new Provider(sn, port); provider.start(); PROVIDER_INSTANCE = provider; } static void stop() { if (PROVIDER_INSTANCE != null) { PROVIDER_INSTANCE.exit(); PROVIDER_INSTANCE = null; } } private static class Provider extends Thread { private final byte[] sn; private final int port; private boolean done = false; private DatagramSocket ds = null; // 存储消息的Buffer final byte[] buffer = new byte[128]; Provider(String sn, int port) { super(); this.sn = sn.getBytes(); this.port = port; } @Override public void run() { super.run(); System.out.println("UDPProvider Started."); try { // 监听20000 端口 ds = new DatagramSocket(UDPConstants.PORT_SERVER); // 接收消息的Packet DatagramPacket receivePack = new DatagramPacket(buffer, buffer.length); while (!done) { // 接收 ds.receive(receivePack); // 打印接收到的信息与发送者的信息 // 发送者的IP地址 String clientIp = receivePack.getAddress().getHostAddress(); int clientPort = receivePack.getPort(); int clientDataLen = receivePack.getLength(); byte[] clientData = receivePack.getData(); boolean isValid = clientDataLen >= (UDPConstants.HEADER.length + 2 + 4) && ByteUtils.startsWith(clientData, UDPConstants.HEADER); System.out.println("ServerProvider receive form ip:" + clientIp + "\tport:" + clientPort + "\tdataValid:" + isValid); if (!isValid) { // 无效继续 continue; } // 解析命令与回送端口 int index = UDPConstants.HEADER.length; short cmd = (short) ((clientData[index++] << 8) | (clientData[index++] & 0xff)); int responsePort = (((clientData[index++]) << 24) | ((clientData[index++] & 0xff) << 16) | ((clientData[index++] & 0xff) << 8) | ((clientData[index] & 0xff))); // 判断合法性 if (cmd == 1 && responsePort > 0) { // 构建一份回送数据 ByteBuffer byteBuffer = ByteBuffer.wrap(buffer); byteBuffer.put(UDPConstants.HEADER); byteBuffer.putShort((short) 2); byteBuffer.putInt(port); byteBuffer.put(sn); int len = byteBuffer.position(); // 直接根据发送者构建一份回送信息 DatagramPacket responsePacket = new DatagramPacket(buffer, len, receivePack.getAddress(), responsePort); ds.send(responsePacket); System.out.println("ServerProvider response to:" + clientIp + "\tport:" + responsePort + "\tdataLen:" + len); } else { System.out.println("ServerProvider receive cmd nonsupport; cmd:" + cmd + "\tport:" + port); } } } 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(); } } }