通信必要条件
常见的通讯方式
如何建立多台主机互连?
通过Mac地址来唯一标识一台主机
七层模型
数据从A至B,先封装再解封
TCP协议
三次握手
看着4次握手,但是中间两次是同时进行的,进行合并,这样最终就有了3次握手
四次挥手
1-客户端发送断开连接请求给服务端
2-服务端发送消息确认给客户端
3-服务端发送断开连接请求给客户端
4-客户端发送消息确认给服务端
三次握手的原因是:防止过期的连接请求到达服务器端,如果只有两次握手,则服务器端会建立一个不需要的连接,因此会造成服务器资源的浪费。
四次挥手的原因是:四次挥手的2-3不能合并,因为一个服务端要服务于多个客户端,不能保证某一个客户端发送断开请求之后服务端立即将结果数据全部传输回给这个客户端
详解参考:https://blog.csdn.net/qzwzsl/article/details/126252110
https://mp.weixin.qq.com/s?__biz=MzI4NjE4NTUwNQ==&mid=2247494421&idx=1&sn=8a1e925f02a3c491c3a0b34f201c344c&chksm=ebe26a5bdc95e34d590609df45c207948c0fd97ec9baee54c70a8993e2c7937c8d0da2086ab2&scene=27
TCP协议
Net
模块实现了底层通信接口
通信过程
通信事件
通信事件&方法
server.js
const net = require('net')
// 创建服务端实例
const server = net.createServer()
const PORT = 1234
const HOST = 'localhost'
server.listen(PORT, HOST)
server.on('listening', () => {
console.log(`服务端已经开启在 ${HOST}: ${PORT}`)
})
// 接收消息 回写消息
server.on('connection', (socket) => {
socket.on('data', (chunk) => {
const msg = chunk.toString()
console.log(msg)
// 回数据
socket.write(Buffer.from('您好' + msg))
})
})
server.on('close', () => {
console.log('服务端关闭了')
})
server.on('error', (err) => {
if (err.code == 'EADDRINUSE') {
console.log('地址正在被使用')
}else{
console.log(err)
}
})
client.js
const net = require('net')
const client = net.createConnection({
port: 1234,
host: '127.0.0.1'
})
client.on('connect', () => {
client.write('success')
})
client.on('data', (chunk) => {
console.log(chunk.toString())
})
client.on('error', (err) => {
console.log(err)
})
client.on('close', () => {
console.log('客户端断开连接')
})
通信包括数据的发送端和接收端
发送端累积数据统一发送
接受端缓冲数据之后再消费
TCP拥塞机制决定发送机制
解决方式:可以加延迟
Buffer数据读写
transform.js
class MyTransformCode{
constructor() {
this.packageHeaderLen = 4 //字节
this.serialNum = 0
this.serialLen = 2
}
// 编码
encode(data, serialNum) {
const body = Buffer.from(data)
// 01 先按照指定的长度来申请一片内存空间做为 header 来使用
const headerBuf = Buffer.alloc(this.packageHeaderLen)
// 02
headerBuf.writeInt16BE(serialNum || this.serialNum)
headerBuf.writeInt16BE(body.length, this.serialLen)
if (serialNum == undefined) {
serialNum++
}
return Buffer.concat([headerBuf, body])
}
// 解码
decode(buffer) {
const headerBuf = buffer.slice(0, this.packageHeaderLen)
const bodyBuf = buffer.slice(this.packageHeaderLen)
return {
serialNum: headerBuf.readInt16BE(),
bodyLength: headerBuf.readInt16BE(this.serialLen),
body: bodyBuf.toString()
}
}
// 获取包长度的方法
getPackageLen(buffer) {
if (buffer.length < this.packageHeaderLen) {
return 0
} else {
return this.packageHeaderLen + buffer.readInt16BE(this.serialLen)
}
}
}
module.exports = MyTransformCode
test.js
const MyTransform = require('./myTransform.js')
let ts = new MyTransform()
let str1 = '11'
// console.log(Buffer.from(str1))
// console.log(ts.encode(str1, 1))
let encodeBuf = ts.encode(str1, 1)
/*let a = ts.decode(encodeBuf)
console.log(a) */
let len = ts.getPackageLen(encodeBuf)
console.log(len)
server.js
const net = require('net')
const MyTransform = require('./myTransform.js')
const server = net.createServer()
let overageBuffer = null
let ts = new MyTransform()
server.listen('1234', 'localhost')
server.on('listening', () => {
console.log('服务端运行在 localhost:1234')
})
server.on('connection', (socket) => {
socket.on('data', (chunk) => {
if (overageBuffer) {
chunk = Buffer.concat([overageBuffer, chunk])
}
let packageLen = 0
while(packageLen = ts.getPackageLen(chunk)) {
const packageCon = chunk.slice(0, packageLen)
chunk = chunk.slice(packageLen)
const ret = ts.decode(packageCon)
console.log(ret)
socket.write(ts.encode(ret.body, ret.serialNum))
}
overageBuffer = chunk
})
})
client.js
const net = require('net')
const MyTransform = require('./myTransform.js')
let overageBuffer = null
let ts = new MyTransform()
const client = net.createConnection({
host: 'localhost',
port: 1234
})
client.write(ts.encode('11'))
client.write(ts.encode('22'))
client.write(ts.encode('33'))
client.write(ts.encode('44'))
client.write(ts.encode('55'))
client.on('data', (chunk) => {
if (overageBuffer) {
chunk = Buffer.concat([overageBuffer, chunk])
}
let packageLen = 0
while(packageLen = ts.getPackageLen(chunk)) {
const packageCon = chunk.slice(0, packageLen)
chunk = chunk.slice(packageLen)
const ret = ts.decode(packageCon)
console.log(ret)
}
overageBuffer = chunk
})
mytransform.js
class MyTransformCode{
constructor() {
this.packageHeaderLen = 4 // 字节
this.serialNum = 0
this.serialLen = 2
}
// 编码
encode(data, serialNum) {
const body = Buffer.from(data)
// 01 先按照指定的长度来申请一片内存空间做为 header 来使用
const headerBuf = Buffer.alloc(this.packageHeaderLen)
// 02
headerBuf.writeInt16BE(serialNum || this.serialNum)
headerBuf.writeInt16BE(body.length, this.serialLen)
if (serialNum == undefined) {
this.serialNum++
}
return Buffer.concat([headerBuf, body])
}
// 解码
decode(buffer) {
const headerBuf = buffer.slice(0, this.packageHeaderLen)
const bodyBuf = buffer.slice(this.packageHeaderLen)
return {
serialNum: headerBuf.readInt16BE(),
bodyLength: headerBuf.readInt16BE(this.serialLen),
body: bodyBuf.toString()
}
}
// 获取包长度的方法
getPackageLen(buffer) {
if (buffer.length < this.packageHeaderLen) {
return 0
} else {
return this.packageHeaderLen + buffer.readInt16BE(this.serialLen)
}
}
}
module.exports = MyTransformCode
server.js
const net = require('net')
// 创建服务端
let server = net.createServer()
server.listen(1234, () => {
console.log('服务端启动了....')
})
server.on('connection', (socket) => {
socket.on('data', (data) => {
console.log(data.toString())
})
socket.end('test http request')
})
client.js
const http = require('http')
// 创建服务端
let server = http.createServer((req, res) => {
// 针对于请求和响应完成各自的操作
console.log('1111')
})
server.listen(1234, () => {
console.log('server is running......')
})
request.js
const http = require('http')
const url = require('url')
const server = http.createServer((req, res) => {
// console.log('请求进来了')
// 请求路径
let {pathname, query} = url.parse(req.url, true)
console.log(pathname, '----', query)
// 请求方式
console.log(req.method)
// 版本号
// console.log(req.httpVersion)
// 请求头
// console.log(req.headers)
// 请求体数据获取
let arr = []
req.on('data', (data) => {
arr.push(data)
})
req.on('end', () => {
console.log(Buffer.concat(arr).toString())
})
})
server.listen(1234, () => {
console.log('server is start......')
})
response.js
const http = require('http')
const server = http.createServer((req, res) => {
console.log('有请求进来了')
// res
// res.write('ok')
// res.end()
// res.end('test ok')
res.statusCode = 302
res.setHeader('Content-type', 'text/html;charset=utf-8')
res.end('success')
})
server.listen(1234, () => {
console.log('server is start.....')
})
服务端-》服务端 是没有跨域的
client.js
const http = require('http')
let options = {
host: 'localhost',
port: 1234,
path: '/',
method: 'POST'
}
let server = http.createServer((request, response) => {
let req = http.request(options, (res) => {
let arr = []
res.on('data', (data) => {
arr.push(data)
})
res.on('end', () => {
// console.log(Buffer.concat(arr).toString())
let ret = Buffer.concat(arr).toString()
response.setHeader('Content-type', 'text/html;charset=utf-8')
response.end(ret)
})
})
req.end('success')
})
server.listen(2345, () => {
console.log('本地的服务端启动了')
})
server.js
const http = require('http')
const server = http.createServer((req, res) => {
// console.log('请求进来了')
let arr = []
req.on('data', (data) => {
arr.push(data)
})
req.on('end', () => {
console.log(Buffer.concat(arr).toString())
res.end('111111')
})
})
server.listen(1234, () => {
console.log('外部服务端启动了')
})
解决方案:因为服务端访问服务端是不需要跨域的,所以在本地搭建一个服务
server.js
const http = require('http')
const server = http.createServer((req, res) => {
// console.log('请求进来了')
let arr = []
req.on('data', (data) => {
arr.push(data)
})
req.on('end', () => {
console.log(Buffer.concat(arr).toString())
res.end('111111')
})
})
server.listen(1234, () => {
console.log('外部服务端启动了')
})
agent-client.js
const http = require('http')
let options = {
host: 'localhost',
port: 1234,
path: '/',
method: 'POST'
}
let server = http.createServer((request, response) => {
let req = http.request(options, (res) => {
let arr = []
res.on('data', (data) => {
arr.push(data)
})
res.on('end', () => {
// console.log(Buffer.concat(arr).toString())
let ret = Buffer.concat(arr).toString()
response.setHeader('Content-type', 'text/html;charset=utf-8')
response.end(ret)
})
})
req.end('拉勾教育')
})
server.listen(2345, () => {
console.log('本地的服务端启动了')
})
server.js
const mime = require(‘mime’)获取文件类型
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
const mime = require('mime')
const server = http.createServer((req, res) => {
// console.log('请求进来了')
// 1 路径处理
let {pathname, query} = url.parse(req.url)
pathname = decodeURIComponent(pathname)
let absPath = path.join(__dirname, pathname)
// console.log(absPath)
// 2 目标资源状态处理
fs.stat(absPath, (err, statObj) => {
if(err) {
res.statusCode = 404
res.end('Not Found')
return
}
if (statObj.isFile()) {
// 此时说明路径对应的目标是一个文件,可以直接读取然后回写
fs.readFile(absPath, (err, data) => {
res.setHeader('Content-type', mime.getType(absPath) + ';charset=utf-8')
res.end(data)
})
} else {
fs.readFile(path.join(absPath, 'index.html'), (err, data) => {
res.setHeader('Content-type', mime.getType(absPath) + ';charset=utf-8')
res.end(data)
})
}
})
})
server.listen(1234, () => {
console.log('server is start.....')
})
index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
<link rel="stylesheet" href="./index.css">
head>
<body>
<h2>测试内容1111h2>
body>
html>
利用一些 node 内置的核心模块,配合着一些第三方工具包,实现自己的命令行工具,使可以调用相应的命令,在指定的目录下来启动一个 web 服务,接着就可以使用浏览器来访问当前目录下的所有静态资源。
这里参照一个已经存在的 server 工具的使用。
为了便于看语法,www 先加上.js
再运行 npm init -y
生成 package.json 文件,修改 bin 里边的指令路径为 bin/www.js
在 www.js 里写上说明行的东西 #! /usr/bin/env node
再随便输出个东西:console.log(‘执行了’)
然后再将这个包link
到全局,npm link
即可
直接运行 lgserve
OK, 全局的命令 lgserve 就生成了
利用第三方工具包: npm install commander
,注意:这里当前是
"commander": "^6.0.0"
版本
#! /usr/bin/env node
const {program} = require('commander');
// program.option('-p --port', 'set server port')
// 配置信息
let options = {
'-p --port ' : {
'description': 'init server port',
'example': 'myloserver -p 3306',
},
'-d --directory ' : {
'description': 'init server directory',
'example': 'myloserver -d c:',
},
}
function formatConfig(configs, callback){
Object.entries(configs).forEach(([key, val])=>{
callback(key, val)
})
}
formatConfig(options, (cmd, val)=>{
program.option(cmd, val.description)
})
program.parse(process.argv)
program.on('--help', ()=>{
console.log('examples: ');
formatConfig(options, (cmd, val)=>{
console.log(val.example)
})
})
program.name('mylgserver')
添加上版本号:注意,版本号在 package.json 里边
const version = require('../package.json').version;
program.version(version)
let cmdConfig = program.parse(process.argv)
console.log(cmdConfig);
#! /usr/bin/env node
const {program} = require('commander')
// console.log('执行了')
// program.option('-p --port', 'set server port')
// 配置信息
let options = {
'-p --port ' : {
'description': 'init server port',
'example': 'lgserve -p 3306'
},
'-d --directory ' : {
'description': 'init server directory',
'example': 'lgserve -d c:'
}
}
function formatConfig (configs, cb) {
Object.entries(configs).forEach(([key, val]) => {
cb(key, val)
})
}
formatConfig(options, (cmd, val) => {
program.option(cmd, val.description)
})
program.on('--help', () => {
console.log('Examples: ')
formatConfig(options, (cmd, val) => {
console.log(val.example)
})
})
program.name('lgserve')
let version = require('../package.json').version
program.version(version)
let cmdConfig = program.parse(process.argv)
// console.log(cmdConfig)
let Server = require('../main.js')
new Server(cmdConfig).start()
这里的主要逻辑都放在 main.js 里在www.js 里直接引入就行,也可以直接放在 www.js 里,比较臃肿就不便于维护了
const Server = require('../main.js');
new Server(cmdConfig).start()
// main.js
const http = require('http');
class Server{
constructor(config){
this.config = config
}
start(){
console.log('服务端已经运行了');
}
}
module.exports = Server
const http = require('http');
function mergeConfig(config){
return {
port: 1234,
directory: process.cwd(),
...config
}
}
class Server{
constructor(config){
this.config = mergeConfig(config)
console.log(this.config);
}
start(){
console.log('服务端已经运行了');
}
}
module.exports = Server
加上参数:lgserve -p 3307 -d c:
启动一个服务:
start(){
let server = http.createServer(this.serveHandle.bind(this))
server.listen(this.config.port, ()=>{
console.log('服务端已经启动了......')
})
}
serveHandle(req, res){
console.log('有请求进来了');
}
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs').promises;
const { createReadStream } = require('fs');
const mime = require('mime');
function mergeConfig(config) {
return {
port: 1234,
directory: process.cwd(),
...config
}
}
class Server {
constructor(config) {
this.config = mergeConfig(config)
}
start() {
let server = http.createServer(this.serveHandle.bind(this))
server.listen(this.config.port, () => {
console.log('服务端已经启动了......')
})
}
async serveHandle(req, res) {
let { pathname, query } = url.parse(req.url)
pathname = decodeURIComponent(pathname)
let absPath = path.join(this.config.directory, pathname)
console.log(absPath);
try {
let statObj = await fs.stat(absPath)
if (statObj.isFile()) { // 是个文件
this.fileHandle(req, res, absPath)
} else { // 是个文件夹
this.fileHandle(req, res, absPath + '/www.js')
}
} catch (error) {
this.errorHandle(req, res, error)
}
}
fileHandle(req, res, absPath) {
res.statusCode = 200
res.setHeader('Content-Type', mime.getType(absPath) + '; charset=utf-8')
createReadStream(absPath).pipe(res)
}
errorHandle(req, res, error) {
// console.log(error);
res.statusCode = 404
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('Not Found')
}
}
module.exports = Server
已经存在的 serve 工具的操作是如果我们访问的是一个目录,它就会把它下边的所有资源名称展示出来,放在浏览器所对应的界面当中进行显示,这里也这样操作。
# 使用了 ejs 模板引擎
npm install ejs
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs').promises;
const { createReadStream } = require('fs');
const mime = require('mime');
const ejs = require('ejs');
const { promisify } = require('util')
function mergeConfig(config) {
return {
port: 1234,
directory: process.cwd(),
...config
}
}
class Server {
constructor(config) {
this.config = mergeConfig(config)
}
start() {
let server = http.createServer(this.serveHandle.bind(this))
server.listen(this.config.port, () => {
console.log('服务端已经启动了......')
})
}
async serveHandle(req, res) {
console.log('请求进来了')
console.log(req.url)
let { pathname, query } = url.parse(req.url)
pathname = decodeURIComponent(pathname)
let absPath = path.join(this.config.directory, pathname)
console.log(absPath);
try {
let statObj = await fs.stat(absPath)
if (statObj.isFile()) { // 是个文件
this.fileHandle(req, res, absPath)
} else { // 是个文件夹
let dirs = await fs.readdir(absPath)
console.log(dirs);
// promisify
let renderFile = promisify(ejs.renderFile)
let ret = await renderFile(path.resolve(__dirname, 'template.html'), {arr: dirs})
res.end(ret)
}
} catch (error) {
this.errorHandle(req, res, error)
}
}
fileHandle(req, res, absPath) {
res.statusCode = 200
res.setHeader('Content-Type', mime.getType(absPath) + '; charset=utf-8')
createReadStream(absPath).pipe(res)
}
errorHandle(req, res, error) {
// console.log(error);
res.statusCode = 404
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('Not Found')
}
}
module.exports = Server
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<ul>
<% for(let i=0; i< arr.length; i++) { %>
<li><a href="#"><%=arr[i]%>a>li>
<% } %>
ul>
body>
html>
main.js
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs').promises
const {createReadStream} = require('fs')
const mime = require('mime')
const ejs = require('ejs')
const {promisify} = require('util')
function mergeConfig (config) {
return{
port: 1234,
directory: process.cwd(),
...config
}
}
class Server{
constructor(config) {
this.config = mergeConfig(config)
// console.log(this.config)
}
start() {
let server = http.createServer(this.serveHandle.bind(this))
server.listen(this.config.port, () => {
console.log('服务端已经启动了.......')
})
}
async serveHandle(req, res) {
let {pathname} = url.parse(req.url)
pathname = decodeURIComponent(pathname)
let abspath = path.join(this.config.directory, pathname)
// console.log(abspath)
try {
let statObj = await fs.stat(abspath)
if (statObj.isFile()) {
this.fileHandle(req, res, abspath)
} else {
let dirs = await fs.readdir(abspath)
dirs = dirs.map((item) => {
return{
path: path.join(pathname, item),
dirs: item
}
})
// console.log(dirs)
let renderFile = promisify(ejs.renderFile)
let parentpath = path.dirname(pathname)
let ret = await renderFile(path.resolve(__dirname, 'template.html'), {
arr: dirs,
parent: pathname == '/' ? false : true,
parentpath: parentpath,
title: path.basename(abspath)
})
res.end(ret)
}
} catch (err) {
this.errorHandle(req, res, err)
}
}
errorHandle(req, res, err) {
console.log(err)
res.statusCode = 404
res.setHeader('Content-type', 'text/html;charset=utf-8')
res.end('Not Found')
}
fileHandle(req, res, abspath) {
res.statusCode = 200
res.setHeader('Content-type', mime.getType(abspath) + ';charset=utf-8')
createReadStream(abspath).pipe(res)
}
}
module.exports = Server
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
list-style: none;
}
</style>
</head>
<body>
<h3>IndexOf <%=title%></h3>
<ul>
<%if(parent) {%>
<li><a href="<%=parentpath%>">上一层</a></li>
<%}%>
<%for(let i = 0; i < arr.length; i++) {%>
<li><a href="<%=arr[i].path%>"><%=arr[i].dirs%></a></li>
<%}%>
</ul>
</body>
</html>