基础 所谓 socket
通常也称作”套接字”,用于描述 IP
地址和端口,是一个通信链的句柄。网络上具有唯一标识的 IP
地址和端口组合在一起才能构成唯一能识别的标识符套接字,应用程序通常通过”套接字”向网络发出请求或者应答网络请求。
几个常见类
InetAddress
用于标识网络上的硬件资源,主要表示 IP
地址。
SocketAddress
表示 Sokcet
地址,不包含使用的具体协议,是一个抽象类。
InetSocketAddress
继承了 SocketAddress
,包含主机名,IP
地址和端口(hostname, InetAddress, port
)。
Socket
客户端 Socket
,默认采用的传输层协议为 TCP
。
ServerSocket
服务器 Socket
,默认采用的传输层协议为 TCP
。等待客户端请求,并基于这些请求执行操作返回一个结果。
SocketImpl
通用抽象类,用来创建客户端和服务端具体的 Socket
。Socket
通信流程的所有方法都是通过该类和子类实现的。
DatagramPacket
表示存放数据的数据报。
DatagramSocket
实现了一个发送和接收数据报的 socket
,传输层协议使用 UDP
。客户端和服务端都使用该套接字来实现通信。
传输协议
TCP:Tranfer Control Protocol
是一种面向连接的保证可靠传输的协议,通过 TCP
协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个 socket
之间必须建立连接,以便在 TCP
协议的基础上进行通信,当一个 server socket
等待建立连接 accept
时,客户端 socket
可以要求进行连接。一旦连接起来后,它们就可以进行双向数据传输,双方都可以进行发送或接收数据。
UDP:User Datagram Protocol
是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。传输数据时数据报大小必须限定在 64KB
之内。
端口 区分一台主机的多个不同应用程序,端口号范围为 0-65535,其中 0-1023 位为系统保留。如:HTTP:80 FTP:21 Telnet:23
。
五元组 通信术语,通常是指:源 IP
地址,源端口,目的 IP
地址,目的端口和传输层协议,两台计算机通信必须要明确指定五元组的值。
地址和端口Socket
中包含了源和目标的 IP
地址,端口。
传输协议类型Socket
表示 TCP
协议;DatagramSocket
表示 UDP
协议。
输入输出流读写 Soket
数据连接是全双工的,一旦建立连接可以得到 Socket
的输入流和输出流,主机可以使用这两个流同时发送和接受数据。流是同步的 ,也就是说当请求流读/写一段数据时,阻塞等待直到有数据。Java
还支持使用通道和缓冲区的非阻塞 I/O
(NIO
),暂不讨论。
socket.getInputStream()
获取输入流,通过输入流读取 数据。
socket.getOutputStream()
获取输出流,通过输出流写入 数据。
??关闭一个流也会也会关闭一个 Sokcet
连接。??对于同一个 Socket
,如果关闭了输出流,则与该输出流关联的 Socket
也会被关闭,所以一般不用关闭流,直接关闭 Socket
即可。
Socket
原理机制
通信的两端都有 Socket
网络通信其实就是 Socket
间的通信
数据在两个 Socket
间通过 IO
传输
四种常见异常 以下四种类型异常都是继承于 IOException
,所以很多之后直接弹出 IOException
即可。
UnkownHostException
:主机名字或 IP
错误
ConnectException
:服务器拒绝连接、服务器没有启动、超出队列数,拒绝连接等
SocketTimeoutException
:连接超时
BindException
:Socket
对象无法与指定的本地 IP
地址或端口绑定
Socket
构造方法 1 2 3 4 5 6 Socket(String host, int port)throws UnknownHostException, IOException Socket(InetAddress address, int port) throws IOException Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException ... private Socket (SocketAddress address, SocketAddress localAddr, boolean stream) throws IOException
Socket
有多个构造方法,但是最终调用的是最后一个。至少需要指定目的主机名 host
或者 IP
地址,以及端口号 port
。 参数解析:
address
远程即服务端,包含目标 IP
地址,主机名,端口。不能为空,否则会抛出异常。
localAddr
本地即客户端,包含源 IP
地址,主机名,端口。如果为空,表示系统自动分配。
stream
为 true
表示使用流式连接即 TCP
方式;为 false
表示使用数据报即 UDP
方式。默认为 true
。
关闭 在 try - finally
块中采用 close-if-not-null
来关闭 Socket
连接。
Socket
选项
TCP_NODELAY
设置为 true
可以保证无论包的大小都会尽快发送,不用缓冲到足够大的数据包。
SO_LINGER
指定 Socket
关闭时,还没有发送的数据包如何处理。默认情况下 close
方法会立即返回,但系统会尝试发送剩余的数据。如果延迟时间设置为 0,所有未发送数据都会被丢弃。如果设置了事件,close
会阻塞到指定时间,等待数据接收和确认,如果超出时间,剩余数据将会被丢弃。
SO_TIMEOUT
Socket
在读取数据时,read()
会阻塞尽可能长的时间。如果设置后,阻塞时间不会超过指定时间,否则会抛出异常,但是 Socket
仍然是处于连接状态,下次可以继续读。
SO_RCVBUF
TCP
使用缓冲区提升性能,参数为设置接受缓冲区大小。
SO_SNDBUF
设置发送缓冲区大小。
SO_KEEPALIVE
默认值为 false
,如果打开,在 Sokcet
没有数据传输时,每两个小时客户端会发送一个数据包确保服务器是正常的。
SO_OOBINLINE
参数被设置时,TCP
会发送一个紧急数据包,接受方收到后会优先处理。
SO_REUSEADDR
设置 Socket
是否可以重复使用端口,默认是也可以的。
IP_TOS
设置数据拥堵时的处理策略。
常见 API
socket.getRemoteSocketAddress()
获取目的 IP
地址,主机名,端口。
socket.getInetAddress()
获取目的 IP
地址,主机名。
socket.getPort()
获取目的端口。
socket.getLocalSocketAddress()
获取源 IP
地址,主机名,端口。
socket.getLocalAddress()
获取源 IP
地址,主机名。
socket.getLocalPort()
获取源端口。
SocketServer 构造方法 1 2 3 4 public ServerSocket () throws IOException public ServerSocket (int port) throws IOException public ServerSocket (int port, int backlog) throws IOException public ServerSocket (int port, int backlog, InetAddress bindAddr) throws IOException
参数解析:
port 服务器端口号,0 表示自动分配的端口。
backlog 请求链接队列的最大长度。
bindAddr 服务器将绑定的本地地址 InetAddress
关闭 在 try - finally
块中采用 close-if-not-null
来关闭 SocketServer
连接。
Socket
选项
SO_TIMEOUT
设置 Socket
阻塞的时间,如:accept, read, receive
。
SO_REUSEADDR
设置 Socket
是否可以重复使用端口,默认是也可以的。
SO_RCVBUF
设置 Socket
接受数据缓冲区大小。
日志 log
服务器需要形成记录日志的习惯,来记录客户端访问信息,出现的错误等等。
多线程设计 通常服务端会有多个客户端访问,所以可以设置一个线程池来处理每个客户端的请求,避免阻塞。
常见 API
serverSocket.getLocalSocketAddress()
获取本地 IP
地址,主机名,端口。典型值为 0.0.0.0/0.0.0.0:***
。
serverSocket.getInetAddress()
获取本地 IP
地址,典型值为 0.0.0.0/0.0.0.0
,即自动分配的本地地址。
serverSocket.getLocalPort
获取本地端口,即 Socket
通信服务端中的目的端口。
客户端和服务端的 TCP
通信步骤 通信流程图
大概流程如下:
服务端 SocketServer
初始化
服务端等待并接受客户端的连接 accept
客户端 Socket
初始化
数据通信
关闭输入输出流,再关闭 Socket/SocketServer
服务端 SocketServer
初始化
创建 Socket
SocketImpl.java: protected abstract void create(boolean stream) throws IOException;
,创建服务端 Socket
。
绑定 bind
SocketImpl.java: protected abstract void bind(InetAddress host, int port) throws IOException;
,显示指定或者系统自动分配本地 IP
地址及空闲端口,绑定后形成目的 IP
地址和端口。
监听 listen
SocketImpl.java: protected abstract void listen(int backlog) throws IOException;
,设置请求连接队列的最大值。
这三步都在 ServerSocket
的构造方法中实现,不用显示调用。
服务端等待接受连接 accept
1 2 SocketServer.java: public Socket accept () throws IOException ; SocketImpl.java: protected abstract void accept (SocketImpl s) throws IOException ;
阻塞等待,监听和接受客户端的 Socket
连接,并返回服务端的 Socket
,最终会调用 SocketImpl
的方法接受连接。
客户端 Socket
初始化
创建 Socket
SocketImpl.java: protected abstract void create(boolean stream) throws IOException;
,创建客户端 Socket
。
绑定 bind
SocketImpl.java: protected abstract void bind(InetAddress host, int port) throws IOException;
,显示指定或者系统自动分配本地 IP
地址及空闲端口,绑定后形成源 IP
地址和端口。
连接 connect
1 2 3 4 5 Socket.java: public void connect (SocketAddress endpoint) throws IOException Socket.java: public void connect (SocketAddress endpoint, int timeout) throws IOException SocketImpl.java: protected abstract void connect (String host, int port) throws IOException ;SocketImpl.java: protected abstract void connect (InetAddress address, int port) throws IOException ; SocketImpl.java: protected abstract void connect (SocketAddress address, int timeout) throws IOException ;
SocketAddress
包含目标 IP
地址和端口,在指定时间内连接服务端的 Socket
。如果 timeout
为 0,表示不限时。
这三步都在 Socket
的构造方法中实现,不用显示调用。也就是说客户端在 TCP
协议的 Socket
编程中,非常简单,只需要指定目的 SocketAddress
,就可以直接获取输入输出流来读写数据。
数据通信
服务端建立连接后 通过输入流读取客户端发送的请求信息;通过输出流向客户端发送响应信息。
客户端建立连接后 通过输出流向服务器端发送请求信息;通过输入流读取服务端响应的信息。
关闭输入输出流,再关闭 Socket
ServerSocket
和 Socket
没有先后关闭的必然顺序。ServerSocket
先关闭,并不会影响当前已经连接的 Sokcet
TCP
通信示例流程图
服务端 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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 public class TestTCPSocketServer { private static final String QUIT = "quit" ; private static final String EXIT = "exit" ; private volatile boolean isQuited = false ; private ServerSocket ss = null ; private Socket socket = null ; private Thread listenQuit; private ListenRunnable listenRunnable; private ExecutorService threadPool; private class ListenRunnable implements Runnable { private ServerSocket ss; public ListenRunnable (ServerSocket ss) { this .ss = ss; } @Override public void run () { try { BufferedReader ssRead = new BufferedReader( new InputStreamReader(System.in)); System.out.println(Thread.currentThread().getName() + " listen server input..." ); String ssInput = ssRead.readLine(); System.out.println(ssInput); if (willQuit(ssInput)) { ssRead.close(); isQuited = true ; } } catch (IOException e){ System.err.println(e); } finally { if (isQuited) { if (threadPool != null && !threadPool.isShutdown()) { threadPool.shutdown(); } closeServerSocket(ss); } } } } private class ClientCallable implements Callable <Void > { private Socket socket; public ClientCallable (Socket socket) { this .socket = socket; } @Override public Void call () { try { String origHost = socket.getInetAddress().toString(); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream())); String line = in.readLine(); while (line != null && !willQuit(line)) { System.out.println(origHost + ", you input is : " + line); line = in.readLine(); } in.close(); } catch (IOException e){ System.err.println(e); } finally { closeSocket(socket); } return null ; } } private void closeSocket (Socket socket) { if (socket == null ) return ; try { System.out.println(socket.getInetAddress().toString() + " closed." ); socket.close(); } catch (IOException e){ System.err.println(e); } } private void closeServerSocket (ServerSocket ss) { if (ss == null ) return ; try { System.out.println("Server closed" ); ss.close(); } catch (IOException e){ System.err.println(e); } } private boolean willQuit (String str) { return str != null && (str.equalsIgnoreCase(QUIT) || str.equalsIgnoreCase(EXIT)); } private void startServerDaemon (ServerSocket ss) { listenRunnable = new ListenRunnable(ss); listenQuit = new Thread(listenRunnable); listenQuit.setDaemon(true ); listenQuit.start(); } private void run () { try { ss = new ServerSocket(10000 ); startServerDaemon(ss); System.out.println("serverSocket.getInetAddress: " + ss.getInetAddress()); System.out.println("serverSocket.getLocalSocketAddress: " + ss.getLocalSocketAddress()); System.out.println("serverSocket.getLocalPort: " + ss.getLocalPort()); System.out.println("The server is waiting you connect and input..." ); while (!isQuited) { socket = ss.accept(); System.out.println("Socket: " + socket + " connected." ); Callable<Void> client = new ClientCallable(socket); threadPool.submit(client); System.out.println("ReAccept..." ); } } catch (IOException e) { e.printStackTrace(); } finally { closeSocket(socket); closeServerSocket(ss); } } public TestTCPSocketServer () { threadPool = Executors.newFixedThreadPool(20 ); } public static void main (String[] args) { TestTCPSocketServer server = new TestTCPSocketServer(); server.run(); } }
大致思路:
初始化线程池,用于接受并执行客户端的请求任务
创建 ServerSocket
对象,绑定监听端口
开启后台线程,监听终端输入 exit/quit
循环调用 accept()
方法,监听客户端请求
接收到请求后,为每个客户端创建 Socket
专线连接
线程池为每个客户端提供一个单独线程用于 Socket
通信
服务器端继续等待新的客户端连接
独立线程中,通过输入流读取客户端发送的请求信息,通过输出流向客户端发送响应信息
客户端断开连接后,关闭相关资源,并关闭该 Socket
,释放当前线程
服务端接收到终端输入退出指令后,关闭线程池并关闭 ServerSocket
客户端 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 public class TestTCPSocketClient { private static final String QUIT = "quit" ; private static final String EXIT = "exit" ; Socket socket = null ; public TestTCPSocketClient () { try { socket = new Socket("127.0.0.1" , 10000 ); System.out.println("Socket: " + socket); System.out.println("socket.getRemoteSocketAddress: " + socket.getRemoteSocketAddress()); System.out.println("socket.getInetAddress: " + socket.getInetAddress()); System.out.println("socket.getPort: " + socket.getPort()); System.out.println("socket.getLocalSocketAddress: " + socket.getLocalSocketAddress()); System.out.println("socket.getLocalAddress: " + socket.getLocalAddress()); System.out.println("socket.getLocalPort: " + socket.getLocalPort()); PrintWriter out = new PrintWriter(socket.getOutputStream() , true ); BufferedReader line = new BufferedReader( new InputStreamReader(System.in)); System.out.println("read line! block..." ); String read = line.readLine(); while (!willQuit(read)) { out.println(read); read = line.readLine(); } System.out.println("read over!" ); line.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null ) { try { socket.close(); } catch (IOException e){ System.err.println(e); } } } } private boolean willQuit (String str) { return str != null && (str.equalsIgnoreCase(QUIT) || str.equalsIgnoreCase(EXIT)); } public static void main (String[] args) { new TestTCPSocketClient(); } }
大致思路:
创建 Socket
对象,指明需要连接的服务器的地址和端口号
连接建立后,从终端读取输入信息
通过输出流向服务器端发送读取到的信息
通过输入流获取服务器响应的信息
关闭相关资源,关闭 Socket
DatagramPacket
数据报(Datagram
):将数据填充到 UDP
包中。数据报存放到 DatagramPacket
中,而 DatagramSocket
用来收发数据报。数据报的所有信息都包含在这个包中(包括发往的目标地址),Socket
只需要了解端口监听和发送。UDP
这种模式并没有像 TCP
那样两台主机有唯一连接的概念,一个 Socket
会收发所有指定端口的数据,而不会关心对方是哪个主机。 DatagramSocket
可以从多个独立主机收发数据,与 TCP
不同,这个 Socket
并不会专用于某个连接。TCP
中 Socket
把网络连接看作是一个流,通过输入输出流来收发数据。但是 UDP
处理的总是单个数据报包,填充在数据报包中的所有数据会以一个包的形式发送,这些数据要么全部接受,要么全部丢弃。一个包与另一个包并不一定相关,而且无法确认先后顺序。 对于流必须提供数据的有序队列,而数据报会尽可能快的发送到接收方。
UDP
服务端通常不需要使用多线程,不会阻塞等待客户端的响应,除非为了应对大量耗时工作才会使用多线程。
UDP
数据报是基于 IP
数据报建立的,只向其底层 IP
数据添加了很少的内容(8 个字节的首部信息)。UDP
包中数据的理论长度为 65507 个字节,但实际上很多平台限制往往是 8KB
(8192 字节),大多数情况下更大的包会被简单的截取为 8KB
。并且为了保证安全性,UDP
包的数据部分应该尽量少于 512 字节。
构造方法 1 2 3 4 5 6 7 8 9 10 public DatagramPacket (byte buf[], int offset, int length) {...}public DatagramPacket (byte buf[], int length) {...}public DatagramPacket (byte buf[], int offset, int length, InetAddress address, int port) {...}public DatagramPacket (byte buf[], int offset, int length, SocketAddress address) {...}public DatagramPacket (byte buf[], int length, InetAddress address, int port) {...}public DatagramPacket (byte buf[], int length, SocketAddress address) {...}
注意:DatagramPacket
虽然都是构造方法,但是有 2 个是接收数据报的构造方法,剩余 4 个是发送数据报的构造方法。普通情况下构造方法主要用于不同对象提供不同的类型信息,而不是像数据报这样提供不同的功能对象。
参数解析:
buf[]
保存接收或发送数据的数组,也就是不管是发送还是接受拿到的都是字节数组。
length
数组中用于接受或发送数据的长度。
offset
数组的偏移量,默认为 0。
InetAddress
发送数据时接收方的 IP
地址。
port
发送数据时接收方的端口。
SocketAddress
发送数据时接收方的地址和端口
get
方法get
方法可以获取构造方法中传进来的所有参数信息。
1 2 3 4 5 6 7 8 9 10 public synchronized InetAddress getAddress () {...}public synchronized int getPort () {...}public synchronized SocketAddress getSocketAddress () {...}public synchronized byte [] getData() {...}public synchronized int getOffset () {...}public synchronized int getLength () {...}
set
方法set
方法可以在构造方法创建数据报后,改变所有的数据报信息,相当于重新创建了一个数据报。因为 DatagramPacket
对象的重复创建和垃圾回收影响性能,所以重用对象比构造对象快的多。
1 2 3 4 5 6 7 8 public synchronized void setAddress (InetAddress iaddr) {...}public synchronized void setPort (int iport) {...}public synchronized void setSocketAddress (SocketAddress address) {...}public synchronized void setData (byte [] buf) {...}public synchronized void setData (byte [] buf, int offset, int length) {...}public synchronized void setLength (int length) {...}
注意:所有的 set
方法都加了同步锁。
DatagramSocket
要收发数据报,需要先打开一个 DatagramSocket
,而所有的 DatagramSocket
必须绑定一个本地端口,这个端口用来监听收发的数据,并写入数据报的首部。DatagramSocket
只存储本地地址和端口,所有的远程地址和端口都在 DatagramPacket
中,所以 DatagramSocket
可以同时和多台主机收发数据(只要 DatagramPacket
不同就可以了)。
构造方法 1 2 3 4 5 6 public class DatagramSocket implements java .io .Closeable { public DatagramSocket () throws SocketException {...} public DatagramSocket (int port) throws SocketException {...} public DatagramSocket (int port, InetAddress laddr) throws SocketException {...} public DatagramSocket (SocketAddress bindaddr) throws SocketException {...} }
收发数据报 1 2 public void send (DatagramPacket p) throws IOException {...}public synchronized void receive (DatagramPacket p) throws IOException {...}
send
用来发送数据报;receive
加了同步锁,并且会阻塞 当前线程,直到有数据到达,接收一个数据报。
常见 API
1 2 3 4 5 6 7 8 9 10 11 public void close () {...}public SocketAddress getLocalSocketAddress () {...}public InetAddress getLocalAddress () {...}public int getLocalPort () {...}public void connect (SocketAddress addr) throws SocketException {...}public void connect (InetAddress address, int port) {...}public void disconnect () {...}
Socket
选项
SO_TIMEOUT
Socket
在接收数据时,receive()
会阻塞尽可能长的时间。如果设置后,阻塞时间不会超过指定时间,否则会抛出异常。设置为 0 表示永不超时。
SO_RCVBUF
UDP
设置网络接收缓冲区大小,与 TCP
对比,UDP
应该设置足够大的缓冲区。
SO_SNDBUF
建议发送缓冲区大小,但是操作系统可以忽略这个建议。
SO_REUSEADDR
和 TCP Socket
的意义不同,设置后表示是否允许多个数据报同时绑定到相同的端口和地址。重用端口通常会用于 UDP
组播。
SO_BROADCAST
控制是否允许一个 Socket
向广播地址收发包,默认是打开的。UDP
广播通常用于 DHCP
协议,路由器和网关一般不转发广播消息,但仍然会在本地网络中带来大量业务流。
IP_TOS
设置数据拥堵时的处理策略。
UDP
通信示例流程图
服务端 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 public class TestDayTimeUDPServer { private static final int PORT = 13 ; private static final int CAPACITY = 1024 ; private static final Logger audit = Logger.getLogger("requests" ); private static final Logger errors = Logger.getLogger("errors" ); public static void main (String[] args) { DatagramSocket datagramSocket = null ; try { datagramSocket = new DatagramSocket(PORT); System.out.println("Source: " + datagramSocket.getLocalSocketAddress()); while (true ){ try { DatagramPacket request = new DatagramPacket( new byte [CAPACITY], CAPACITY); datagramSocket.receive(request); String dayTime = new Date().toString(); byte [] data = dayTime.getBytes("US-ASCII" ); DatagramPacket response = new DatagramPacket(data, data.length, request.getAddress(), request.getPort()); datagramSocket.send(response); System.out.println("Destination: " + request.getSocketAddress()); audit.info(dayTime + " " + request.getAddress()); } catch (IOException e){ errors.log(Level.SEVERE, e.getMessage(), e); } } } catch (IOException e){ errors.log(Level.SEVERE, e.getMessage(), e); } } }
大致思路:
创建 DatagramSocket
,绑定端口号
创建 DatagramPacket
,指定目标地址和端口
阻塞等待接收客户端发送的数据
向客户端发送响应数据
客户端 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 public class TestUDPGetDayTime { private static final String HOSTNAME = "127.0.0.1" ; private static final int PORT = 38910 ; private static final int DELAY_TIME = 10000 ; private static final int CAPACITY = 1024 ; public static void main (String[] args) { DatagramSocket datagramSocket = null ; try { datagramSocket = new DatagramSocket(); datagramSocket.setSoTimeout(DELAY_TIME); InetAddress host = InetAddress.getByName(HOSTNAME); DatagramPacket request = new DatagramPacket(new byte [1 ], 1 , host, PORT); DatagramPacket response = new DatagramPacket(new byte [CAPACITY], CAPACITY); datagramSocket.send(request); System.out.println("Source: " + datagramSocket.getLocalSocketAddress()); System.out.println("Destination: " + request.getSocketAddress()); datagramSocket.receive(response); String dayTime = new String(response.getData(), 0 , response.getLength(), "US-ASCII" ); System.out.println(dayTime); } catch (IOException e){ System.err.println(e); } finally { if (datagramSocket != null ) { datagramSocket.close(); } } } }
大致思路:
定义发送信息,并存储到数组中
创建 DatagramPacket
,指定目标地址和端口,数据数组
创建 DatagramSocket
,绑定本地端口
向服务端请求数据
从服务端获取响应数据
参考文档
Java Socket编程
Socket和ServerSocket学习笔记
Java网络socket编程详解
TCP数据段格式+UDP数据段格式详解