首先必须理解TCP的的11种状态,三次握手的作用,四次挥手的作用,为什么要有TCP,那就是HTTP的弊端,HTTP是一个释放连接的无状态协议,是在应用层,其实它底层也是新建了TCP协议的来进行传输,只是短暂的任务完成即刻断开了,那么它底层的TCP也是断开的,当请求次数特别频繁时就有弊端,比如说聊天,实时信息。。。
其次就是要了解端口和端点的区别,端点也可以理解为一个插口,是用TCP协议连接起来的两个连接点。
还要注意区别socket的数据接口(webSocket API)和http请求的url
插口包含了端口,因为插口 = (IP地址,端口号)。插口是TCP连接的端点。
插口(socket)有多种意思。当使用API时,插口往往被看成是操作系统的一种抽象,这时,插口和一个文件描述符是很相似的,并且是应用编程接口API的一部分。插口由应用程序产生,并指明它将由客户还是服务器来使用。当应用进程创建一个插口时,要指明该插口使用的端口号。
端口则是应用层服务的的一种代号,它用来标志应用层的进程。端口是一个16 bit的整数。各种服务器使用的端口号都是保留端口号【什么是保留端口号?】,以便使客户能够找到服务器。例如万维网服务器使用的端口号是80。
在发送数据时,应用层的数据通过端口向下交付到运输层。在接收数据时,运输层的数据通过适当的端口向上交付到应用层的某个应用程序。
解读前后端代码
首先,前端每执行一句var ws =new WebSocket(“ws://localhost:8080/websocket/1”)就相当于新建了一个和后端相连接的所谓端点(插口),同时会自动生成一个session (这里的session和之前说的session有点不一样,这里是WsSession,每打开一个网页,或者刷新网页,前端js都会执行new 语句,都是不一样的WsSession,(见下图),后端都会自动执行一个 Onopen方法,这里理解为:每执行一句 new 相当于建了一个端点,那就自动生成一个session 后端也是主要通过这个session和后端进行传输的)。。
奇怪的现象:每刷新一个页面或者打开新的窗口都是不一样的WsSession(由打印可以知道),但是每一次都加到CopyOnWriteArraySet这个集合里面,但是当打印这个集合的数量的时候是和实际不符的,
后端这个被@ServerEndpoint("/websocket/{sid}")注解过的类表明用这个类来在服务端建一个端点,等待连接,每一连接上了就会生成一个这个类的实例对象,(注意:在使用了@ServerEndpoint注解的类是无法直接使用@Autowired的,因为@ServerEndpoint表明当前类是websocket的服务端点,在spring容器启动时会初始化一次该类,当有新的websocket连接的时候,也会进行该类实例的创建(每一次连接时都会创建一个实例));要保存全部连接也就是seesion,那么就要给这个类新建很多的静态成员,也就类变量;这个类里面基本上所有的方法都加了一个Session参数,在 onOpen()方法里面 this.session = session;就是把一开始的session赋值给新建的实例对象的session属性。。
为什么说session重要呢??因为这个端点连接要发送信息都是通过这个session来发送的,后端执行this.session.getBasicRemote().sendText(message);就可以发送,前端发送就是用js里面WebSocket类的方法;执行ws.send(“ws.send()发信息!!!”);语句,后端接受信息的方法就是在@onMessage注解的方法里用String message参数接受;这个message是一个参数名字可以变。。
后端的session把数据传过来了之后就是用js里面的 WebSocket类的onmessage()方法来接受,ev.data就是传来的数据(代码如下),向服务器传数据就是用WebSocket类的send方法来传过去的。。
ws.onmessage =function (ev) {
var receive_msg=ev.data;
alert(receive_msg)
console.log(receive_msg)
}
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
static Log log=LogFactory.getLog(WebSocketServer.class);
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
//接收sid
private String sid="";
/**
* 连接建立成功调用的方法*/
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;/*一个管道代表一个session*/
@OnOpen
public void onOpen(Session session,@PathParam("sid") String sid) {
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
log.info("有新窗口开始监听:"+sid+"sessionId:"+session.getId()+",当前在线人数为" + getOnlineCount());
this.sid=sid;
try {
sendMessage("连接成功");
} catch (IOException e) {
log.error("websocket IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口"+sid+"的信息:"+message);
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
log.info("向前端发信息");
message="后端也发给你:"+message;
this.session.getBasicRemote().sendText(message);
}
/**
* 群发自定义消息
* */
public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
log.info("推送消息到窗口"+sid+",推送内容:"+message);
for (WebSocketServer item : webSocketSet) {
try {
//这里可以设定只推送给这个sid的,为null则全部推送
if(sid==null) {
item.sendMessage(message);
}else if(item.sid.equals(sid)){
item.sendMessage(message);
}
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
<script type="application/javascript" >
var ws =new WebSocket("ws://localhost:8080/websocket/1")
ws.onopen =function (ev) {
console.log("开始连接");
}
ws.onmessage =function (ev) {
var receive_msg=ev.data;
alert(receive_msg)
console.log(receive_msg)
}
ws.onclose = function (ev) {
console.log("关闭连接");
}
ws.onerror =function (ev) {
alert("erro" +ev)
}
function sendMessage() {
ws.send("ws.send()发信息!!!");
}
</script>
</head>
<body>
<button type="button" id="send" class="btn btn-primary"
onclick="sendMessage();">
Send!
</button>