nodejs 网络通信

158 阅读25分钟

一、网络模型

七层模型 网络七层模型.png

五层模型

网络五层模型.png

实体层

就是把电脑连接起来的物理手段,作用是负责传送0和1的电信号

链接层

它在"实体层"的上方,确定了0和1的分组方式

以太网协议

  • 早期的时候,每家公司都有自己的电信号分组方式。逐渐地,一种叫做"以太网"(Ethernet)的协议,占据了主导地位。

  • 以太网规定,一组电信号构成一个数据包,叫做"帧"(Frame)。每一帧分成两个部分:标头(Head)和数据(Data)

  • "标头"包含数据包的一些说明项,比如发送者、接受者、数据类型等等;"数据"则是数据包的具体内容。

  • "标头"的长度,固定为18字节。"数据"的长度,最短为46字节,最长为1500字节。

  • 因此,整个"帧"最短为64字节,最长为1518字节。如果数据很长,就必须分割成多个帧进行发送。

MAC地址

  • 以太网规定,连入网络的所有设备,都必须具有"网卡"接口。数据包必须是从一块网卡,传送到另一块网卡。网卡的地址,就是数据包的发送地址和接收地址,这叫做MAC地址。

  • 每块网卡出厂的时候,都有一个全世界独一无二的MAC地址,长度是48个二进制位,通常用12个十六进制数表示。前6个十六进制数是厂商编号,后6个是该厂商的网卡流水号。有了MAC地址,就可以定位网卡和数据包的路径了。 如:00-B0-D0-86-BB-F7

广播

1号计算机向2号计算机发送一个数据包,同一个子网络的3号、4号、5号计算机都会收到这个包。它们读取这个包的"标头",找到接收方的MAC地址,然后与自身的MAC地址相比较,如果两者相同,就接受这个包,做进一步处理,否则就丢弃这个包。这种发送方式就叫做"广播"(broadcasting)

网络层

IP协议

  • 规定网络地址的协议,叫做IP协议。它所定义的地址,就被称为IP地址。目前,广泛采用的是IP协议第四版,简称IPv4。这个版本规定,网络地址由32个二进制位组成,我们用分成四段的十进制数表示IP地址,从0.0.0.0一直到255.255.255.255。
  • IP协议的作用主要有两个,一个是为每一台计算机分配IP地址,另一个是确定哪些地址在同一个子网络。

传输层

端口号

  • 端口是0到65535之间的一个整数,正好16个二进制位。0到1023的端口被系统占用,用户只能选用大于1023的端口。不管是浏览网页还是在线聊天,应用程序会随机选用一个端口,然后与服务器的相应端口联系。

UDP协议

我们必须在数据包中加入端口信息,这就需要新的协议。最简单的实现叫做UDP协议

UDP数据包非常简单,"标头"部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包

TCP协议

TCP:Transmission Control Protocol 传输控制协议

是一种面向连接的、可靠的、基于字节流的传输层通信协议

UDP 是不可靠的

TCP 保证了数据的可靠性

应用层

应用层请求.jpg

二、TCP(socket编程)

socket是TCP/IP协议的API

TCP是数据的介质,Socket是TCP的介质.

三次握手.png

简易群聊

服务端(server.js)

const net = require('net')

const server = net.createServer()

const clients = []

server.on('connection', clientSocket => {
  clients.push(clientSocket)
  //监听clientSocke的data事件
  clientSocket.on('data',data=>{
    console.log("客户端说:"+data.toString())

    //把数据发给所有客户端
    clients.forEach(socket=>{
      if (socket !== clientSocket) {
        socket.write(data)
      }
    })
  })
  //给客户端发送消息
  clientSocket.write('hello')
})

server.listen(3000,()=>{
  console.log("服务器启动成功")
})

客户端(client.js)

const net = require('net')

const client = net.createConnection({
  host:'127.0.0.1',
  port:3000
})

client.on('connect',()=>{
  console.log("成功连接到服务器~!!!")
  //给服务端发送消息

  process.stdin.on('data',data=>{
    client.write(data.toString().trim())
  })
})

//接收数据的
client.on('data', data=>{
  console.log(data.toString())
})

三、UDP

User Datagram Protocol,简称 UDP ,又称用户数据报协议

概述

  • 和 TCP 一样,位于网络传输层用于处理数据包
  • UDP 最大的特点是无连接
  • UDP 传输速度快
  • UDP 数据传输不可靠
    • 不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的
    • 可靠性由应用层负责
  • 支持一对一通信,也支持一对多通信
  • 许多关键的互联网应用程序使用 UDP
    • 如 DNS 域名系统服务、TFTP 简单文件传输协议、DHCP 动态主机设置协议 等
  • UDP 适用于对速度要求比较高,对数据质量要求不严谨的应用
    • 例如流媒体、实时多人游戏、实时音视频
UDPTCP
连接无连接面向连接
速度无需建立连接,速度较快需要建立连接,速度较慢
目的主机一对一,一对多仅能一对一
带宽UDP 报头较短,消耗带宽更少消耗更多的带宽
消息边界
可靠性
顺序无序有序

注:事实上,UDP协议的这种乱序性基本上很少出现,通常只会在网络非常拥挤的情况下才有可能发生。

什么时候用 TCP,什么时候用 UDP?

  • 对速度要求比较高的时候使用UDP,例如视频聊天, QQ聊天
  • 对数据安全要求比较高的时候使用TCP,例如数据传输,文件下载
  • 假如对于视频聊天来说,如果画质优先那就选用TCP, 如果流畅度优先那就选用UDP

dgram模块

Node 为我们提供了 dgram 模块用于构建 UDP 服务。

dgram 模块提供了 UDP 数据包 socket 的实现。

Socket 方法
API说明
bind()绑定端口和主机
address()返回 Socket 地址对象
close()关闭 Socket 并停止监听
send()发送消息
addMembership()添加组播成员
dropMembership()删除组播成员
setBroadcast()设置是否启动广播
setTTL()设置数据报生存时间
setMulticastTTL()设置组播数据报生存时间
Socket 事件
API说明
listening监听成功时触发,仅触发一次
message收到消息时触发
error发生错误时触发
close关闭 Socket 时触发

UDP单播

  • 单播是目的地址为单一目标的一种传播方式
  • 地址范围:0.0.0.0 ~ 223.255.255.255
#server.js

const dgram = require('dgram')
const server = dgram.createSocket('udp4')

server.on('listening', () => {
  const address = server.address();
  console.log(`服务器监听 ${address.address}:${address.port}`);
});

server.on('message', (msg, rinfo) => {
  console.log(`服务器接收到来自 ${rinfo.address}:${rinfo.port}${msg}`);
  server.send('你好客户端',rinfo.port)
});

server.on('error', (err) => {
  console.log(`服务器异常:\n${err.stack}`);
  server.close();
});

server.bind(3000);




#client.js

const dgram = require('dgram')
const client = dgram.createSocket('udp4')


client.on('listening', () => {
  const address = client.address();
  console.log(`服务器监听 ${address.address}:${address.port}`);
  client.send('你好主机', 3000, 'localhost');
});

client.on('message', (msg, rinfo) => {
  console.log(`服务器接收到来自 ${rinfo.address}:${rinfo.port}${msg}`);
});

client.on('error', (err) => {
  console.log(`服务器异常:\n${err.stack}`);
  client.close();
});

//client.bind(3000);

UDP广播

目的地址为网络中的所有设备

地址范围分为两种

  • 受限广播:它不被路由转发,IP 地址的网络字段和主机字段全为1就是地址 255.255.255.255
  • 直接广播:会被路由转发,IP地址的网络字段定义这个网络,主机字段通常全为1,如 192.168.10.255
#server.js

const dgram = require('dgram')
const server = dgram.createSocket('udp4')

server.on('listening', () => {
  const address = server.address();
  console.log(`服务器监听 ${address.address}:${address.port}`);
  //开起广播模式
  server.setBroadcast(true)

  server.send('哈哈哈',8000,'255.255.255.255')

  //每隔3秒发送一条广播消息
  setInterval(() => {
    //受限地址
    //server.send('哈哈哈',8000,'255.255.255.255')
    //直接地址
    server.send('哈哈哈',8000,'192.168.0.255')
  }, 3000);
});

server.on('message', (msg, rinfo) => {
  console.log(`服务器接收到来自 ${rinfo.address}:${rinfo.port}${msg}`);
  server.send('你好客户端',rinfo.port)
});

server.on('error', (err) => {
  console.log(`服务器异常:\n${err.stack}`);
  server.close();
});

server.bind(3000);



#client.js

const dgram = require('dgram')
const client = dgram.createSocket('udp4')


client.on('message', (msg, rinfo) => {
  console.log(`服务器接收到来自 ${rinfo.address}:${rinfo.port}${msg}`);
});

client.on('error', (err) => {
  console.log(`服务器异常:\n${err.stack}`);
  client.close();
});

client.bind(8000);

UDP组播

  • 多播(Multicast)也叫组播,把信息传递给一组目的地地址
  • 地址范围:224.0.0.0 ~ 239.255.255.255
  • 224.0.0.0 ~ 224.0.0.255 为永久组地址,224.0.0.0.0 保留不分配,其它供路由协议使用
  • 224.0.1.0 ~ 224.0.1.255 为公用组播地址,可以用于 Internet
  • 224.0.2.0 ~ 238.255.255.255 为用户可用的组播地址(临时组),全网范围有效,使用需要申请
  • 239.0.0.0 ~ 239.255.255.255 为本地管理组播地址,仅在特定本地范围有效
#server.js


const dgram = require('dgram')
const server = dgram.createSocket('udp4')

server.on('listening', () => {
  const address = server.address();
  console.log(`服务器监听 ${address.address}:${address.port}`);

  setInterval(() => {
    server.send('哈哈哈',8000,'224.0.1.100')
  }, 3000);
});

server.on('message', (msg, rinfo) => {
  console.log(`服务器接收到来自 ${rinfo.address}:${rinfo.port}${msg}`);
  server.send('你好客户端',rinfo.port)
});

server.on('error', (err) => {
  console.log(`服务器异常:\n${err.stack}`);
  server.close();
});

server.bind(3000);




#client.js

const dgram = require('dgram')
const client = dgram.createSocket('udp4')

client.on('listening', () => {
  const address = client.address();
  console.log(`服务器监听 ${address.address}:${address.port}`);

  client.addMembership('224.0.1.100')
});

client.on('message', (msg, rinfo) => {
  console.log(`服务器接收到来自 ${rinfo.address}:${rinfo.port}${msg}`);
});

client.on('error', (err) => {
  console.log(`服务器异常:\n${err.stack}`);
  client.close();
});

client.bind(8000);

四、HTTP

概述

TCP 和 UDP 都属于网络传输层协议,如果要构建高效的网络应用,就应该从传输层进行着手。但是对于经典的浏览器网页和服务端通信场景,如果单纯的使用更底层的传输层协议则会变得麻烦。

基于传输层之上专门制定了更上一层的通信协议:HTTP,用于浏览器和服务端进行通信。由于 HTTP 协议本身并不考虑数据如何传输及其他细节问题,所以属于应用层协议。

const http = require('http')

const hostname = '127.0.0.1'
const port = 3000

const server = http.createServer((req,res)=>{
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain')
  res.end('hello word')
})

server.listen(port,hostname,()=>{
  console.log(`服务器运行在 http://${hostname}:${port}上`)
})

HTTP头域

HTTP的头域包括通用头、请求头、响应头和实体头四个部分。每个头域由一个域名,冒号(:)和域值三部分组成。

HTTP通用头
#1、Cache-Control
Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置 Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no-store、max-age、 max-stale、min-fresh、only-if-cached,响应消息中的指令包括public、private、no-cache、no- store、no-transform、must-revalidate、proxy-revalidate、max-age。各个消息中的指令含义如下:

no-cache: 指示请求或响应消息不能缓存,实际上是可以存储在本地缓存区中的,只是在与原始服务器进行新鲜度验证之前,缓存不能将其提供给客户端使用。 

no-store: 缓存应该尽快从存储器中删除文档的所有痕迹,因为其中可能会包含敏感信息。

max-age: 缓存无法返回缓存时间长于max-age规定秒的文档,若不超规定秒浏览器将不会发送对应的请求到服务器,数据由缓存直接返回;超过这一时间段才进一步由服务器决定是返回新数据还是仍由缓存提供。若同时还发送了max-stale指令,则使用期可能会超过其过期时间。

min-fresh: 至少在未来规定秒内文档要保持新鲜,接受其新鲜生命期大于其当前 Age  min-fresh 值之和的缓存对象。

max-stale: 指示客户端可以接收过期响应消息,如果指定max-stale消息的值,那么客户端可以接收过期但在指定值之内的响应消息。

only-if-cached: 只有当缓存中有副本存在时,客户端才会获得一份副本。

Public: 指示响应可被任何缓存区缓存,可以用缓存内容回应任何用户。

Private: 指示对于单个用户的整个或部分响应消息,不能被共享缓存处理,只能用缓存内容回应先前请求该内容的那个用户。

#2、Pragma
Pragma头域用来包含实现特定的指令,最常用的是Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache- Control:no-cache相同。

#3、Connection
Connection表示是否需要持久连接。如果Servlet看到这里的值为“Keep-Alive”,或者看到请求使用的是HTTP 1.1(HTTP 1.1默认进行持久连接),它就可以利用持久连接的优点,当页面包含多个元素时(例如Applet,图片),显著地减少下载所需要的时间。要实现这一点,Servlet需要在应答中发送一个Content-Length头,最简单的实现方法是:先把内容写入ByteArrayOutputStream,然后在正式写出内容之前计算它的大小。

Close: 告诉WEB服务器或者代理服务器,在完成本次请求的响应后,断开连接,不要等待本次连接的后续请求了。

Keepalive: 告诉WEB服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求。

Keep-Alive: 如果浏览器请求保持连接,则该头部表明希望 WEB 服务器保持连接多长时间(秒),如Keep-Alive:300。

#4、Date
Date头域表示消息发送的时间,服务器响应中要包含这个头部,因为缓存在评估响应的新鲜度时要用到,其时间的描述格式由RFC822定义。例如,Date:Mon, 31 Dec 2001 04:25:57 GMT。Date描述的时间表示世界标准时,换算成本地时间,需要知道用户所在的时区。

#5、Transfer-Encoding 
WEB服务器表明自己对本响应消息体(不是消息体里面的对象)作了怎样的编码,比如是否分块(chunked),例如:Transfer-Encoding:chunked

#6、Upgrade
它可以指定另一种可能完全不同的协议,如HTTP/1.1客户端可以向服务器发送一条HTTP/1.0请求,其中包含值为“HTTP/1.1”的Update头部,这样客户端就可以测试一下服务器是否也使用HTTP/1.1了。

#7、Via
列出从客户端到 OCS 或者相反方向的响应经过了哪些代理服务器,他们用什么协议(和版本)发送的请求。
当客户端请求到达第一个代理服务器时,该服务器会在自己发出的请求里面添加 Via 头部,并填上自己的相关信息,当下一个代理服务器 收到第一个代理服务器的请求时,会在自己发出的请求里面复制前一个代理服务器的请求的Via头部,并把自己的相关信息加到后面,以此类推,当 OCS 收到最后一个代理服务器的请求时,检查 Via 头部,就知道该请求所经过的路由。例如:Via:1.0 236-81.D07071953.sina.com.cn:80 (squid/2.6.STABLE13)
HTTP请求头

请求头用于说明是谁或什么在发送请求、请求源于何处,或者客户端的喜好及能力。服务器可以根据请求头部给出的客户端信息,试着为客户端提供更好的响应。请求头域可能包含下列字段Accept、Accept-Charset、Accept- Encoding、Accept-Language、Authorization、From、Host、If-Modified-Since、If-Match、If-None-Match、If-Range、If-Range、If-Unmodified-Since、Max-Forwards、Proxy-Authorization、Range、Referer、User-Agent。对请求头域的扩展要求通讯双方都支持,如果存在不支持的请求头域,一般将会作为实体头域处理。

#1、Accept
告诉WEB服务器自己接受什么介质类型,*/* 表示任何类型,type/* 表示该类型下的所有子类型,type/sub-type。

#2、Accept-Charset
浏览器告诉服务器自己能接收的字符集。

#3、Accept-Encoding
浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate)。

#4、Accept-Language
浏览器申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等。

#5、Authorization
当客户端接收到来自WEB服务器的 WWW-Authenticate 响应时,用该头部来回应自己的身份验证信息给WEB服务器。

#6、If-Match
如果对象的 ETag 没有改变,其实也就意味著对象没有改变,才执行请求的动作,获取文档。

#7、If-None-Match
如果对象的 ETag 改变了,其实也就意味著对象也改变了,才执行请求的动作,获取文档。

#8、If-Modified-Since
如果请求的对象在该头部指定的时间之后修改了,才执行请求的动作(比如返回对象),否则返回代码304,告诉浏览器该对象没有修改。例如:If-Modified-Since:Thu, 10 Apr 2008 09:14:42 GMT

#9、If-Unmodified-Since
如果请求的对象在该头部指定的时间之后没修改过,才执行请求的动作(比如返回对象)。

#10、If-Range
浏览器告诉 WEB 服务器,如果我请求的对象没有改变,就把我缺少的部分给我,如果对象改变了,就把整个对象给我。浏览器通过发送请求对象的ETag 或者自己所知道的最后修改时间给 WEB 服务器,让其判断对象是否改变了。总是跟 Range 头部一起使用。

#11、Range
浏览器(比如 Flashget 多线程下载时)告诉 WEB 服务器自己想取对象的哪部分。例如:Range:bytes=1173546

#12、Proxy-Authenticate
代理服务器响应浏览器,要求其提供代理身份验证信息。

#13、Proxy-Authorization
浏览器响应代理服务器的身份验证请求,提供自己的身份信息。

#14、Host
客户端指定自己想访问的WEB服务器的域名/IP 地址和端口号。如Host:rss.sina.com.cn

#15、Referer
浏览器向WEB 服务器表明自己是从哪个网页URL获得点击当前请求中的网址/URL,例如:Referer:http://www.jb51.net  

#16、User-Agent
浏览器表明自己的身份(是哪种浏览器)。例如:User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN;rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14
HTTP响应头

响应头向客户端提供一些额外信息,比如谁在发送响应、响应者的功能,甚至与响应相关的一些特殊指令。这些头部有助于客户端处理响应,并在将来发起更好的请求。响应头域包含Age、Location、Proxy-Authenticate、Public、Retry- After、Server、Vary、Warning、WWW-Authenticate。对响应头域的扩展要求通讯双方都支持,如果存在不支持的响应头域,一般将会作为实体头域处理。

#1、Age
当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了。

#2、Server
WEB 服务器表明自己是什么软件及版本等信息。例如:Server:Apache/2.0.61 (Unix)

#3、Accept-Ranges
WEB服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受。

#4、Vary
WEB服务器用该头部的内容告诉 Cache 服务器,在什么条件下才能用本响应所返回的对象响应后续的请求。假如源WEB服务器在接到第一个请求消息时,其响应消息的头部为:Content-Encoding:gzip; 
Vary:Content-Encoding,那么Cache服务器会分析后续请求消息的头部,检查其Accept-Encoding,是否跟先前响应的Vary头部值一致,即是否使用相同的内容编码方法,这样就可以防止Cache服务器用自己Cache 里面压缩后的实体响应给不具备解压能力的浏览器。例如:Vary:Accept-Encoding。
HTTP实体头

实体头部提供了有关实体及其内容的大量信息,从有关对象类型的信息,到能够对资源使用的各种有效的请求方法。总之,实体头部可以告知接收者它在对什么进行处理。请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。实体头域包含关于实体的原信息,实体头包括信息性头部Allow、Location,内容头部Content-Base、Content-Encoding、Content-Language、Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type,缓存头部Etag、Expires、Last-Modified、extension-header。

#1、Allow
服务器支持哪些请求方法(如GET、POST等)。

#2、Location
表示客户应当到哪里去提取文档,用于将接收端定位到资源的位置(URL)上。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。

#3、Content-Base
解析主体中的相对URL时使用的基础URL。

#4、Content-Encoding
WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip

#5、Content-Language
WEB 服务器告诉浏览器理解主体时最适宜使用的自然语言。

#6、Content-Length
WEB服务器告诉浏览器自己响应的对象的长度或尺寸,例如:Content-Length:26012

#7、Content-Location
资源实际所处的位置。

#8、Content-MD5
主体的MD5校验和。

#9、Content-Range
实体头用于指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。一般格式: Content-Range:bytes-unitSPfirst-byte-pos-last-byte-pos/entity-legth。例如,传送头500个字节次字段的形式:Content-Range:bytes0- 499/1234如果一个http消息包含此节(例如,对范围请求的响应或对一系列范围的重叠请求),Content-Range表示传送的范围,Content-Length表示实际传送的字节数。

#10、Content-Type
WEB 服务器告诉浏览器自己响应的对象的类型。例如:Content-Type:application/xml

#11、Etag
就是一个对象(比如URL)的标志值,就一个对象而言,比如一个html文件,如果被修改了,其Etag也会别修改,所以,ETag的作用跟Last-Modified的作用差不多,主要供WEB服务器判断一个对象是否改变了。比如前一次请求某个html文件时,获得了其 ETag,当这次又请求这个文件时,浏览器就会把先前获得ETag值发送给WEB服务器,然后WEB服务器会把这个ETag跟该文件的当前ETag进行对比,然后就知道这个文件有没有改变了。

#12、Expires
WEB服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟WEB服务器验证了其有效性后,才能用来响应客户请求。是 HTTP/1.0 的头部。例如:Expires:Sat, 23 May 2009 10:02:12 GMT

#13、Last-Modified
WEB服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。例如:Last-Modified:Tue, 06 May 2008 02:42:43 GMT

HTTP状态码

  • 200 - 请求成功
  • 304 - 资源(网页等)被转移到其它URL,缓存
  • 404 - 请求的资源(网页等)不存在。客户端错误
  • 500 - 内部服务器错误

MIME类型

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。

npm install mime

const mime = require('mime');

mime.getType('txt');                    // ⇨ 'text/plain'
mime.getExtension('text/plain');        // ⇨ 'txt'
文件后缀Mime类型说明
.flvflv/flv-flash在线播放
.html或.htmtext/html超文本标记语言文本
.csstext/csscss样式文件
.jsapplication/x-javascriptjs文件
.rtfapplication/rtfRTF文本
.gifimage/gifGIF图形
.jpeg或.jpgimage/jpegJPEG图形
.auaudio/basicau声音文件
.mid或.midiaudio/midi或audio/x-midiMIDI音乐文件
.ra或.ram或.rmaudio/x-pn-realaudioRealAudio音乐文件
.mpg或.mpeg或.mp3video/mpegMPEG文件
.avivideo/x-msvideoAVI文件
.gzapplication/x-gzipGZIP文件
.tarapplication/x-tarTAR文件
.exeapplication/octet-stream下载文件类型
.rmvbvideo/vnd.rn-realvideo在线播放
.txttext/plain普通文本
.mrpapplication/octet-streamMRP文件(国内普遍的手机)
.ipaapplication/iphone-package-archiveIPA文件(IPHONE)
.debapplication/x-debian-package-archiveDED文件(IPHONE)
.apkapplication/vnd.android.package-archiveAPK文件(安卓系统)
.cabapplication/vnd.cab-com-archiveCAB文件(Windows Mobile)
.xapapplication/x-silverlight-appXAP文件(Windows Phone 7)
.sisapplication/vnd.symbian.install-archiveSIS文件(symbian平台)
.jarapplication/java-archiveJAR文件(JAVA平台手机通用格式)
.jadtext/vnd.sun.j2me.app-descriptorJAD文件(JAVA平台手机通用格式)
.sisxapplication/vnd.symbian.epoc/x-sisx-appSISX文件(symbian平台)

Server 实例

API说明
Event:'close'服务关闭时触发
Event:'request'收到请求消息时触发
server.close()关闭服务
server.listening获取服务状态

请求对象(request)

API说明
request.method请求方法
request.url请求路径
request.headers请求头
request.httpVersion请求HTTP协议版本
request: {
    method: 'GET',
    url: '/',
    header: {
      host: 'localhost:3000',
      connection: 'keep-alive',
      'cache-control': 'max-age=0',
      'upgrade-insecure-requests': '1',
      'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
      'sec-fetch-user': '?1',
      accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
      'sec-fetch-site': 'none',
      'sec-fetch-mode': 'navigate',
      'accept-encoding': 'gzip, deflate, br',
      'accept-language': 'zh-CN,zh;q=0.9'
    }
  },

响应对象(response)

API说明
response.end()结束响应
response.setHeader(name, value)设置响应头
response.removeHeader(name, value)删除响应头
response.statusCode设置响应状态码
response.statusMessage设置响应状态短语
response.write()写入响应数据
response.writeHead()写入响应头
res.setHeader('Content-Type', 'text/html; charset=utf-8')

实例

读取html

const http = require('http')
const fs = require('fs')

const hostname = '127.0.0.1'
const port = 3000

const server = http.createServer((req,res)=>{
  fs.readFile('./index.html', (err, data) => {
    if (err) throw err;
    res.statusCode = 200
    res.setHeader('Content-Type', 'text/html;charset=utf-8')
    res.end(data)
  });
})

server.listen(port,hostname,()=>{
  console.log(`服务器运行在 http://${hostname}:${port}上`)
})

读取静态资源

const http = require('http')
const fs = require('fs')
const mime = require('mime') //需要安装依赖
const path = require('path')

const hostname = '127.0.0.1'
const port = 3000

const server = http.createServer((req,res)=>{
  const {url} = req
  if (url === '/') {
    fs.readFile('./index.html', (err, data) => {
      if (err) throw err;
      res.statusCode = 200
      res.setHeader('Content-Type', 'text/html;charset=utf-8')
      res.end(data)
    });
  }else if(url.startsWith('/static/')){
    fs.readFile(`.${url}`,(err,data)=>{
      if (err) {
        res.statusCode = 404
        res.setHeader('Content-Type', 'text/plain;charset=utf-8')
        res.end('404 Not Found !')
      }
      const contentType = mime.getType(path.extname(url))
      res.statusCode = 200
      res.setHeader('Content-Type', contentType)
      res.end(data)
    })
  }else{
    res.statusCode = 404
    res.setHeader('Content-Type', 'text/plain;charset=utf-8')
    res.end('404 Not Found !')
  }
})

server.listen(port,hostname,()=>{
  console.log(`服务器运行在 http://${hostname}:${port}上`)
})

五、WebSocket

类似于 HTTP,websocket是一种网络通信协议。

官网

为什么需要 WebSocket

我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起,没有请求就没有响应。

举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。

轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。

什么是 WebSocket

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

特点

  1. 建立在 TCP 协议之上,服务器端的实现比较容易。
  2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  3. 数据格式比较轻量,性能开销小,通信高效。
  4. 可以发送文本,也可以发送二进制数据。
  5. 没有同源限制,客户端可以与任意服务器通信。
  6. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

转存失败,建议直接上传图片文件

客户端WebSocket

示例

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};
WebSocket 构造函数

WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。

var ws = new WebSocket('ws://localhost:8080');
//执行上面语句之后,客户端就会与服务器进行连接。
事件:onopen

实例对象的onopen属性,用于指定连接成功后的回调函数。

ws.onopen = ()=>{
  // 发送消息一定要在建立连接成功以后
  ws.send('Hello Server!');
}

如果要指定多个回调函数,可以使用addEventListener方法。

ws.addEventListener('open', function (event) {
  ws.send('Hello Server!');
});
事件: onclose

实例对象的onclose属性,用于指定连接关闭后的回调函数。

ws.onclose = function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
};

ws.addEventListener("close", function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
});
事件: onmessage

实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。

ws.onmessage = function(event) {
  var data = event.data;
  // 处理数据
};

ws.addEventListener("message", function(event) {
  var data = event.data;
  // 处理数据
});

注意,服务器数据可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)。

ws.onmessage = function(event){
  if(typeof event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

除了动态判断收到的数据类型,也可以使用binaryType属性,显式指定收到的二进制数据类型。

// 收到的是 blob 数据
ws.binaryType = "blob";
ws.onmessage = function(e) {
  console.log(e.data.size);
};

// 收到的是 ArrayBuffer 数据
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
  console.log(e.data.byteLength);
};
事件:onerror

实例对象的onerror属性,用于指定报错时的回调函数。

socket.onerror = function(event) {
  // handle error event
};

socket.addEventListener("error", function(event) {
  // handle error event
});
方法:send()

实例对象的send()方法用于向服务器发送数据。

ws.send('your message');

发送 Blob 对象的例子。

var file = document
  .querySelector('input[type="file"]')
  .files[0];
ws.send(file);

发送 ArrayBuffer 对象的例子。

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
ws.send(binary.buffer);
方法:close()

实例对象的 close() 方法用于关闭连接。

ws.close()

连接关闭之后会触发实例对象的 onclose 事件。

实例属性:bufferedAmount

实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。

var data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
  // 发送完毕
} else {
  // 发送还没结束
}