Java Socket Socket,即套接字,是一种介于应用和传输层之间的抽象层,可实现同网络内不同应用之间相互发送和接收数据。
对于 Socket,有以下三个关键问题:
1. 如何定位到目标应用
在发送和接收数据时,关键在于如何定位到应用,而定位到应用首先需要定位到主机。
在同一子网内,每台主机的 IP 地址是唯一的,故可以借助 IP 地址定位主机,而在每一台主机上,不同的程序往往监听在不同的端口号上,因此,可以利用端口号来定位应用。
因此,一个 socket 由一个 url 地址和一个 port 端口号唯一确定。
2. 与TCP/IP的关系
socket 与 TCP/IP 协议簇无关,其本质是编程接口,用于向应用提供数据传输服务的接口 。Java 中的 socket 主要是基于 TCP/IP 的封装,在使用过程中,应用可借助socket接口,建立基于TCP或UDP的数据传输机制,从而实现同网络跨应用之间的数据传输功能,从而将应用与传输层的具体协议分离开来,使得上层应用无需关注过多细节,只专注数据传输即可。
3. Socket的工作过程
根据传输类型选择 socket 接口进行调用,以 TCP/IP 协议簇为例,主要包括两种socket,即针对 TCP 的流 socket 和针对 UDP 的数据报 socket。
根据目的应用主机、所在端口号等信息实现寻址过程。
链接建立与数据传输。
关闭链接。
TCP Echo Request 实例 Server端
import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;import java.net.SocketAddress;public class TCPEchoServer { private static final int BUFSIZE = 32 ; public static void main (String[] args) throws IOException { if (args.length != 1 ) { throw new IllegalArgumentException("Parameter(s): <Port>" ); } int servPort = Integer.parseInt(args[0 ]); ServerSocket servSock = new ServerSocket(servPort); int recvMsgSize; byte [] receiveBuf = new byte [BUFSIZE]; while (true ) { Socket clntSock = servSock.accept(); SocketAddress clientAddress = clntSock.getRemoteSocketAddress(); System.out.println("Handling client at " + clientAddress); InputStream in = clntSock.getInputStream(); OutputStream out = clntSock.getOutputStream(); while ((recvMsgSize = in.read(receiveBuf)) != -1 ) { out.write(receiveBuf, 0 , recvMsgSize); } clntSock.close(); } } }
说明:
Socket 中的输入输出流是流抽象,可看做一个字符序列,输入流支持读取字节,输出流支持取出字节。每个 Socket 实例都维护了 一个 InputStream 和一个 OutputStream 实例,数据传输也主要依靠从流中获取数据并解析的过程。
ServerSocket 与 Socket 区别,ServerSocket 主要用于服务端,用于为新的 TCP 连接请求提供一个新的已连接的 Socket 实例。Socket 则用于服务端和客户端,用于表示 TCP 连接的一端。因此,服务端需要同时处理 ServerSocket 和 Socket 实例,而客户端只需要处理 Socket 实例即可。
Client端
import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;import java.net.SocketException;public class TCPEchoClient { public static void main (String[] args) throws IOException { if ((args.length < 2 ) || (args.length > 3 )) { throw new IllegalArgumentException("Parameter(s): <server><word> [<Port>]" ); } String server = args[0 ]; byte [] data = args[1 ].getBytes(); int servPort = (args.length == 3 ) ? Integer.parseInt(args[2 ]) : 10240 ; Socket socket = new Socket(server, servPort); System.out.println("Connected to server... sending echo string" ); InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); out.write(data); int totalBytesRcvd = 0 ; int bytesRcvd; while (totalBytesRcvd < data.length) { if ((bytesRcvd = in.read(data, totalBytesRcvd, data.length - totalBytesRcvd)) == -1 ) { throw new SocketException("Connection closed permaturely" ); } totalBytesRcvd += bytesRcvd; } System.out.println("Received: " + new String(data)); socket.close(); } }
说明:
发送数据时只通过 write() 方法,接收时为何需要多个 read() 方法? TCP 协议无法确定在 read() 和 write() 方法中所发送信息的界限,而且发送过程中可能存在乱序现象,即分割成多个部分,所以无法通过一次 read() 获取到全部数据信息。
UDP Echo Request 实例 Server端
import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.SocketException;public class UDPEchoServer { private static final int ECHOMAX = 255 ; public static void main (String[] args) throws IOException { if (args.length != 1 ) { throw new IllegalArgumentException("Parameter(s): <Port>" ); } int servPort = Integer.parseInt(args[0 ]); DatagramSocket socket = new DatagramSocket(servPort); DatagramPacket packet = new DatagramPacket(new byte [ECHOMAX], ECHOMAX); while (true ) { socket.receive(packet); System.out.println("Handling client at " + packet.getAddress().getHostAddress() + " on port " + packet.getPort()); socket.send(packet); packet.setLength(ECHOMAX); } } }
说明:
UDP服务端 与 TCP 服务端不同,TCP 对于每一个客户端请求都需要先建立连接,而 UDP 则不需要。因此,UDP 只需创建一个 Socket 等待客户端连接即可。
在该 UDP 服务器的实现中,只接收和发送数据报文中的前 ECHOMAX 个字符,超出部分直接丢弃。
在处理过接收到的消息后,数据包的内部长度会设置为刚处理过的消息长度,通常比初始长度要短,因此需重置缓冲区为初始长度。否则后续可能会使得缓冲区长度不断减小,使得数据包被截断。
Client端
import java.io.IOException;import java.io.InterruptedIOException;import java.net.*;public class UDPEchoClient { private static final int TIMEOUT = 3000 ; private static final int MAXTRIES = 5 ; public static void main (String[] args) throws IOException { if ((args.length < 2 ) || (args.length > 3 )) { throw new IllegalArgumentException("Parameter(s): <Server> <Word> [<Port>]" ); } InetAddress serverAddress = InetAddress.getByName(args[0 ]); byte [] byteToSend = args[1 ].getBytes(); int servPort = (args.length == 3 ) ? Integer.parseInt(args[2 ]) : 10241 ; DatagramSocket socket = new DatagramSocket(); socket.setSoTimeout(TIMEOUT); DatagramPacket sendPacket = new DatagramPacket(byteToSend, byteToSend.length, serverAddress, servPort); DatagramPacket receivePacket = new DatagramPacket(new byte [byteToSend.length], byteToSend.length); int tries = 0 ; boolean receivedResponse = false ; do { socket.send(sendPacket); try { socket.receive(receivePacket); if (!receivePacket.getAddress().equals(serverAddress)) { throw new IOException("Received packet from an unknown source" ); } receivedResponse = true ; } catch (InterruptedIOException e) { tries += 1 ; System.out.println("Timed out, " + (MAXTRIES - tries) + " more tries..." ); } } while (!receivedResponse && (tries < MAXTRIES)); if (receivedResponse) { System.out.println("Received: " + new String(receivePacket.getData())); } else { System.out.println("No response -- giving up." ); } socket.close(); } }
说明:
由于 UDP 提供的是尽最大可能的交付,所以在发送 Echo Request 请求时,无法保证一定可以送达目标地址和端口,因此考虑设置重传次数,若在超过最大等待时间后仍未收到回复,则重发当前请求,若重发次数超过最大重试次数,则可直接返回未发送成功。
UDP Socket 与 TCP Socket 区别
UDP 保存了消息的边界信息,而 TCP 则没有。 在 TCP 中需通过多次 read() 来接收一次 write() 的信息,而 UDP 中对于单次 send() 的数据,最多只需一次 receive() 调用。
TCP 存在传输缓冲区,UDP 则无需对数据进行缓存。 由于 TCP 存在错误重传机制,因此需保留数据的缓存,以便于重传操作,当调用 write() 方法并返回后,数据被复制到传输缓冲区中,数据有可能处于发送过程中或还没有发生传送。而 UDP 则不存在该机制,因此无需缓存数据,当调用 send() 方法返回后,消息处于发送过程中。
UDP 会丢掉超过最大长度限制的数据,而 TCP 不会。 在 TCP 中,一旦建立连接后,对于所有数据都可以看做一个连续的字节序列。而在 UDP 中接收到的消息则可能来自于不同的源地址和端口,因此会将接收到的数据放在消息队列中,按照顺序来响应,超过最大长度的消息直接截断。Datagrampacket 所能传输的最大数据量为 65507 字节,也就是一个 UDP 报文能承载的最大数据。
参考 https://blog.csdn.net/wyzidu/article/details/83826656
https://www.jianshu.com/p/cde27461c226