Socket
基本概念
Socket 是一种用于网络通信的编程接口(API),它提供了一种机制,使不同主机之间可以通过网络进行数据传输和通信。
Socket(套接字)本身确实不是一个协议,而是一种编程接口(API),它提供了网络通信的能力,使得开发者可以在不同的进程之间,或者在不同的计算机之间进行数据传输。这些数据传输可以是基于 TCP(传输控制协议)的,也可以是基于 UDP(用户数据报协议)的,或者在某些情况下,还可能基于其他类型的协议(尽管 TCP 和 UDP 是最常见的)。
在 OSI 模型中,网络通信被划分为七层,其中应用层是最高层,直接面向用户的应用程序;传输层则负责端到端的数据传输,确保数据的完整性和顺序。Socket API 允许应用程序(位于应用层)通过标准的、统一的方式来访问传输层服务,而无需关心底层的实现细节。
具体来说,当开发者使用 Socket 进行编程时,他们通常会在应用层编写代码,通过 Socket API 来发送和接收数据。这些 API 调用会被操作系统转换成适当的 TCP 或 UDP 数据包,然后通过网络发送到目标地址,使得网络通信变得简单和可靠。
因此,Socket 是网络通信中非常关键的一个组成部分,它提供了一种高效、灵活的方式来在应用程序之间交换数据。通过 Socket API,开发者可以构建出各种复杂的网络应用程序,如聊天应用、在线游戏等。
通信流程
Socket 服务端
-
创建 ServerSocket 实例:通过调用 ServerSocket 的构造器,并指定一个端口号(可选的,也可以指定一个 IP 地址,但通常指定为 null,表示监听所有可用的网络接口)。
-
等待连接:通过调用 ServerSocket 的 accept() 方法等待客户端的连接。这个方法会阻塞,直到一个连接被建立。
-
通信:一旦 accept() 方法返回了一个 Socket 实例,就可以通过这个实例的输入流和输出流与客户端进行通信了。
-
关闭连接:通信结束后,需要关闭 Socket 和 ServerSocket 实例以释放资源。
import java.io.*;
import java.net.*;
public class Server {
public static void main(String[] args) {
int port = 12345; // 设定端口号
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("Server is running on port " + port);
while (true) {
// 等待客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("New client connected: " + clientSocket.getInetAddress().getHostAddress());
// 处理客户端请求
handleClient(clientSocket);
// 关闭客户端连接
clientSocket.close();
}
} catch (IOException e) {
System.err.println("Server exception: " + e.getMessage());
e.printStackTrace();
}
}
private static void handleClient(Socket clientSocket) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("Received from client: " + inputLine);
// 回显消息给客户端
out.println(inputLine);
}
} catch (IOException e) {
System.err.println("Error handling client: " + e.getMessage());
e.printStackTrace();
}
}
}
Socket 客户端
-
创建 Socket 实例:通过调用 Socket 的构造器,并指定服务器的 IP 地址和端口号。
-
通信:一旦 Socket 实例被创建,就可以通过它的输入流和输出流与服务器进行通信了。
-
关闭连接:通信结束后,需要关闭 Socket 实例以释放资源。
import java.io.*;
import java.net.*;
public class Client {
public static void main(String[] args) {
String hostname = "localhost"; // 服务端地址
int port = 12345; // 服务端端口
try (Socket socket = new Socket(hostname, port);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
String userInput;
System.out.println("Connected to server. Enter messages (type 'exit' to quit):");
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
// 输入exit并回车以退出客户端
if ("exit".equalsIgnoreCase(userInput)) {
break;
}
// 读取并显示服务端响应
System.out.println("Server response: " + in.readLine());
}
} catch (UnknownHostException e) {
System.err.println("Don't know about host " + hostname);
e.printStackTrace();
} catch (IOException e) {
System.err.println("Couldn't get I/O for the connection to " + hostname);
e.printStackTrace();
}
}
}
长连接和短连接
长连接:
-
客户端和服务器之间只建立一次连接,该连接在数据传输期间保持开启状态
-
连接建立后,双方可以在该连接上多次发送和接收数据
-
为了保持连接活跃,防止因网络问题或超时等导致的连接中断,长连接通常会实现心跳机制来检测对方是否仍然在线,从而保持连接的活性,心跳包的发送和接收通常由专门的线程或定时器来处理。
-
长连接的关闭通常在会话结束时或发生错误时进行。
短连接:
-
在每次需要进行数据传输时,客户端都会建立一个新的连接
-
连接建立后,立即进行数据传输
-
数据传输完成后,客户端会主动关闭连接,不再保持。
Websocket
基本概念
WebSocket 和 HTTP 相同,都是应用层协议,它俩的传输层都是基于 TCP 的。那么就有一个问题了:已经有了 HTTP 协议,为什么还需要 WebSocket ?
因为 HTTP 协议有一个缺陷:通信只能由客户端发起,做不到服务器主动向客户端推送信息。HTTP 协议的这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。只能使用定时任务来不断地向服务器发起请求,非常浪费资源。
所以 WebSocket 的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,即 WebSocket 允许服务器端与客户端进行全双工通信。
WebSocket 没有同源限制,客户端可以与任意服务器通信。
WebSocket 协议标识符是ws(如果加密,则为wss)。
WebSocket 和 HTTP
区别:
-
通信模式:
-
HTTP:HTTP 是一种单向的通信协议,即客户端主动发起请求(request),服务器被动响应(response)。服务器不能主动向客户端推送数据,除非客户端发起新的请求。
-
WebSocket:WebSocket 是一种双向通信协议,它建立了一个持久化的连接,允许服务器和客户端之间进行全双工通信。即服务器和客户端都可以随时主动发送数据给对方。
-
-
连接类型:
-
HTTP:HTTP 连接通常是短连接(非持久化),即每次请求都会建立一个新的连接,并在响应后关闭连接。虽然 HTTP/1.1 引入了 keep-alive 机制来保持连接在一定时间内不关闭,但仍然需要客户端主动发起请求来维持连接。
-
WebSocket:WebSocket 建立的是长连接,一旦连接建立,就可以保持这个连接状态直到明确关闭为止。这使得 WebSocket 能够更高效地处理实时通信需求。
-
-
数据格式:
-
HTTP:HTTP 通信的数据主要是基于文本格式的,二进制数据(如图片等)需要转化为 base64 编码文本后才能传输。
-
WebSocket:WebSocket 支持发送文本和二进制数据,不需要进行额外的编码转换。
-
联系:
WebSocket 协议的建立过程中,客户端会向服务器发送一个标准的 HTTP 请求来发起连接。这个请求中包含了特定的头部字段(如Upgrade: websocket和Connection: Upgrade),用于告诉服务器客户端希望将连接升级为 WebSocket 协议。服务器在接收到这个请求后,如果同意升级,就会返回一个状态码为 101 Switching Protocols [ˈproʊtəˌkɔlz] 的响应,并包含相应的 WebSocket 协议头部字段。这个过程实际上是在利用 HTTP 协议来完成 WebSocket 连接的握手阶段。
客户端示例代码
<template>
<div>
<button @click="send('你要发送的数据')">发消息</button>
</div>
</template>
<script>
export default {
data () {
return {
path:"ws://192.168.0.200:8005/qrCodePage/ID=1/refreshTime=5",
socket:""
}
},
mounted () {
// 初始化
this.init()
},
methods: {
init: function () {
if(typeof(WebSocket) === "undefined"){
alert("您的浏览器不支持socket")
}else{
// 实例化socket
this.socket = new WebSocket(this.path)
// 监听socket连接
this.socket.onopen = this.open
// 监听socket错误信息
this.socket.onerror = this.error
// 监听socket消息
this.socket.onmessage = this.getMessage
}
},
// 发送消息给被连接的服务端
send: function (params) {
this.socket.send(params)
},
open: function () {
console.log("socket连接成功")
},
error: function () {
console.log("连接错误")
},
getMessage: function (msg) {
console.log(msg.data)
},
close: function () {
console.log("socket已经关闭")
}
},
destroyed () {
// 销毁监听
this.socket.onclose = this.close
}
}
</script>