什么是UDP
不可靠
案例
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;
}
}
provider
public class UDPProvider {
public static void main(String[] args) throws IOException {
// 生成一份唯一标示
String sn = UUID.randomUUID().toString();
Provider provider = new Provider(sn);
provider.start();
// 读取任意键盘信息后可以退出
//noinspection ResultOfMethodCallIgnored
System.in.read();
provider.exit();
}
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 {
// 监听20000 端口
ds = new DatagramSocket(20000);
while (!done) {
// 构建接收实体
final byte[] buf = new byte[512];
DatagramPacket receivePack = new DatagramPacket(buf, buf.length);
// 接收
ds.receive(receivePack);
// 打印接收到的信息与发送者的信息
// 发送者的IP地址
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();
}
}
}
Searcher
public class UDPSearcher {
private static final int LISTEN_PORT = 30000;
public static void main(String[] args) throws IOException, InterruptedException {
System.out.println("UDPSearcher Started.");
Listener listener = listen();
sendBroadcast();
// 读取任意键盘信息后可以退出
//noinspection ResultOfMethodCallIgnored
System.in.read();
List devices = listener.getDevicesAndClose();
for (Device device : devices) {
System.out.println("Device:" + device.toString());
}
// 完成
System.out.println("UDPSearcher Finished.");
}
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;
}
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();
// 直接构建packet
DatagramPacket requestPacket = new DatagramPacket(requestDataBytes,
requestDataBytes.length);
// 20000端口, 广播地址
requestPacket.setAddress(InetAddress.getByName("255.255.255.255"));
requestPacket.setPort(20000);
// 发送
ds.send(requestPacket);
ds.close();
// 完成
System.out.println("UDPSearcher sendBroadcast finished.");
}
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 + '\'' +
'}';
}
}
private static class Listener extends Thread {
private final int listenPort;
private final CountDownLatch countDownLatch;
private final List devices = new ArrayList<>();
private boolean done = false;
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);
// 打印接收到的信息与发送者的信息
// 发送者的IP地址
String ip = receivePack.getAddress().getHostAddress();
int port = receivePack.getPort();
int dataLen = receivePack.getLength();
String data = new String(receivePack.getData(), 0, dataLen);
System.out.println("UDPSearcher receive form 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("UDPSearcher listener finished.");
}
private void close() {
if (ds != null) {
ds.close();
ds = null;
}
}
List getDevicesAndClose() {
done = true;
close();
return devices;
}
}
}
三次握手(连接上)
四次挥手(断开连接)
可靠性
案例
public class Tools {
public static int byteArrayToInt(byte[] b) {
return b[3] & 0xFF |
(b[2] & 0xFF) << 8 |
(b[1] & 0xFF) << 16 |
(b[0] & 0xFF) << 24;
}
public static byte[] intToByteArray(int a) {
return new byte[]{
(byte) ((a >> 24) & 0xFF),
(byte) ((a >> 16) & 0xFF),
(byte) ((a >> 8) & 0xFF),
(byte) (a & 0xFF)
};
}
}
public class Server {
private static final int PORT = 20000;
public static void main(String[] args) throws IOException {
ServerSocket server = createServerSocket();
initServerSocket(server);
// 绑定到本地端口上
server.bind(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 50);
System.out.println("服务器准备就绪~");
System.out.println("服务器信息:" + server.getInetAddress() + " P:" + server.getLocalPort());
// 等待客户端连接
for (; ; ) {
// 得到客户端
Socket client = server.accept();
// 客户端构建异步线程
ClientHandler clientHandler = new ClientHandler(client);
// 启动线程
clientHandler.start();
}
}
private static ServerSocket createServerSocket() throws IOException {
// 创建基础的ServerSocket
ServerSocket serverSocket = new ServerSocket();
// 绑定到本地端口20000上,并且设置当前可允许等待链接的队列为50个
//serverSocket = new ServerSocket(PORT);
// 等效于上面的方案,队列设置为50个
//serverSocket = new ServerSocket(PORT, 50);
// 与上面等同
// serverSocket = new ServerSocket(PORT, 50, Inet4Address.getLocalHost());
return serverSocket;
}
private static void initServerSocket(ServerSocket serverSocket) throws IOException {
// 是否复用未完全关闭的地址端口
serverSocket.setReuseAddress(true);
// 等效Socket#setReceiveBufferSize
serverSocket.setReceiveBufferSize(64 * 1024 * 1024);
// 设置serverSocket#accept超时时间
// serverSocket.setSoTimeout(2000);
// 设置性能参数:短链接,延迟,带宽的相对重要性
serverSocket.setPerformancePreferences(1, 1, 1);
}
/**
* 客户端消息处理
*/
private static class ClientHandler extends Thread {
private Socket socket;
ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
super.run();
System.out.println("新客户端连接:" + socket.getInetAddress() +
" P:" + socket.getPort());
try {
// 得到套接字流
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[256];
int readCount = inputStream.read(buffer);
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, 0, readCount);
// byte
byte be = byteBuffer.get();
// char
char c = byteBuffer.getChar();
// int
int i = byteBuffer.getInt();
// bool
boolean b = byteBuffer.get() == 1;
// Long
long l = byteBuffer.getLong();
// float
float f = byteBuffer.getFloat();
// double
double d = byteBuffer.getDouble();
// String
int pos = byteBuffer.position();
String str = new String(buffer, pos, readCount - pos - 1);
System.out.println("收到数量:" + readCount + " 数据:"
+ be + "\n"
+ c + "\n"
+ i + "\n"
+ b + "\n"
+ l + "\n"
+ f + "\n"
+ d + "\n"
+ str + "\n");
outputStream.write(buffer, 0, readCount);
outputStream.close();
inputStream.close();
} catch (Exception e) {
System.out.println("连接异常断开");
} finally {
// 连接关闭
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("客户端已退出:" + socket.getInetAddress() +
" P:" + socket.getPort());
}
}
}
public class Client {
private static final int PORT = 20000;
private static final int LOCAL_PORT = 20001;
public static void main(String[] args) throws IOException {
Socket socket = createSocket();
initSocket(socket);
// 链接到本地20000端口,超时时间3秒,超过则抛出超时异常
socket.connect(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 3000);
System.out.println("已发起服务器连接,并进入后续流程~");
System.out.println("客户端信息:" + socket.getLocalAddress() + " P:" + socket.getLocalPort());
System.out.println("服务器信息:" + socket.getInetAddress() + " P:" + socket.getPort());
try {
// 发送接收数据
todo(socket);
} catch (Exception e) {
System.out.println("异常关闭");
}
// 释放资源
socket.close();
System.out.println("客户端已退出~");
}
private static Socket createSocket() throws IOException {
/*
// 无代理模式,等效于空构造函数
Socket socket = new Socket(Proxy.NO_PROXY);
// 新建一份具有HTTP代理的套接字,传输数据将通过www.baidu.com:8080端口转发
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(Inet4Address.getByName("www.baidu.com"), 8800));
socket = new Socket(proxy);
// 新建一个套接字,并且直接链接到本地20000的服务器上
socket = new Socket("localhost", PORT);
// 新建一个套接字,并且直接链接到本地20000的服务器上
socket = new Socket(Inet4Address.getLocalHost(), PORT);
// 新建一个套接字,并且直接链接到本地20000的服务器上,并且绑定到本地20001端口上
socket = new Socket("localhost", PORT, Inet4Address.getLocalHost(), LOCAL_PORT);
socket = new Socket(Inet4Address.getLocalHost(), PORT, Inet4Address.getLocalHost(), LOCAL_PORT);
*/
Socket socket = new Socket();
// 绑定到本地20001端口
socket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), LOCAL_PORT));
return socket;
}
private static void initSocket(Socket socket) throws SocketException {
// 设置读取超时时间为2秒
socket.setSoTimeout(2000);
// 是否复用未完全关闭的Socket地址,对于指定bind操作后的套接字有效
socket.setReuseAddress(true);
// 是否开启Nagle算法
socket.setTcpNoDelay(true);
// 是否需要在长时无数据响应时发送确认数据(类似心跳包),时间大约为2小时
socket.setKeepAlive(true);
// 对于close关闭操作行为进行怎样的处理;默认为false,0
// false、0:默认情况,关闭时立即返回,底层系统接管输出流,将缓冲区内的数据发送完成
// true、0:关闭时立即返回,缓冲区数据抛弃,直接发送RST结束命令到对方,并无需经过2MSL等待
// true、200:关闭时最长阻塞200毫秒,随后按第二情况处理
socket.setSoLinger(true, 20);
// 是否让紧急数据内敛,默认false;紧急数据通过 socket.sendUrgentData(1);发送
socket.setOOBInline(true);
// 设置接收发送缓冲器大小
socket.setReceiveBufferSize(64 * 1024 * 1024);
socket.setSendBufferSize(64 * 1024 * 1024);
// 设置性能参数:短链接,延迟,带宽的相对重要性
socket.setPerformancePreferences(1, 1, 0);
}
private static void todo(Socket client) throws IOException {
// 得到Socket输出流
OutputStream outputStream = client.getOutputStream();
// 得到Socket输入流
InputStream inputStream = client.getInputStream();
byte[] buffer = new byte[256];
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
// byte
byteBuffer.put((byte) 126);
// char
char c = 'a';
byteBuffer.putChar(c);
// int
int i = 2323123;
byteBuffer.putInt(i);
// bool
boolean b = true;
byteBuffer.put(b ? (byte) 1 : (byte) 0);
// Long
long l = 298789739;
byteBuffer.putLong(l);
// float
float f = 12.345f;
byteBuffer.putFloat(f);
// double
double d = 13.31241248782973;
byteBuffer.putDouble(d);
// String
String str = "Hello你好!";
byteBuffer.put(str.getBytes());
// 发送到服务器
outputStream.write(buffer, 0, byteBuffer.position() + 1);
// 接收服务器返回
int read = inputStream.read(buffer);
System.out.println("收到数量:" + read);
// 资源释放
outputStream.close();
inputStream.close();
}
}