网络通信
一、网络通信
1.网络通信基本原理
通信必要条件:
主机之间需要有传输介质(数据信号的传输)
主机上必须有网卡设备(数据信号的调制与解调制)
主机之间需要协商网络速率
2.网络通信方式
交换机通讯
路由器通讯
交换机的接口数量有上限
局域网存在大量主机会造成广播风暴(任意一条消息都需要其它机器接收,再确认受否有效)
3.网络层次模型
OSI七层模型:
数据的封装与解封装(TCP/IP五层协议模式):
参考:https://blog.csdn.net/weixin_...
4.tcp协议
tcp报文结构图解:
常见控制字段:
三次握手图解:
四次挥手图解:
为什么挥手中,中间两次不能合并呢?
一个服务端会服务于多个客户端,服务端接收到消息后,不一定能即使将结果及时传回
TCP协议总结:
TCP处于传输层,基于端口,面向连接
主机之间要想通信需要先建立双向数据通道
TCP的握手与挥手本质上都是四次
二、创建TCP通信
Net模块实现了底层通信接口
通信过程:
创建服务端:接收和回写客户端数据
创建客户端:发送和接收服务端数据
数据传输:内置服务事件和方法读写数据
核心内置通信事件:
listening事件:调用server.listen方法之后触发
connection事件:新的连接建立时触发
close事件:当server关闭时触发
error事件:当错误出现的时候触发
通信事件&方法:
data事件:当接收到数据的时候触发该事件
write方法:在socket上发送数据,默认是UTF8编码
end操作:当socket的一端发送FIN包时触发,结束可读端
1.net模块使用
node中tcp连接的创建与通信:
server:
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}`);
});
// 就收消息,回写消息 socket 双工流
server.on(\'connection\',(socket)=>{
console.log(\'有客户端连接\');
socket.on(\'data\',(chunk)=>{
const msg = chunk.toString();
console.log(\'client:\'+msg);
// 回写消息
socket.write(Buffer.from(\'hello client\'));
})
});
server.on(\'close\',()=>{
console.log(\'服务端已经关闭\');
})
server.on(\'error\',(err)=>{
console.log(\'服务端发生错误\',err);
})
client:
const net = require(\'net\');
const client = net.createConnection({
port: 1234,
host: \'127.0.0.1\'
})
client.on(\'connect\', () => {
client.write(\'hello server\');
})
client.on(\'data\',(chunk)=>{
console.log(\'server:\'+chunk.toString());
});
client.on(\'err\',()=>{
console.log(\'客户端发生错误\');
});
client.on(\'close\',()=>{
console.log(\'客户端已经关闭\');
})
2.TCP粘包问题
问题展示:
client代码:
client.on(\'connect\', () => {
client.write(\'hello server\');
client.write(\'hello server\');
client.write(\'hello server\');
client.write(\'hello server\');
})
server代码:
server.on(\'connection\',(socket)=>{
console.log(\'有客户端连接\');
socket.on(\'data\',(chunk)=>{
const msg = chunk.toString();
console.log(\'client:\'+msg);
// 回写消息
socket.write(Buffer.from(\'hello client\'));
})
});
结果展示:
\'client:\'打印了一次,出现了粘包问题,
1.间断发送数据
client代码:
let dataArr = [
\'hello server\',
\'hello server-2\',
\'hello server-3\',
\'hello server-4\',
]
client.on(\'connect\', () => {
for (let i = 0; i < dataArr.length; i++) {
(function (val, index) {
setTimeout(() => {
client.write(val)
}, 1000 * (index + 1))
})(dataArr[i], i)
}
})
2.数据的封包与拆包
约定包的结构:
数据传输过程:
进行数据编码,获取二进制数据包
按规则拆解数据,获取指定长度的数据
Buffer数据读写:
writeInt16BE:将value从指定位置写入
readInt16BE:从指定位置开始读取数据
封包与拆包类实现:
class MyTransformCode {
constructor() {
this.packageHeaderLen = 4;
this.serialNum = 0;
this.serialLen = 2;
}
// 编码
encode(data, serialNum) {
let body = Buffer.from(data);
// 01 先按照指定的长度申请一个缓冲区
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) {
let 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);
}
}
}
使用:
const MyTransform = require(\'./03-myTransform\');
let ts = new MyTransform();
let str1 = \'江江学习\';
let encodeBuf = ts.encode(str1,1);
console.log(ts.decode(encodeBuf));
let len = ts.getPackageLen(encodeBuf);
console.log(len)
封包解决粘包:
// server
server.on(\'connection\', (socket) => {
console.log(\'有客户端连接\');
socket.on(\'data\', (chunk) => {
if (overageBuffer) {
chunk = Buffer.concat([overageBuffer, chunk]);
overageBuffer = null;
}
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
client.on(\'connect\', () => {
client.write(ts.encode(\'拉钩教育\'));
client.write(ts.encode(\'拉钩教育\'));
client.write(ts.encode(\'拉钩教育\'));
client.write(ts.encode(\'拉钩教育\'));
client.write(ts.encode(\'拉钩教育\'));
client.write(ts.encode(\'拉钩教育\'));
})
client.on(\'data\', (chunk) => {
if (overageBuffer) {
chunk = Buffer.concat([overageBuffer, chunk]);
overageBuffer = null;
}
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;
});
三、Http协议
1.使用http模块
开启一个http服务器:
const http = require(\'http\');
let server = http.createServer((req,res)=>{
res.end(\'hello world\');
});
server.listen(1234,()=>{
console.log(\'server is listening 1234\');
})
获取http请求信息:
const http = require(\'http\');
const url = require(\'url\');
const server = http.createServer((req,res)=>{
// 请求路径
let {pathname,query} = url.parse(req.url,true);
console.log(\'pahtinfo:\',pathname,\'----\',query)
// 请求方法
console.log(\'method:\',req.method);
// 版本号
console.log(\'httpVersion:\',req.httpVersion);
// 请求头
console.log(\'headers:\',req.headers);
let arr = []
// 请求体
req.on(\'data\',(data)=>{
arr.push(data);
});
req.on(\'end\',()=>{
console.log(\'body:\',Buffer.concat(arr).toString())
})
res.end(\'hello client\')
});
server.listen(1234,()=>{
console.log(\'server is listening 1234\');
})
设置http响应:
const http = require(\'http\');
const server = http.createServer((req,res)=>{
console.log(\'request enter\');
// 设置响应状态码
res.statusCode = 302;
// 设置响应头信息
res.setHeader(\'Content-Type\',\'text/html;charset=utf-8\');
// res.write(\'ok\');
res.end(\'江江\');
});
server.listen(1234,()=>{
console.log(\'servet is listening 1234\');
})
2.客户端代理
agent-server代码:
const http = require(\'http\');
const url = require(\'url\');
const qeurystring = require(\'querystring\');
const server = http.createServer((req, res) => {
let { pathname, query } = url.parse(req.url, true);
console.log(pathname, \'---\', query);
let arr = [];
req.on(\'data\', (data) => {
arr.push(data);
})
req.on(\'end\', () => {
let obj = Buffer.concat(arr).toString();
if(req.headers[\'content-type\'] == \'application/json\'){
let ret = JSON.parse(obj);
ret.add = \'add\';
res.end(JSON.stringify(ret));
}else if(req.headers[\'content-type\'] == \'application/x-www-form-urlencoded\'){
let ret = qeurystring.parse(obj);
res.end(JSON.stringify(ret));
}
})
})
server.listen(1234, () => {
console.log(\'server is listening 1234\');
})
agent-client代码:
const http = require(\'http\');
let options = {
host: \'localhost\',
port: 1234,
path: \'/\',
method: \'POST\',
headers: {
\'Content-Type\': \'application/x-www-form-urlencoded\',
}
}
let req = http.request(options, (res) => {
let arr = [];
res.on(\'data\',(chunk)=>{
arr.push(chunk);
})
res.on(\'end\',()=>{
console.log(Buffer.concat(arr).toString());
})
})
// req.end(`{\"name\":\"zhangsan\"}`)
req.end(`a=1&b=2`)
3.代理客户端跨域
外部服务器代码:
const http = require(\'http\');
const server = http.createServer((req, res) => {
let arr = [];
req.on(\'data\',(data)=>{
arr.push(data);
})
req.on(\'end\',()=>{
console.log(Buffer.concat(arr).toString());
res.end(\'外部服务i器端数据\');
})
})
server.listen(1234, () => {
console.log(\'外部服务端启动了\');
})
代理客户服务器:
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\', () => {
let ret = Buffer.concat(arr).toString();
response.setHeader(\'Content-Type\', \'text/html;charset=utf-8\');
console.log(\'ret\', ret)
response.end(ret)
})
})
req.end(\'hello world\');
})
server.listen(2345, () => {
console.log(\'本地服务端已经启动\');
})