概念
Socket
在开发过程中,会用到操作系统提供的类库,这种类库一般被称为 API(Application Programming Interface,应用编程接口); Socket 是TCP/IP 提供的用于网络开发的API;Socket原本是由 BSD UNIX 开发的,但是后被移植到了 Windows 以及嵌入式操作系统中。
TCP(Transmission Control Protocol )
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。
三次握手
TCP建立连接的过程包括三次握手
- 客户端发送 SYN(SEQ=x)报文给服务器端,进入 SYN_SEND 状态。
- 服务器端收到 SYN 报文,回应一个 SYN (SEQ=y)ACK(ACK=x+1)报文,进入 SYN_RECV 状态。
- 客户端收到服务器端的 SYN 报文,回应一个 ACK(ACK=y+1)报文,进入 Established 状态。
四次挥手
TCP断开连接的过程包括四次挥手
- 某个端首先调用 close,称该端执行“主动关闭”。该端的 TCP 于是发送一个 FIN 分节,表示数据发送完毕。
- 接收到这个 FIN 的对端执行 “被动关闭”,这个 FIN 由 TCP 确认。
- 一段时间后,接收到这个文件结束符的应用进程将调用 close 关闭它的套接字,这导致它的 TCP 也发送一个 FIN。
- 接收这个最终FIN的原发送端 TCP(即执行主动关闭的那一端)确认这个 FIN。
无论是客户还是服务器,任何一端都可以执行主动关闭。通常情况是,客户执行主动关闭,但是某些协议,例如,HTTP/1.0却由服务器执行主动关闭。
UDP(User Datagram Protocol)
UDP是一种无连接的、不可靠的、面向数据报文的运输层协议;由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
关键类
ServerSocket
供服务端使用,以获取一个端口,并监听客户端请求
序号 | 方法 | 描述 |
---|---|---|
1 | public ServerSocket() throws IOException | 创建非绑定服务器套接字 |
2 | public ServerSocket(int port) throws IOException | 创建绑定到特定端口的服务器套接字 |
3 | public ServerSocket(int port, int backlog) throws IOException | 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号 |
4 | public ServerSocket(int port, int backlog, InetAddress address) throws IOException | 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器 |
5 | public int getLocalPort() | 返回此套接字在其上侦听的端口 |
6 | public Socket accept() throws IOException | 侦听并接受到此套接字的连接 |
7 | public void setSoTimeout(int timeout) | 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位 |
8 | public void bind(SocketAddress host, int backlog) | 将 ServerSocket 绑定到特定地址(IP 地址和端口号) |
Socket
序号 | 方法 | 描述 |
---|---|---|
1 | public Socket(String host, int port) throws UnknownHostException, IOException. | 创建一个流套接字并将其连接到指定主机上的指定端口号 |
2 | public Socket(InetAddress host, int port) throws IOException | 创建一个流套接字并将其连接到指定 IP 地址的指定端口号 |
3 | public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException | 创建一个套接字并将其连接到指定远程主机上的指定远程端口 |
4 | public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException | 创建一个套接字并将其连接到指定远程地址上的指定远程端口 |
5 | public Socket() | 通过系统默认类型的 SocketImpl 创建未连接套接字 |
6 | public void connect(SocketAddress host, int timeout) throws IOException | 将此套接字连接到服务器,并指定一个超时值 |
7 | public InetAddress getInetAddress() | 返回套接字连接的地址 |
8 | public int getPort() | 返回此套接字连接到的远程端口 |
9 | public int getLocalPort() | 返回此套接字绑定到的本地端口 |
10 | public SocketAddress getRemoteSocketAddress() | 返回此套接字连接的端点的地址,如果未连接则返回 null |
11 | public InputStream getInputStream() throws IOException | 返回此套接字的输入流 |
12 | public OutputStream getOutputStream() throws IOException | 返回此套接字的输出流 |
13 | public void close() throws IOException | 关闭此套接字 |
DatagramSocket
序号 | 方法 | 描述 |
---|---|---|
1 | 1 | 1 |
DatagramPacket
序号 | 方法 | 描述 |
---|---|---|
1 | 1 | 1 |
示例
TCP
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class GreetingTcp {
private static final String LOCAL_HOST = "localhost";
private static final int PORT_SERVER = 2048;
public static void main(String[] args) {
try {
// 开启服务端
Server server = new Server();
server.start();
// 开启客户端1
Client client1 = new Client(1);
client1.start();
// 开启客户端2
Client client2 = new Client(2);
client2.start();
} catch (IOException e) {
e.printStackTrace();
}
}
private static class Client extends Thread {
private int num;
Client(int num) {
this.num = num;
}
@Override
public void run() {
String serverName = LOCAL_HOST;
int port = PORT_SERVER;
try {
// 连接(客户端端口由系统随机分配)
Socket client = new Socket(serverName, port);
System.out.println("客户端-" + num + "-log--连接成功,"
+ "LocalSocketAddress:" + client.getLocalSocketAddress()
+ ",RemoteSocketAddress:" + client.getRemoteSocketAddress()
+ ",LocalAddress:" + client.getLocalAddress()
+ ",InetAddress:" + client.getInetAddress());
// 发送
OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.writeUTF("=====Hello from " + client.getLocalSocketAddress() + "====");
// 接收
InputStream inFromServer = client.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
System.out.println(in.readUTF());
// 关闭
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static class Server extends Thread {
ServerSocket serverSocket;
Server() throws IOException {
serverSocket = new ServerSocket(PORT_SERVER);
serverSocket.setSoTimeout(10000);
System.out.println("服务端-0-log--创建欢迎socket,"
+ "LocalSocketAddress:" + serverSocket.getLocalSocketAddress()
+ ",InetAddress:" + serverSocket.getInetAddress());
}
@Override
public void run() {
while (!interrupted()) {
try {
// 连接
Socket server = serverSocket.accept();
System.out.println("服务端-0-log--创建连接socket,"
+ "LocalSocketAddress:" + server.getLocalSocketAddress()
+ ",RemoteSocketAddress:" + server.getRemoteSocketAddress()
+ ",LocalAddress:" + server.getLocalAddress()
+ ",InetAddress:" + server.getInetAddress());
// 接收
DataInputStream in = new DataInputStream(server.getInputStream());
System.out.println(in.readUTF());
// 回复
DataOutputStream out = new DataOutputStream(server.getOutputStream());
out.writeUTF("=====Hello too from " + server.getLocalSocketAddress() + "====");
// 关闭
server.close();
} catch (SocketTimeoutException s) {
System.out.println("服务端-0-log--ServerSocket timed out!");
break;
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
}
}
运行结果:
服务端-0-log--创建欢迎socket,LocalSocketAddress:0.0.0.0/0.0.0.0:2048,InetAddress:0.0.0.0/0.0.0.0
客户端-2-log--连接成功,LocalSocketAddress:/127.0.0.1:59159,RemoteSocketAddress:localhost/127.0.0.1:2048,LocalAddress:/127.0.0.1,InetAddress:localhost/127.0.0.1
客户端-1-log--连接成功,LocalSocketAddress:/127.0.0.1:59160,RemoteSocketAddress:localhost/127.0.0.1:2048,LocalAddress:/127.0.0.1,InetAddress:localhost/127.0.0.1
服务端-0-log--创建连接socket,LocalSocketAddress:/127.0.0.1:2048,RemoteSocketAddress:/127.0.0.1:59159,LocalAddress:/127.0.0.1,InetAddress:/127.0.0.1
=====Hello from /127.0.0.1:59159====
=====Hello too from /127.0.0.1:2048====
服务端-0-log--创建连接socket,LocalSocketAddress:/127.0.0.1:2048,RemoteSocketAddress:/127.0.0.1:59160,LocalAddress:/127.0.0.1,InetAddress:/127.0.0.1
=====Hello from /127.0.0.1:59160====
=====Hello too from /127.0.0.1:2048====
UDP
import java.io.*;
import java.net.*;
public class GreetingUDP {
private static final String LOCAL_HOST = "localhost";
private static final int PORT_SERVER = 2049;
public static void main(String[] args) {
try {
// 开启服务端
Server server = new Server();
server.start();
// 开启客户端1
Client client1 = new Client(1);
client1.start();
// 开启客户端2
Client client2 = new Client(2);
client2.start();
} catch (IOException e) {
e.printStackTrace();
}
}
private static class Client extends Thread {
private int num;
Client(int num) {
this.num = num;
}
@Override
public void run() {
String serverName = LOCAL_HOST;
int port = PORT_SERVER;
try {
// 连接
DatagramSocket client = new DatagramSocket();
client.connect(InetAddress.getByName(serverName), port);
System.out.println("客户端-" + num + "-log--连接成功,"
+ "LocalSocketAddress:" + client.getLocalSocketAddress()
+ ",RemoteSocketAddress:" + client.getRemoteSocketAddress()
+ ",LocalAddress:" + client.getLocalAddress()
+ ",InetAddress:" + client.getInetAddress());
// 发送
byte[] data = ("=====Hello from " + client.getLocalSocketAddress() + "====").getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length);
client.send(packet);
// 接收
byte[] buffer = new byte[1024];
packet = new DatagramPacket(buffer, buffer.length);
client.receive(packet);
String resp = new String(packet.getData(), packet.getOffset(), packet.getLength());
System.out.println(resp);
// 关闭
client.disconnect();
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static class Server extends Thread {
private DatagramSocket datagramSocket;
Server() throws SocketException {
datagramSocket = new DatagramSocket(PORT_SERVER);
datagramSocket.setSoTimeout(10000);
System.out.println("服务端-0-log--创建socket :" + datagramSocket.getLocalSocketAddress());
}
@Override
public void run() {
while (!interrupted()) {
try {
// 连接
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
datagramSocket.receive(packet);
System.out.println("服务端-0-log--连接成功,"
+ "LocalSocketAddress:" + datagramSocket.getLocalSocketAddress()
+ ",RemoteSocketAddress:" + datagramSocket.getRemoteSocketAddress()
+ ",LocalAddress:" + datagramSocket.getLocalAddress()
+ ",InetAddress:" + datagramSocket.getInetAddress());
// 接收
String data = new String(packet.getData(), packet.getOffset(), packet.getLength());
System.out.println("服务端-0-log--读取到数据:" + data);
// 回复
String str_send = "=====Hello too from " + datagramSocket.getLocalSocketAddress() + "====";
packet = new DatagramPacket(str_send.getBytes(), str_send.length(), packet.getAddress(), packet.getPort());
datagramSocket.send(packet);
} catch (SocketTimeoutException s) {
System.out.println("服务端-0-log--DatagramSocket timed out!");
break;
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
}
}
运行结果:
服务端-0-log--创建socket :0.0.0.0/0.0.0.0:2049
客户端-2-log--连接成功,LocalSocketAddress:0.0.0.0/0.0.0.0:52745,RemoteSocketAddress:localhost/127.0.0.1:2049,LocalAddress:0.0.0.0/0.0.0.0,InetAddress:localhost/127.0.0.1
客户端-1-log--连接成功,LocalSocketAddress:0.0.0.0/0.0.0.0:52744,RemoteSocketAddress:localhost/127.0.0.1:2049,LocalAddress:0.0.0.0/0.0.0.0,InetAddress:localhost/127.0.0.1
服务端-0-log--连接成功,LocalSocketAddress:0.0.0.0/0.0.0.0:2049,RemoteSocketAddress:null,LocalAddress:0.0.0.0/0.0.0.0,InetAddress:null
服务端-0-log--读取到数据:=====Hello from 0.0.0.0/0.0.0.0:52745====
服务端-0-log--连接成功,LocalSocketAddress:0.0.0.0/0.0.0.0:2049,RemoteSocketAddress:null,LocalAddress:0.0.0.0/0.0.0.0,InetAddress:null
服务端-0-log--读取到数据:=====Hello from 0.0.0.0/0.0.0.0:52744====
=====Hello too from 0.0.0.0/0.0.0.0:2049====
=====Hello too from 0.0.0.0/0.0.0.0:2049====
参考资料
http://tutorials.jenkov.com/java-networking/index.html
https://blog.csdn.net/freekiteyu/article/details/72236734
https://blog.fundebug.com/2019/03/22/differences-of-tcp-and-udp/
https://zhuanlan.zhihu.com/p/33797520