socket 基于 TCP/IP协议实现,在网络模型中属于传输层
Java 网络编程中的核心概念
- IP 地址:用于标识网络中的计算机
- 端口号:用于标识计算机上的应用程序或进程
- Socket(套接字):网络通信的基本单位,通过 IP 地址和端口号标识
- 协议:网络通信的规则,如 TCP(传输控制协议)和 UDP(用户数据报协议)
Java 网络编程的核心类
- Socket:用于创建客户端套接字
- ServerSocket:用于创建服务器套接字
- DatagramSocket:用于创建支持 UDP 协议的套接字
- URL:用于处理统一资源定位符
- URLConnection:用于读取和写入 URL 引用的资源
原理:Socket 实现线程通信
Socket 常用 API
构造方法
(1) public Socket()
(2) public Socket(String host, int port) throws UnknownHostException, IOException
(3) public Socket(InetAddress address,int port) throws IOException
(4) public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException
(5) public Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException
(6) public Socket(Proxy proxy)
需要注意的是:其中第(4)种和第(5)种会指定本地客户端的IP地址和端口信息
获取 socket 信息
获得远程服务器的IP地址。
getInetAddress()
获得远程服务器的端口。
getPort()
获得客户本地的IP地址。
getLocalAddress()
获得客户本地的端口。
getLocalPort()
获得输入流。如果 Socket还没有连接,或者已经关闭,或者已经通过shutdownInput()方法关闭输入流,那么此方法会抛出IOException。
getInputStream()
获得输出流。如果Socket还没有连接,或者已经关闭,或者已经通过shutdownOutput()方法关闭输出流,那么此方法会抛出IOException。
getOutputStream()
socket状态和关闭
isClosed() :如果Socket已经连接到远程主机,并且还没有关闭,则返回true,否则返回false。
isConnected() :如果Socket曾经连接到远程主机,则返回true,否则返回false。
isBound() :如果 Socket已经与一个本地端口绑定,则返回true,否则返回false。
close():关闭 socket
shutdownInput():关闭输入流。
shutdownOutput():关闭输出流。
值得注意的是,先后调用Socket的 shutdownInput()和 shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等价于调用Socket 的 close()方法。在通信结束后,仍然要调用Socket的close()方法,因为只有该方法才会释放Socket占用的资源,如占用的本地端口等。
实践:Socket 实现线程通信
server
- socket():创建 serverSocket 连接
- bind():绑定 6301 端口
- listen():serverSocket 底层实现
- accept():serverSocket.accept()
- read():创建 read 线程
- write():创建 write 线程
- close():结束 server 线程
server 代码:
Server
public class Server {
public static void main(String[] args) {
System.out.println("服务端启动");
try {
ServerSocket serverSocket = new ServerSocket(6301);
while (true) {
// 接受 socket 请求
Socket socket = serverSocket.accept();
new Thread(new ServerReader(socket)).start();
new Thread(new ServerWriter(socket)).start();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
ServerReader
class ServerReader implements Runnable {
private Socket socket;
public ServerReader(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
while (true) {
System.out.println(objectInputStream.readObject());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
ServerWriter
class ServerWriter implements Runnable {
private Socket socket;
public ServerWriter(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("服务端 请输入:");
String str = scanner.nextLine();
JSONObject jsonObject = new JSONObject();
jsonObject.put("type", "msg");
jsonObject.put("content", str);
objectOutputStream.writeObject(jsonObject);
objectOutputStream.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
client
- socket():
new Socket("localhost", 6301);
- bind():
new Socket("localhost", 6301);
- connect():
new Socket("localhost", 6301);
- write():创建 write 线程
- read():创建 read 线程
- close():socket.close()
client 代码:
Client
public class Client {
private static Socket socket;
public static boolean socketStatus = false; // 是否连接成功
public static void main(String[] args) {
while (!socketStatus) {
try {
socket = new Socket("localhost", 6301);
connect();
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void connect() {
try {
socketStatus = true;
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
new Thread(new ClientReader(socket, inputStream)).start();
new Thread(new ClientWriter(socket, outputStream)).start();
new Thread(new ClientHeart(socket, outputStream)).start();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void reconnect() {
while (true) {
System.out.println("正在尝试重新连接...");
connect();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
ClientReader
class ClientReader implements Runnable {
private Socket socket;
private ObjectInputStream objectInputStream;
public ClientReader(Socket socket, ObjectInputStream objectInputStream) {
this.socket = socket;
this.objectInputStream = objectInputStream;
}
@Override
public void run() {
while (true) {
try {
System.out.println(objectInputStream.readObject());
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
ClientWriter
class ClientWriter implements Runnable {
private Socket socket;
private ObjectOutputStream objectOutputStream;
public ClientWriter(Socket socket, ObjectOutputStream objectOutputStream) {
this.socket = socket;
this.objectOutputStream = objectOutputStream;
}
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
while (true) {
try {
System.out.println(socket.getLocalPort() + "客户端 请输入:");
String str = scanner.nextLine();
JSONObject jsonObject = new JSONObject();
jsonObject.put("type", "msg");
jsonObject.put("content", str);
objectOutputStream.writeObject(jsonObject);
objectOutputStream.flush(); // flush 把数据从缓存 buffer刷到流中
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
值得注意的是:ClinetWriter 和 ClientHeart 的区别在于发送消息的不同,包括
type
和content
class ClientHeart implements Runnable {
Socket socket;
ObjectOutputStream objectOutputStream;
public ClientHeart(Socket socket, ObjectOutputStream objectOutputStream) {
this.socket = socket;
this.objectOutputStream = objectOutputStream;
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(5000);
JSONObject jsonObject = new JSONObject();
jsonObject.put("type", "heart");
jsonObject.put("content", "===心跳💓===");
objectOutputStream.writeObject(jsonObject);
objectOutputStream.flush();
}
} catch (Exception e) {
// throw new RuntimeException(e);
e.printStackTrace();
try {
// 连接异常断开后的操作
socket.close();
Client.socketStatus = false;
Client.reconnect();
} catch (Exception ex) {
e.printStackTrace();
}
}
}
}
测试结果
实现了线程通信、server重启后 client重新连接
序列化
这里使用ObjectOutputStream实现序列化,以及 fastjson 的 JSONObject 实现对消息的封装
via
- https://www.bilibili.com/video/BV1vf4y1p7uN
- https://www.mianshiya.com/question/1800349212845875202
- https://blog.csdn.net/penny611476/article/details/119905062
- https://juejin.cn/post/7026156159874646024