前端点滴(Node.js)(四)网络编程 ---- 侧重(上)

Node 网络编程

前言

利用Node可以十分方便地搭建网络服务器,在WEB领域,大多数编程语言需要专门的web服务器作为容器,比如ASP,ASP.NET需要IIS作为服务器,PHP需要搭载在Apache或者Nignx环境等,JSP需要Tomcat服务器等。当对于Node而言,只需要几行代码就可以构建一个服务器,无需额外的容器。
Node提供了net、http、https、dgram这四个模块,分别用于处理TCP、HTTP、HTTPS、UDP,适用于服务器与客户端。

一、构建 TCP 服务器

1. 七层模型与TCP协议

TCP全名传输控制协议,在OSI模型中有以下七层,被称为七层网络协议。许多应用层议都是基于TCP构建,典型的有HTTP、SMTP、IMAP等协议。
在这里插入图片描述
TCP是面向连接的协议,其显著特征为3次握手后才形成会话。
在这里插入图片描述
注意:只有在会话形成之后,服务器端和客户端之间才能互相发送数据,在创建会话的过程中,服务器端和客户端分别提供一个套接字,这两个套接字共同形成了一个链接,服务器端与客户端则通过套接字实现两者之间连接的操作。
具体流程:请移步到
https://blog.csdn.net/Errrl/article/details/103662867

2. 创建TCP服务器

在基本了解TCP工作原理后,接下来就可以开始创建TCP服务器端来接受请求:

server.js

/* 引入net核心模块 */
var net = require('net');
/* 创建一个TCP服务器 */
var server = net.createServer(function(socket){
	socket.on('data',function(data){
		socket.write('hello');
	});
	socket.on('end',function(){
		socket.write('end');
	});
	socket.write('welcome to node tcp');
});
/* 监听端口号 */
server.listen(8000,function(){
	console.log('server is done');
})

利用win10自带的telnet客户端对上述的见到服务器进行会话
在这里插入图片描述
在这里插入图片描述
通过net模块构造客户端进行会话,测试上述构建的TCP服务器:

client.js

var net = require('net');

var client = net.connect({port:8000},function(){
    console.log('client is connect');
    client.write('world!\r\n');
});

client.on('data',function(data){
    console.log(data.toString());
    client.end();
})

client.on('end',function(){
    console.log('client is disconnect');
})

在这里插入图片描述

3. TCP服务器事件

(1)服务器事件

对于通过net.createServer()创建的服务器而言,他是一个EventEmitter实例,他自定义事件有以下几种:

  • listening:在调用server.listen()绑定端口,简介写法为server.listen(port,listeningListener),通过listen()方法的的第二个参数传入。
  • connection:每个客户端套接字连接到服务端时触发,简介写法为通过net.createServer(),最后一个参数传入。
  • close:当服务器关闭时触发,在调用server.close()后,服务器将停止接受新的套接字连接,但保持当前的连接,对待所有连接都断开后会触发该事件。
  • error:当服务器发生异常时,将会触发该事件。比如监听一个使用中的端口,将会触发一个异常,如果不侦听error事件,服务器将会抛出异常。
(2)连接事件

服务器可以同时与多个客户端保持连接,对于每个连接而言是典型的可写可读Stream对象。Stream对象可以用于服务器与客户端之间的通信,既可以通过data事件从一端读取另一端发来的数据,也可以通过write()方法从一端向另一端发送数据,它具有如下自定义事件:

  • data:当一端调用write()发送数据时,另一端触发data事件,事件传递的数据即是write()发送的数据。
  • end:当连接中的任意一端发送FIN数据时,将会触发该事件。
  • connect:改时间用于客户端,当套接字与服务器端连接成功时被触发。
  • drain:当任意一端调用write()发送数据时,当前这端会触发该事件。
  • error:当发生异常时触发该事件。
  • close:当套接字完全关闭时触发事件。
  • timout:当一定时间后连续不在活跃时,该事件会被触发,通知用户当前该连接已经被闲置了。

二、构建 UDP服务器

1. 与 TCP 协议的区别

请移步到:
https://blog.csdn.net/Errrl/article/details/103662867

2. 创建 UDP 套接字

创建UDP套接字十分简单,UDP套接字一旦创建,既可以作为客户端发送数据,也可以作为服务器端接收数据,创建一个UDP套接字:

var dgram = require('dgram');
var socket = dgram.createSocket ('udp4')

3. 创建 UDP 服务器端

如果要想UDP套接字接受网路消息,只要调用dgram.bind(port,[address])进行绑定即可。

server.js

var dgram = require('dgram');
var server = dgram.createSocket ('udp4');

server.on('message',function(msg,rinfo){
	console.log("server got:"+msg+"from"+rinfo.address+":"+rinfo.port);
})

server.on('listening',function(){
	var address = server.address(); 
	console.log("server listening"+address.address+":"+address.port);
})

server.bind(41234)

该套接字将接收所有网卡上41234端口信息上的消息。在绑定完成后,将会触发listening事件。

4. 创建 UDP 客户端

创建一个客户端与服务器端进行对话:

client.js

var dgram = require('dgram');

var message = new Buffer('hello node udp');
var client = dgram .createSocket('udp4');
client.send(message,0,message.length,41234,"localhost",function(err,bytes){
	client.close();
})

在这里插入图片描述
当套接字对象用在客户端时,可以调用send()方法发送消息到网络中。send()方法的参数如下:

socket.send(buf,offset,length,port,address,[callback])
  • buf:buffer
  • offset:buffer偏移量
  • length:buffer的长度
  • port:目标端口
  • address:目标地址
  • callback:发送完成后的回调

5. UDP 套接字事件

UDP套接字相对于TCP套接字使用起来更加简单,它只是一个EventEmitter的实例,它具有如下自定义事件:

  • message:当UDP套接字侦听网卡端口后,接收到消息触发该事件,出发携带的数据为消息buffer对象和一个远程地址信息。
  • listening:当UDP套接字开始侦听时触发该事件。
  • close:调用close()方法时触发该事件,并不再触发message事件。如需再次触发message事件,重新绑定即可。
  • error:当异常发生时出发该事件,如果不侦听,异常将直接抛出,使进程退出。

三、构建 HTTP 服务器端、客户端

在Node中构建HTTP服务极其容易,Node官网上的经典例子就展示了如何用几行代码实现一个HTTP服务器:

var http = require('http');
/* 创建服务器 */
http.createServer(function(req,res){
	res.writeHead(200,{'content-type':'text/plain'});
	res.end('hello world');
}).listen(8000,'127.0.0.1');
console.log('server run at http://127.0.0.1:8000/');

在这里插入图片描述

1. HTTP

(1)HTTP 报文

在启动上述代码后,我们对经典的示例代码进行了一次报文的获取,这里使用的工具是curl,通过 -v选项,可以显示这次网络通讯所有的报文信息。
在这里插入图片描述
请求报文的四部分:

  • TCP三次握手。
  • 请求报文。(请求头、请求体)
  • 响应报文(包括响应头、响应体)。
  • 会话结束信息。

在这里插入图片描述
从上述可以看出HTTP的特点,它是基于请求响应式的,基于TCP协议。

2. http 模块、HTTP服务器端

Node的http模块包含对HTTP处理的封装,在Node中,HTTP服务继承自TCP服务器(net模块),它能够与多个客户端保持连接,由于采用事件驱动的形式,并不为每一个连接创建额外的线程或进程,并保持很低的内存占有率,所以能实现高校并发。

(1)HTTP 请求

请求头

> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.55.1
> Accept: */*
>

请求头第一行GET / HTTP/1.1解析之后会分解成如下属性:

  • request.method:值是GET,一种请求方法,常用的请求方法还有:POST、DELETE、PUT、CONNECT等请求方法。
  • request.url:值为/,这就可以解释为什么在项目中查询request.url会返回/,原因就是取决于报文。
  • request.httpVersion:值为1.1,表示版本(规则)。

其余的包头就以简单、规律的key:value的格式,被解析后放置在request.headers属性上传递给业务逻辑以供调用。

(2)HTTP 响应

响应头

< HTTP/1.1 200 OK
< content-type: text/plain

在项目中经常要写入响应头,用于获取符合类型的数据。
除此之外,http模块会自动设置一些头信息:(用于处理缓存

< Date: Fri, 07 Feb 2020 14:30:32 GMT
< Connection: keep-alive             
< Transfer-Encoding: chunked         
<                                    

响应体
调用respone.write()或者调用respone.end()传入的内容称为响应体:

hello world

调用respone.write()或者调用respone.end()的区别在于:
前者只发送,不结束响应,会造成客户端处于等待状态。
而后者就会先调用write()发送完后调用end()结束响应。

(3)HTTP 服务的事件

同TCP服务一样,HTTP服务器也抽象了一些事件,以供应用层使用,同样典型的是,服务器也是一个EventEmitter实例:

  • connection 事件:在开始HTTP请求和响应之前,客户端与服务器需要建立底层的TCP连接,这个连接可能因为开启了keep-alive的原因,可以在多次请求与响应之间使用;当这个连接建立时,服务器会触发一次connection事件。
  • request 事件:建立TCP连接后,HTTP模块底层将在数据流中抽出HTTP请求和HTTP响应,当请求数据发送到服务器端,在解析出HTTP请求头后,将会触发该事件;在res.end()后,TCP连接可能将用于下一次请求响应。
  • close 事件:与TCP服务器行为一致,调用server.close()方法停止接受新的连接,当已有的连接都断开时,触发该事件;可以给server.close()传递一个回调函数来快速注册该事件。
  • checkContinue 事件:某些客户端在发送较大的数据时,并不会之间将数据发送,而是先发送一个头部带有Expect:100-continue的请求到服务器,服务器将会触发checkContinue事件;如果没有为服务器监听这个事件,服务器将会自动响应客户端100 Continue的状态码,表示可以接受数据上传;如果不接受或者数据确实超出承载时响应客户端400 Bad Request拒绝客户端继续发送数据即可。需要注意的是:当该事件发生时不会触发request事件,两个事件互斥。当客户端收到100 Continue后重新发送请求时才会触发request事件。与预检(Preflighted)的跨域请求类似
  • connect 事件:当客户端发起CONNECT请求时触发,二发起CONNECT请求通常在HTTP代理时出现;如果不监听该事件,发起该事件的连接就会中断。
  • upgrade 事件:当客户端要求升级连接的协议时,需要和服务器端协商,客户端会在请求头中带上Upgrade字段,服务器端会在接收到这样的请求时触发该事件。者在后面的WebSoket中会有详细的流程介绍。如果不监听该事件,发起该请求的连接就会中断。
  • clientError 事件:连接的客户端触发error事件时,这个错误会传递到服务器端,此时触发该事件。
扩展

keep-alive
在http早期,每个http请求都要求打开一个tpc socket连接,并且使用一次之后就断开这个tcp连接。

使用keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT状态连接,以此提高性能和提高httpd服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()的调用)

3. HTTP 客户端

http模块提供了一个底层的API:http.request(options,connect),用于构建HTTP客户端。

var http = require('http');
/* 请求报文 */
var options = {
	hostname:'127.0.0.1',
	port:8000,
	path:'/',
	method:'GET'
}
/* 发送报文 */
var req = http.request(options,function(res){
	/* 获取状态码 */
	console.log('status:'+res.statusCode);
	/* 获取响应头 */
	console.log('headers:'+res.headers);
	res.setEncoding('utf8');
	/* 获取响应体 */
	res.on('data',function(datas){
		console.log(datas);
	})
})
/* 发送后断开连接,缓解服务器压力 */
req.end();

在这里插入图片描述
修改:

/* 获取响应头 */
	console.log('headers:'+JSON.stringify(res.headers));

在这里插入图片描述
其中options的参数配置:

  • host:服务器的域名或者ip地址,默认为localhost。
  • hostname:服务器名称。
  • port:端口号。默认80。
  • method:HTTP请求方法,默认GET。
  • path:请求路径,默认/
  • headers:请求头对象。
  • auth:Basic认证,这个值将会被计算成请求头中的Authorization部分。

报文体的内容由请求对象的write()和end()方式实现:通过write()方法向连接中写入数据,通过end()方法告知报文结束。他与前端中的Ajax调用非常相似,Ajax的实质就是一个异步的网络HTTP请求。

(1)HTTP 响应

HTTP客户端的响应对象与服务器端类似,在客户端请求对象中,它的事件名叫做response。客户端请求后(也就是解析报文完成后)响应头就会触发response事件,同时传递一个响应对象以供客户端进行响应操作。对于上述代码而言,res就是response,datas就是响应对象。

(2)HTTP 代理

如服务器端的实现一般http模块提供的客户端请求对象也是基于TCP层实现的,在keep-alive机制下,一个底层会话连接可以多次用于请求。为了重复使用TCP连接,http模块包含一个默认的客户端代理对象http.globalAgent。它对每一个服务端(host+port)创建连接进行了管理,默认情况下,通过客户端请求对象对同一服务器端发起的HTTP请求最多可以创建5个连接。实际上它就是一个连接池(循环代理)
在这里插入图片描述
那么如何进行代理,很简单:
重构options

/* 设置代理 */
var agent = new http.Agent({
    maxSochets: 10,
    keepAlive: true,
})
var options = {
    hostname: '127.0.0.1',
    port: 8000,
    path: '/',
    method: 'GET',
    agent: agent
}
/* 发送报文 */
var req = http.request(options, function (res) {
    /* 获取状态码 */
    console.log('status:' + res.statusCode);
    /* 获取响应头 */
    console.log('headers:' + JSON.stringify(res.headers));
    res.setEncoding('utf8');
    /* 获取响应体 */
    res.on('data', function (datas) {
        console.log(datas);
    })
})

/* 发送后断开连接,缓解服务器压力 */
req.end();

相关链接:
http://nodejs.cn/api/http.html#http_class_http_agent

(3)HTTP 客户端事件
  • response 事件:与服务器端的request事件对应的客户端在请求发送后得到服务器的响应时会触发该事件。
  • socket 事件:在底层连接池中建立的连接分配给当前请求对象时,触发该事件。
  • connect 事件:当客户端向服务器端发送CONNECT请求时,如果服务器响应了200状态码,客户端将会触发该事件。
  • upgrade 事件:客户端向服务器端发起Upgrade请求时,如果服务器端响应了101 Switching Protocols状态,客户端将会触发该事件。
  • continue 事件:客户端向服务器端发起Expect:100-continue头信息,以试图发送叫大数据量,如果服务器响应了100 Continue状态,客户端将触发该事件。

四、构建 webSocket 服务端

1. 客户端下的 webSocket

HTML:(client.html)

<input id="content" type="text">
<button id="send">send</button>

以一个webSocket聊天室进行实例操作:

/* client.html */
var websocket = new WebSocket("ws://localhost:8000/");
        websocket.onopen = function() {
            console.log("webSocket open");
            // 发送消息放在这里
            document.getElementById("send").onclick = function() {
                var txt = document.getElementById("content").value;
                if (txt) {
                	/* 发送数据 */
                    websocket.send(txt);
                }
            }
        }
        websocket.onclose = function() {
            console.log("websocket close");
        }
        /* 接收响应数据 */
        websocket.onmessage = function(e) {
            console.log(e.data);
            var mes = JSON.parse(e.data);
            showMessage(mes.data, mes.type);
        }
/* server.js */
var ws = require("nodejs-websocket");
/* 端口号 */
const PORT = 8000;
// 每进来一个客户端就记录一下
var clientCount = 0;

var server = ws.createServer(function (conn) {
    console.log("New connection")
    clientCount++;
    conn.nickname = 'user' + clientCount;
    let mes = {};
    mes.type = "enter";
    mes.data = conn.nickname + ' comes in'
    broadcast(JSON.stringify(mes));
    /* 收到 text 文本触发 */
    conn.on("text", function (str) {
        console.log("Received " + str);
        let mes = {};
        mes.type = "message";
        mes.data = conn.nickname + ' says: ' + str;
        broadcast(JSON.stringify(mes));
    })
    /* 当任一侧关闭连接时发出 */
    conn.on("close", function (code, reason) {
        console.log("Connection closed");
        let mes = {};
        mes.type = "leave";
        mes.data = conn.nickname + ' left'
        broadcast(JSON.stringify(mes));
    })
    /* 发生错误时发出(例如尝试在仍然发送二进制数据的同时发送文本数据)。如果握手无效,也会发出响应。 */
    conn.on("error", function (err) {
        console.log("handle err");
        console.log(err);
    })
}).listen(PORT);//监听端口号

console.log("websocket server running on port: " + PORT);
/* 响应数据 */
function broadcast(str) {
    server.connections.forEach(function (connection) {
        connection.sendText(str);
    })
}

上述代码中,浏览器与服务器端创建webSocket协议请求,onopen在请求完成后持续执行,通过事件绑定的方法绑定一个发送按钮发送数据,同时还可以通过onmessage()方法接收服务器端相应的数据的数据。这种行为与TCP客户端很相似,相较于HTTP,它能够双向通信。
并且相比于HTTP,webSocket更接近于传输层协议,它并没有在HTTP的基础上模拟服务器端的推送,而是在TCP上定义独立的协议,但是疑惑的是webSocket的握手部分由HTTP完成,这就是人们感觉webSocket是基于HTTP实现的原因。
webSocket协议主要分为两个部分:握手和数据传输。

2. webSocket 握手

客户端建立连接时,通过HTTP发起的请求报文:
在这里插入图片描述
上面的报文告知客户端正在更换协议(协议升级),更新应用层协议为webSocket协议,并在当前的套接字连接上应用新的协议。剩余的字段分别表示服务器端基于Sec-WebSocket-Key生成的字符串和选中的子协议。客户端将会校验Sec-WebSocket-Key的值,如果成功,将开始接下来的数据传输。
简而言之就是websocket复用了http的握手通道,客户端通过http请求与服务端进行协商,升级协议。协议升级完后校验Sec-WebSocket-Key的值,若成功后面的数据交换则遵照websocket协议,若否反之。

流程:
1、客户端申请协议升级

Request URL: ws://localhost:8888/
Request Method: GET
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: uR5YP/BMO6M24tAFcmHeXw==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
  • Connection: Upgrade 表示要升级协议

  • Upgrade: websocket 表示升级到websocket协议

  • Sec-WebSocket-Version: 13 表示websocket的版本

  • Sec-WebSocket-Key 表示websocket的验证,防止恶意的连接,与服务端响应的Sec-WebSocket-Accept是配套。

2、服务端响应协议升级

Status Code: 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: eS92kXpBNI6fWsCkj6WxH6QeoHs=
Upgrade: websocket
  • Status Code:101 表示状态码,协议切换。
  • Sec-WebSocket-Accept 表示服务端响应的校验,与客户端的Sec-WebSocket-Key是配套的。

3、Sec-WebSocket-Accept是如何计算的

将 Sec-WebSocket-Key 的值与 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接。

然后通过sha1计算,再转成base64。

const crypto = require('crypto');
 
function getSecWebSocketAccept(key) {
    return crypto.createHash('sha1')
        .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
        .digest('base64');
}
 
console.log(getSecWebSocketAccept('uR5YP/BMO6M24tAFcmHeXw=='));

4、协议升级完后,后续的数据传输就需要按websocket协议来走。(了解即可)

websocket客户端与服务端通信的最小单位是 帧,由1个或多个帧组成完整的消息。

客户端:将消息切割成多个帧,发送给服务端。

服务端:接收到消息帧,将帧重新组装成完整的消息。

数据帧的格式

单位是1个比特位,FIN,PSV1,PSV2,PSV3 占1个比特位,opcode占4个比特位。

0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+-------------------------------+
|     Extended payload length continued, if payload len == 127  |
+-------------------------------+-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------+-------------------------------+
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

6、掩码的算法

Masking-key掩码键是由客户端生成的32位随机数,掩码操作不会影响数据载荷的长度。

function unmask(buffer, mask) {
    const length = buffer.length;
    for (var i = 0; i < length; i++) {
        buffer[i] ^= mask[i & 3];
    }
}

7、实现websocket的握手,数据传输
JavaScript:(up.js)

const crypto = require('crypto');
const net = require('net');
 
//计算websocket校验
function getSecWebSocketAccept(key) {
    return crypto.createHash('sha1')
        .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
        .digest('base64');
}
 
//掩码操作
function unmask(buffer, mask) {
    const length = buffer.length;
    for (var i = 0; i < length; i++) {
        buffer[i] ^= mask[i & 3];
    }
}
 
//创建一个tcp服务器
let server = net.createServer(function (socket) {
 
    socket.once('data', function (data) {
        data = data.toString();
 
        //查看请求头中是否有升级websocket协议的头信息
        if (data.match(/Upgrade: websocket/)) {
            let rows = data.split('\r\n');
            //去掉第一行的请求行
            //去掉请求头的尾部两个空行
            rows = rows.slice(1, -2);
            let headers = {};
            rows.forEach(function (value) {
                let [k, v] = value.split(': ');
                headers[k] = v;
            });
            //判断websocket的版本
            if (headers['Sec-WebSocket-Version'] == 13) {
                let secWebSocketKey = headers['Sec-WebSocket-Key'];
                //计算websocket校验
                let secWebSocketAccept = getSecWebSocketAccept(secWebSocketKey);
                //服务端响应的内容
                let res = [
                    'HTTP/1.1 101 Switching Protocols',
                    'Upgrade: websocket',
                    `Sec-WebSocket-Accept: ${secWebSocketAccept}`,
                    'Connection: Upgrade',
                    '\r\n'
                ].join('\r\n');
                //给客户端发送响应内容
                socket.write(res);
 
                //注意这里不要断开连接,继续监听'data'事件
                socket.on('data', function (buffer) {
                    //注意buffer的最小单位是一个字节
                    //取第一个字节的第一位,判断是否是结束位
                    let fin = (buffer[0] & 0b10000000) === 0b10000000;
                    //取第一个字节的后四位,得到的一个是十进制数
                    let opcode = buffer[0] & 0b00001111;
                    //取第二个字节的第一位是否是1,判断是否掩码操作
                    let mask = buffer[1] & 0b100000000 === 0b100000000;
                    //载荷数据的长度
                    let payloadLength = buffer[1] & 0b01111111;
                    //掩码键,占4个字节
                    let maskingKey = buffer.slice(2, 6);
                    //载荷数据,就是客户端发送的实际数据
                    let payloadData = buffer.slice(6);
 
                    //对数据进行解码处理
                    unmask(payloadData, maskingKey);
 
                    //向客户端响应数据
                    let send = Buffer.alloc(2 + payloadData.length);
                    //0b10000000表示发送结束
                    send[0] = opcode | 0b10000000;
                    //载荷数据的长度
                    send[1] = payloadData.length;
                    payloadData.copy(send, 2);
                    socket.write(send);
                });
            }
        }
    });
 
    socket.on('error', function (err) {
        console.log(err);
    });
 
    socket.on('end', function () {
        console.log('连接结束');
    });
 
    socket.on('close', function () {
        console.log('连接关闭');
    });
});
 
//监听8000端口
server.listen(8000);

html:(up.html)

<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<script>
    var ws = new WebSocket('ws://localhost:8888');
    ws.onopen = function () {
        console.log('连接成功');
        ws.send('你好服务端');
    };
    ws.onmessage = function (ev) {
        console.log('接收数据', ev.data);
    };
    ws.onclose = function () {
        console.log('连接断开');
    };
</script>
</body>
</html>

8、结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值