node.js的事件循环
node.js是基于事件驱动的,通常在代码中注册想要等待的事件,设定好回调函数,当事件触发的时候就会调用回调函数。如果node.js没有要处理的事件了,那整个就结束了;事件里面可以继续插入事件,如果有事件是一直要继续下去的,那么node 也就不会退出了,每一次事件处理结束后等待下一个事件的发生
console.log("HelloWorld");
// 计时器事件, 每隔一段事件触发一次, time的单位是毫秒
/*setInterval(function () {
console.log("get time doing");
}, 5 * 1000); // 每间隔5秒调用一次*/
// 插入一个事件,让它多长时间以后执行一次
setTimeout(function() {
console.log("set time out");
}, 3 * 1000);
// process是node的一个全局模块
console.log(process.pid);
console.log(process.version);
console.log(process.platform);
console.log(process.title);
console.log(process.argv); // 在启动的时候,我们可以往程序里面传入参数,参数都是字符串
var argc = process.argv.length;
if (argc >= 3) {
console.log(process.argv[2]);
}
console.log(process.execPath); // node所在的路径
console.log(process.env); // 获得系统的环境变量
// 当我们的node在每次退出的时候,都会抛出一个exit这样一个事件,如果我们用户监听这个事件,那么
// 当有exit事件发生的时候,我们之前设置的回掉函数,将会倍调用;
// process.on来监听事件
process.on("exit", function() {
console.log("now node exit!!!!");
});
// 当我们发生了一个未知的异常的时候,我们调用这个回掉函数;
// node 停止处理当前这个事件,继续等待下一个事件的处理,不会整个退出,
// 服务器就不会随意的奔溃
// 可以把这个错误,保存起来,方便我们去查找
process.on("uncaughtException", function(err) {
console.log("uncaughtException called ", err);
});
// 当我们的程序,如果运行的时候有异常,那么这个时候,我们可以通过捕获异常,
// 如果说我们没有捕获的异常,这个uncaughtException
// 如果发生uncaughtException,node是会退出的;
// 没有主动捕获,所以javascript解释器,他是不会继续执行的;
// current work director 当前的工作目录
// ./ ---> C:\Home\workspace\node_js
// process.chdir("C:\\Home\\workspace"); // 修改我们的工作目录,工作目录默认你的node是在哪个目录下启动的,就是那个目录为你的工作目录
console.log(process.cwd());
// while处理时间,进入等待时间之前调用,完成后,去事件里面来等待新的事件发生;
process.nextTick(function() {
console.log("nextTick");
});
TCP网络连接和数据交换
node.js一般用on来监听对应的事件
服务器
// 将net模块 引入进来
var net = require("net");
// 创建一个net.Server用来监听,当连接进来的时候,就会调用我们的函数
// client_sock,就是我们的与客户端通讯建立连接配对的socket
// client_sock 就是与客户端通讯的net.Socket
var server = net.createServer(function(client_sock) {
console.log("client comming", client_sock.remoteAddress, client_sock.remotePort);
// 设置你接受的格式,
// client_sock.setEncoding("utf8");
// client_sock.setEncoding("hex"); // 转成二进制的文本编码
//
// 客户端断开连接的时候处理,用户断线离开了
client_sock.on("close", function() {
console.log("close socket");
});
// 接收到客户端的数据,调用这个函数
// data 默认是Buffer对象,如果你强制设置为utf8,那么底层会先转换成utf8的字符串,传给你
// hex 底层会把这个Buffer对象转成二进制字符串传给你
// 如果你没有设置任何编码 <Buffer 48 65 6c 6c 6f 57 6f 72 6c 64 21>
// utf8 --> HelloWorld!!! hex--> "48656c6c6f576f726c6421"
client_sock.on("data", function(data) {
console.log(data);
client_sock.write("goodbye!!!");
client_sock.end(); // 正常关闭
});
client_sock.on("error", function(err) {
console.log("error", err);
});
});
// 当我开始监听的时候就会调用这个回掉函数
server.on("listening", function() {
console.log("start listening...");
});
// 监听发生错误的时候调用
server.on("error", function() {
console.log("listen error");
});
server.on("close", function() {
console.log("server stop listener");
});
/*
server.on("connection", function(client_sock) {
console.log("client comming 22222");
});
*/
// 编写代码,指示这个server监听到哪个端口上面。
// 127.0.0.1: 6080
// node就会来监听我们的server,等待连接接入
server.listen({
port: 6080,
host: "127.0.0.1",
exclusive: true,
});
// 停止node对server的监听事件处理,那么node就没有其他的事件要处理,所以就退出了。
// server.unref(); // 取消node,对server的事件的监听;
// server.close(); // 主动的掉这个server.close才会触发这个net.Server的close事件
客户端
var net = require("net");
// net.Socket,
var sock = net.connect({
port: 6080,
host: "127.0.0.1",
}, function() {
console.log('connected to server!');
});
// 连接成功调用的事件
sock.on("connect",function() {
console.log("connect success");
// 在这里我们就可以发送数据了
sock.write("HelloWorld!", "utf8");
// end
});
// end
// 有错误发生调用的事件
sock.on("error", function(e) {
console.log("error", e);
});
// socket关闭的事件
sock.on("close", function() {
console.log("close");
});
// 对方发送了关闭数据包过来的事件
sock.on("end", function() {
console.log("end event");
});
// 当有数据发生的时候,调用;
sock.on("data", function(data) {
console.log(data);
});
内存管理Buffer模块与大小端
1:4个字节的数据,存到内存的4个字节;
小尾: LE高位的数据 --> 高地址字节地方; --> “高高低低”
大尾: BE高位的数据 --> 低地址字节地方;
2: 通常我们的计算机的CPU,小尾存储;
分配:
Buffer.alloc(); Buffer.allocUnsafe(); Buffer.allocUnsafeSlow
Buffer.from(array); Buffer.from(buf); Buffer.from(string);
获取Buffer的长度 buf.length;
读写:
1: Buffer读取字节 buf[index];
2:根据数据属于大尾小尾来决定使用BE/LE;
3: readFloatBE/readDoubleBE / readFloatLE/readDoubleLE;
4: 读/写整数: read/write
Int8/UInt
Int16BE/Int16LE/UInt16BE/UInt16LE
Int32BE/Int32LE/UInt32BE/UInt32LE
IntBE/IntLE/UIntBE/UIntLE
floatBE/floatLE/doublBE/doubleLE
常用方法:
1:Buffer.byteLength(str, encoding) 返回字符对象对应的二进制长度;
2: 交换swap16, swap32, swap64 大尾小尾数据变化;
3: bu.values(); 遍历buf里面的每个字节;
4: buf转字符串 buf.toString();
5: Buffer转json字符串 JSON.stringify(buf) buf.toJSON;
6: buf.fill,用特定的数据填充buffer
// (1)给定一个大小
// (2)会给这些内存一个初值,如果你没有指定,那么这个初值就是0;
var buf = Buffer.alloc(10, 0xff);
console.log(buf);
// (1) 给定分配一个给定大小的Buffer的内存
// (2) 不会对这些内存区赋初值的,随机的数据,它原来是什么就是什么;
// Unsafe 指的是没有初始化的内存
buf = Buffer.allocUnsafe(10);
console.log(buf);
// 不重Buffer缓冲区里面分配,直接从操作系统分配
// Slow指的是没有重缓冲池里面高效的分配
// Unsafe, 指的是内存,没有被初始化
buf = Buffer.allocUnsafeSlow(10);
console.log(buf);
// 获得我们的bufer对象的长度
// Buffer一旦分配,大小再也不能改变。
console.log(buf.length);
// 方便的创建方式,复制
// 创建一个Buffer对象,用来存放这个字符串的二进制
buf = Buffer.from("Helloworld!");
console.log(buf);
buf = Buffer.from([123, 22, 24, 36, 47, -1]);
console.log(buf);
// 重新创建一个Buffer,然后把原来Buffer的数据拷贝给新的Buffer
var buf2 = Buffer.from(buf);
console.log(buf2);
// buf[index] index [0, len - 1];
console.log(buf[0], buf[1]);
// 以大尾的形式存放 4个字节的整数
// 0x00 00 ff ff --->655535
// 00 00 ff ff
// start从哪里开始
buf.writeInt32BE(65535, 0);
console.log(buf);
// ff ff 00 00
buf.writeInt32LE(65535, 0);
console.log(buf);
// offset是从哪里开始的位置
var value = buf.readInt32LE(0);
console.log(value);
buf.writeFloatLE(3.14, 0);
console.log(buf.readFloatLE(0));
var len = Buffer.byteLength("HelloWorld");
console.log(len);
len = Buffer.byteLength(buf2);
console.log(len);
// 4个字节的Int为例 4 * 4 = 16;
// 0, 1, 2, 3,| 4, 5, 6, 7, |8, 9, 10, 11, |12, 13, 14, 15
buf = Buffer.alloc(4 * 4);
buf.writeInt32LE(65535, 0);
buf.writeInt32LE(65535, 4);
buf.writeInt32LE(65535, 8);
buf.writeInt32LE(65535, 12);
console.log(buf);
buf.swap32();
// 3, 2, 1, 0,| 7, 6, 5, 4, |11, 10, 9, 8, |15, 14, 13, 12
console.log(buf);
console.log(buf.readInt32BE(0));
console.log(buf.readInt32BE(4));
console.log(buf.readInt32BE(8));
console.log(buf.readInt32BE(12));
for(var v of buf.values()) {
console.log(v);
}
// 二进制 根据特定的编码来转字符串
console.log(buf.toString('utf8'));
// "0000ffff0000ffff0000ffff0000ffff"
console.log(buf.toString('hex'));
console.log(buf.toJSON());
buf.fill('A');
console.log(buf);
console.log(buf.toString('utf8'));
buf.fill("hello");
console.log(buf);
console.log(buf.toString('utf8'));
npm的安装和管理
1:node.js生态里的第三方模块可以通过npm工具来安装和使用,方便大家开发;
2: npm 安装 node.js 模块:
npm install 模块名称 (本地安装)运行目录/node_modules
npm install -g 模块名称 (全局安装 安装到系统的node_modules下)
注意:安装之前,使用npm init命令初始化一个package.json文件,这是当前项目的依赖模块
1:require项目文件 .js代码.json文本, .node二进制必须要使用:绝对路径(/), 相对路径(./, …/)开头
2: 没有写后缀名和路径的require项目文件默认为第三方库,依次加载: .js, .json. node;
3: 如果没有以绝对路径开头或以相对路径开头的为加载模块:
(1)系统模块去查找是否能找到;
(2)当前项目文件夹搜索 ./node_modules;
(3) 上一级项目文件夹搜索 ./node_modules,直到全部搜索完成;
websocket的使用
1:websocket 是一种通讯协议,底层是TCP socket, 基于TCP,它加入了自己的协议用来传输数据;
2: websocket是h5为了上层方便的使用socket而实现的;
3: 发送数据带着长度信息,能避免粘包的问题;
4: 客户端/服务器像事件驱动一样的编写代码,不用考虑底层复杂的事件模型;
服务器
// 加载node上websocket模块 ws;
var ws = require("ws");
// 启动基于websocket的服务器,监听我们的客户端接入进来。
var server = new ws.Server({
host: "127.0.0.1",
port: 6080,
});
// 监听接入进来的客户端事件
function websocket_add_listener(client_sock) {
// close事件
client_sock.on("close", function() {
console.log("client close");
});
// error事件
client_sock.on("error", function(err) {
console.log("client error", err);
});
// end
// message 事件, data已经是根据websocket协议解码开来的原始数据;
// websocket底层有数据包的封包协议,所以,绝对不会出现粘包的情况。
// 每解一个数据包,就会触发一个message事件;
// 不会出现粘包的情况,send一次,就会把send的数据独立封包。
// 想我们如果是直接基于TCP,我们要自己实现类是于websocket封包协议;
client_sock.on("message", function(data) {
console.log(data);
client_sock.send("Thank you!");
});
// end
}
// connection 事件, 有客户端接入进来;
function on_server_client_comming (client_sock) {
console.log("client comming");
websocket_add_listener(client_sock);
}
server.on("connection", on_server_client_comming);
// error事件,表示的我们监听错误;
function on_server_listen_error(err) {
}
server.on("error", on_server_listen_error);
// headers事件, 回给客户端的字符。
function on_server_headers(data) {
// console.log(data);
}
server.on("headers", on_server_headers);
客户端
var ws = require("ws");
// url ws://127.0.0.1:6080
// 创建了一个客户端的socket,然后让这个客户端去连接服务器的socket
var sock = new ws("ws://127.0.0.1:6080");
sock.on("open", function () {
console.log("connect success !!!!");
sock.send("HelloWorld1");
sock.send("HelloWorld2");
sock.send("HelloWorld3");
sock.send("HelloWorld4");
sock.send(Buffer.alloc(10));
});
sock.on("error", function(err) {
console.log("error: ", err);
});
sock.on("close", function() {
console.log("close");
});
sock.on("message", function(data) {
console.log(data);
});
使用浏览器时:
<!DOCTYPE html>
<html>
<head>
<title>skynet WebSocket example</title>
</head>
<body>
<script>
var ws = new WebSocket('ws://127.0.0.1:6080/ws');
ws.onopen = function(){
alert("open");
ws.send('WebSocket');
};
ws.onmessage = function(ev){
alert(ev.data);
};
ws.onclose = function(ev){
alert("close");
};
ws.onerror = function(ev){
console.log(ev);
alert("error");
};
</script>
</body>
</html>
TCP的拆包封包
1: 在通讯的过程中,我们可能有发送多个数据包,数据包A,数据包B,数据包C,此时我们最好的期望是每次收到数据包A,数据包B,数据包C。但是TCP底层为了传送性能,可能会一次把ABC所有数据一起传过来,这个时候收到的是A+B+C,这个时候上层傻眼了,无法区分A,B,C这个叫做—粘包;
处理方式:
1:要解决ABC数据包无法正确的拆分出A,B,C三个数据,我们需要在ABC之间插入长度/分解标志,这样根据长度和分解标志来解析出ABC的数据包;
2: 打入长度信息两种方式:(1)“数据长度” + 包体 (2)(包体 + 特定的结尾符号)
3: 本例采用 数据长度 +包体的方式。数据长度2个字节,超过2个字节大小的数据,上层可以分多次发送;
4: 这里的包,是上层的应用协议的包,与TCP的包是两回事;
工具脚本netpkg.js:
var netpkg = {
// 根据封包协议我们读取包体的长度;
read_pkg_size: function(pkg_data, offset) {
if (offset > pkg_data.length - 2) { // 没有办法获取长度信息的;
return -1;
}
var len = pkg_data.readUInt16LE(offset);
return len;
},
// 把一个要发送的数据,封包 2个字节的长度 + 数据
// data string 二进制的buffer
package_data: function(data) {
var buf = Buffer.allocUnsafe(2 + data.length);
buf.writeInt16LE(2 + data.length, 0);
buf.fill(data, 2);
return buf;
},
unpack_cmd_from_data: function(client_sock, data) {
var last_pkg = client_sock.last_pkg;
var cmd_set = [];
if (last_pkg != null) { // 上一次剩余没有处理完的半包;
var buf = Buffer.concat([last_pkg, data], last_pkg.length + data.length);
last_pkg = buf;
}
else {
last_pkg = data;
}
var offset = 0;
var pkg_len = netpkg.read_pkg_size(last_pkg, offset);
if (pkg_len < 0) {
return cmd_set;
}
while(offset + pkg_len <= last_pkg.length) { // 判断是否有完整的包;
// 根据长度信息来读取我们的数据,架设我们穿过来的是文本数据
var cmd_buf = Buffer.allocUnsafe(pkg_len - 2); // 2个长度信息
last_pkg.copy(cmd_buf, 0, offset + 2, offset + pkg_len);
cmd_set.push(cmd_buf);
offset += pkg_len;
if (offset >= last_pkg.length) { // 正好我们的包处理完了;
break;
}
pkg_len = netpkg.read_pkg_size(last_pkg, offset);
if (pkg_len < 0) {
break;
}
}
// 能处理的数据包已经处理完成了,保存 0.几个包的数据
if (offset >= last_pkg.length) {
last_pkg = null;
}
else { // offset, length这段数据拷贝到新的Buffer里面
var buf = Buffer.allocUnsafe(last_pkg.length - offset);
last_pkg.copy(buf, 0, offset, last_pkg.length);
last_pkg = buf;
}
// end
client_sock.last_pkg = last_pkg;
return cmd_set;
},
// 模拟底层TCP 粘包的问题
test_pkg_two_action: function(action1, action2) {
var buf = Buffer.allocUnsafe(2 + 2 + action1.length + action2.length);
buf.writeInt16LE(2 + action1.length, 0);
buf.fill(action1, 2);
var offset = 2 + action1.length;
buf.writeInt16LE(2 + action2.length, offset);
buf.fill(action2, offset + 2);
return buf
},
// 模拟的一个大的数据包,分两次发送到客户端;
// one cmd half_cmd + half_cmd2
test_pkg_two_slice: function(half_cmd1, half_cmd2) {
//
var buf1 = Buffer.allocUnsafe(2 + half_cmd1.length);
buf1.writeInt16LE(2 + half_cmd1.length + half_cmd2.length, 0);
buf1.fill(half_cmd1, 2);
var buf2 = Buffer.allocUnsafe(half_cmd2.length);
buf2.fill(half_cmd2, 0);
return [buf1, buf2];
}
};
module.exports = netpkg;
服务器脚本tcpserver.js:
var net = require("net");
var netpkg = require("./netpkg");
var protocol = require("./protocol");
function process_one_cmd(sock, cmd) {
console.log(cmd);
var data = protocol.decode(cmd);
console.log(data);
}
var server = net.createServer(function(client_sock) {
console.log("client comming", client_sock.remoteAddress, client_sock.remotePort);
client_sock.last_pkg = null;
client_sock.on("close", function() {
console.log("close socket");
});
client_sock.on("data", function(data) {
var cmd_set = netpkg.unpack_cmd_from_data(client_sock, data);
for(var key in cmd_set) {
process_one_cmd(client_sock, cmd_set[key]);
}
});
client_sock.on("error", function(err) {
console.log("error", err);
});
});
server.on("listening", function() {
console.log("start listening...");
});
server.on("error", function() {
console.log("listen error");
});
server.on("close", function() {
console.log("server stop listener");
});
server.listen({
port: 6080,
host: "127.0.0.1",
exclusive: true,
});
二进制数据协议、Json数据协议
二进制协议:优点是数据包小传输效率高,缺点是要在代码中单独定义每个协议的封包解包,这个过程可以利用protobuf来进行。
Json协议:优点是可以用同一种方式进行封包解包,可读性强。缺点是包体较大,需要使用数字作为key来减小包体。
这里我们规定了传输的数据包含三个部分,0:服务号,1:命令号,2:数据,下面的代码体现了这两种协议的封包和解包。
var bin_encode = {
0: {
// 账号登陆的二进制协议打包, stype = 0, cmd_type = 1,
// data.uname 存放数据名字, data.upwd = 密码 {uname: "xxxx", upwd = "xxxx };
// 制定了我们的登陆命令协议,前个字节存放服务号,后两个字节我们存放命令号,后面我们存放uname字符串的长度,占2个字节;
// 紧接着uname字符串的内容,接着是upwd的长度信息,占两个字节,紧接着是upwd的内容;
1: function (data) { // encode_login_cmd
// 前面2个字节来存放我们的服务号
var cmd_len = 2 + 2 + 2 + data[0].length + 2 + data[1].length;
var cmd_buf = Buffer.allocUnsafe(cmd_len);
// 写入两个字节的服务号
cmd_buf.writeUInt16LE(0, 0); // 写入了0号服务
cmd_buf.writeUInt16LE(1, 2); // 紧跟两个字节,我们写这个命令的命令号
// end
// 数据体
// uname
var len = data[0].length;
cmd_buf.writeUInt16LE(len, 4); // 写入了0号服务
cmd_buf.fill(data[0], 4 + 2);
// upwd
len = data[1].length;
var offset = 6 + data[0].length;
cmd_buf.writeUInt16LE(len, offset); // 写入了0号服务
cmd_buf.fill(data[1], offset + 2);
// end
return cmd_buf;
},
},
};
var bin_decode = {
0: {
// return
1: function(cmd_buf) {
var cmd = {
0: 0,
1: 1,
2: null,
};
var len = cmd_buf.readUInt16LE(4); // uname的长度
var offset = 4 + 2;
var data = {};
data.uname = cmd_buf.toString("utf8", offset, len + offset);
offset += len;
len = cmd_buf.readUInt16LE(offset); // uname的长度
offset += 2;
data.upwd = cmd_buf.toString("utf8", offset, len + offset);
cmd[2] = data;
return cmd;
},
}
}
function bin_encode_cmd(stype, cmd_type, data) {
if (bin_encode[stype] && bin_encode[stype][cmd_type]) {
return bin_encode[stype][cmd_type](data);
}
return null;
}
function bin_decode_cmd(cmd_buf) {
// 前面2个字节是服务号,接着两个字节是命令号
var stype = cmd_buf.readUInt16LE(0);
var cmd_type = cmd_buf.readUInt16LE(2);
if (bin_decode[stype] && bin_decode[stype][cmd_type]) {
return bin_decode[stype][cmd_type](cmd_buf);
}
return null;
// end
}
var bin_protocol = {
encode: bin_encode_cmd,
decode: bin_decode_cmd,
}
function json_encode(stype, cmd_type, data) {
var data = {
0: stype,
1: cmd_type,
2: data,
};
var cmd_buf = JSON.stringify(data);
console.log(cmd_buf);
return cmd_buf;
}
function json_decode(cmd_buf) {
var cmd_data = JSON.parse(cmd_buf);
console.log(cmd_data);
return cmd_data;
}
var json_protocol = {
encode: json_encode,
decode: json_decode,
}
module.exports = bin_protocol;
// module.exports = json_protocol;
Http基础和Webserver
1: http server是一种支持http协议的服务器;
2: http server监听在一个端口上面,等待客户端的http连接;
3: 客户端,创建一个TCP socket 连接到服务器;
4: 客户端像服务器发送一个 http协议的请求数据包;
5: 服务器获得这个请求,然后返回一个http响应数据包;
6: 服务器关闭Tcp socket, 客户端也关闭TCP socket;
7:Connection:keep-alive 如果有再次访问这个服务器上的网页,会继续使用这一条已经建立的连接,Keep-Alive不会永久保持连接,它有一个保持时间,默认是马上关闭;
请求方法: GET, POST, HEAD, PUT, DELETE, OPTIONS,TRACE, CONNECT,使用GET和POST即可满足一般需求
Post相比于Get不同的地方在于可以携带Body数据,数据内容通过Content-type体现
var express = require("express");
var path = require("path");
// 工作目录/www_root,静态网页存放的根目录index.html是入口文件
var app = express();
app.use(express.static(path.join(process.cwd(), "www_root")));
app.listen(6080);
// web url的响应函数,http://127.0.0.1:6080/login访问,动态网页
app.get("/login", function (request, respones) {
console.log(request.query);
// 底层会打包成http协议的回应,发送给客户端
respones.send("SUCCESS!");
});
客户端Get和Post的数据传输
var http = require("http");
/*
[100] = "Continue",
[101] = "Switching Protocols",
[200] = "OK",
[201] = "Created",
[202] = "Accepted",
[203] = "Non-Authoritative Information",
[204] = "No Content",
[205] = "Reset Content",
[206] = "Partial Content",
[300] = "Multiple Choices",
[301] = "Moved Permanently",
[302] = "Found",
[303] = "See Other",
[304] = "Not Modified",
[305] = "Use Proxy",
[307] = "Temporary Redirect",
[400] = "Bad Request",
[401] = "Unauthorized",
[402] = "Payment Required",
[403] = "Forbidden",
[404] = "Not Found",
[405] = "Method Not Allowed",
[406] = "Not Acceptable",
[407] = "Proxy Authentication Required",
[408] = "Request Time-out",
[409] = "Conflict",
[410] = "Gone",
[411] = "Length Required",
[412] = "Precondition Failed",
[413] = "Request Entity Too Large",
[414] = "Request-URI Too Large",
[415] = "Unsupported Media Type",
[416] = "Requested range not satisfiable",
[417] = "Expectation Failed",
[500] = "Internal Server Error",
[501] = "Not Implemented",
[502] = "Bad Gateway",
[503] = "Service Unavailable",
[504] = "Gateway Time-out",
[505] = "HTTP Version not supported",
}
*/
/*
callback(is_success, data/erro)
*/
// get请求的参数,是带在URL的地址上面的
function http_get(ip, port, url, params, callback) {
// step1,创建一个 http.ClientRequest
var options = {
host: "127.0.0.1",
port: port,
path: url + "?" + params,
method: "GET"
};
// 当有请求返回的时候,参数就会被传递为http.IncomingMessage
var req = http.request(options, function(incoming_msg) {
console.log("respones status " + incoming_msg.statusCode);
// 监听IncomingMessage的data事件,当收到服务器发过来的数据的时候,触发这个事件
incoming_msg.on("data", function(data) {
if (incoming_msg.statusCode === 200) {
callback(true, data);
}
});
});
// 把这个请求发送出去
req.end();
}
/*
http_get("127.0.0.1", 6080, "/login", "uname=blake&upwd=123456", function(is_ok, data) {
if (is_ok) {
console.log(data.toString());
}
});
*/
// post可以带body数据传到服务器
function http_post(ip, port, url, params, body, callback) {
// step1,创建一个 http.ClientRequest
var options = {
host: "127.0.0.1",
port: port,
path: url + "?" + params,
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": body.length
}
};
var req = http.request(options, function(incoming_msg) {
console.log("respones status " + incoming_msg.statusCode);
// 监听IncomingMessage的data事件,当收到服务器发过来的数据的时候,触发这个事件
incoming_msg.on("data", function(data) {
if (incoming_msg.statusCode === 200) {
callback(true, data);
}
});
});
// step2 写入body数据
req.write(body);
// 发送请求
req.end();
}
http_post("127.0.0.1", 6080, "/upload", "filename=my_file.txt", "Hello Htpp Post", function(is_ok, data) {
if (is_ok) {
console.log("upload_success", data.toString());
}
});
服务器处理Get和Post请求
var path = require("path");
var express = require("express");
var app = express();
// 配置我们的网站的根目录,www_root
app.use(express.static(path.join(process.cwd(), "www_root")));
app.listen(6080);
// 设置我们的跨域访问
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("X-Powered-By",' 3.2.1')
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
// end
// get, 处理响应
app.get("/login", function (request, respones) {
console.log("/login comming");
// 服务器收到请求后,获取客户端get操作参数
console.log(request.query);
// 服务器回信息给客户端
respones.send("SUCCESS");
});
app.post("/upload", function(request, respones) {
console.log("/upload comming");
// 获得url上传来的参数
console.log(request.query);
// 获得用户给我们发送过来的数据
// 监听我们的data来获得
request.on("data", function(data) {
console.log(data.toString());
respones.send("UPLOAD OK");
});
});
文件的异步读写
1: 导入fs模块: var fs = require(“fs”);
2: 创建一个目录:
异步:fs.mkdir(“路径”, end_func() {});
同步:fs.mkdirSync(“路径”);
3: fs.existsSync(path),异步的被废弃;
4: fs.rename() 修改文件的名字;
5: 打开一个文件: fs.open/fs.openSync
6: 关闭一个文件: fs.close/fs.closeSync;
7: fs.read/fs.readSync;
8: fs.readFile/fs.readFileSync 异步读文件;
9:fs.readdir/fs.readdirSync 读文件夹数据;
10: fs.write/fs.writeSync
11: fs.writeFile/fs.writeFileSync 写文件;
// 获得我们的fs模块
var fs = require("fs");
// 等待我们创建好了这个文件夹以后,再来执行下一条指令
// fs.mkdirSync("./output");
// 当我们执行这个console.log的时候,文件夹已经创建好了,这个是同步
// console.log("hello");
/*
if (fs.existsSync("./output")) {
console.log("output exist");
fs.rename("./output", "./rename_output", function(err) {
if (err) {
console.log(err);
return;
}
console.log("rename sucess");
});
}
else {
// 异步
fs.mkdir("./output", function(err) {
if (err) {
console.log(err);
return;
}
// 回掉函数的位置,能保证你已经穿件好这个好这个我文件夹。
console.log("success");
// end
});
}
// 获取文件信息
/*fs.stat("./hello.txt", function(err, stat) {
if (err) {
console.log(err);
return;
}
console.log(stat.size);
console.log(stat.ctime);
});*/
// end
// 同步
var stat = fs.statSync("./hello.txt");
console.log(stat.size);
// end
// 异步读一个文件
/*fs.readFile("./hello.txt", function(err, file_data) {
if (err) {
console.log(err);
return;
}
console.log(file_data.toString());
});*/
// end
// 同步读一个文件
var file_data = fs.readFileSync("./hello.txt");
console.log(file_data);
// end
// 打开一个文件
fs.open("./hello.txt", "r", function(err, file_handle) {
if (err) {
console.log(err);
return;
}
// 拿到了访问这个文件的钥匙;文档 里面的fd,文件句柄
console.log("open file success");
fs.fstat(file_handle, function(err, stat) {
var buf = Buffer.allocUnsafe(stat.size);
// 异步读写,把我呢见读入到buffer
fs.read(file_handle, buf, 0, stat.size, null, function(err) {
fs.close(file_handle);
console.log(buf.toString());
});
});
})
// end
// 异步写文件
/*fs.writeFile("./writefile.txt", "Hello this write File called!", function(err) {
if (err) {
console.log("err");
return;
}
console.log("success");
});*/
// end
/*
fs.open("./writefile.txt", "a", function(err, fd) {
fs.write(fd, "Hello this write File called!", function(err) {
fs.close(fd);
});
});
*/
var fd = fs.openSync("./writefile.txt", "a");
fs.writeSync(fd, "Hello this write File called!");
fs.closeSync(fd);
BASE64、MD5、SHA1编码解码
1:Base64编码的作用:由于某些系统中只能使用ASCII字符。Base64就是用来将非ASCII字符的数据转换成ASCII字符的一种方法。
Buffer输出成Base64编码;
new Buffer(content).toString(“base64”);
Base64编码 解码
new Buffer(content, “base64”).toString();
2: MD5值:
任何数据,经过MD5加密后,会生成一串16字节的二进制对应的 'hex’字符串;
var md5 = crypto.createHash(‘md5’);
md5.update(content);
md5.digest(‘hex’);
3: SHA1:
任何数据,经过SHA1加密后,会生成一串20字节的二进制对应的 'hex’字符串
var sha1 = crypto.createHash(‘sha1’);
sha1.update(content);
md5.digest(‘hex’);
base64
var fs = require("fs");
function base64_encode(content) {
var buf = new Buffer(content);
var base64 = buf.toString("base64");
return base64;
}
function base64_decode(base64_str) {
var buf = new Buffer(base64_str, "base64");
return buf;
}
// 进一段二进制的数据转成 base64
var base64_str = base64_encode([0xff, 0xf1, 0x11, 0x13, 0xf7]);
console.log(base64_str);
//
var decode_buf = base64_decode(base64_str);
console.log(decode_buf);
// 二进制的图片,转成文本,传送, BASE64编码/解码;
fs.readFile("avator3.png", function(err, data) {
if (err) {
console.log(err);
return;
}
var img_base64 = base64_encode(data);
// console.log(img_base64);
var img_data = base64_decode(img_base64);
fs.writeFile("base64_img.png", img_data);
});
md5/sha1
hash算法不可逆的,不同内容HASH值不同,可以用来存放密码,判断两个文件是否相同等
var crypto = require("crypto");
function md5(data) {
var md5 = crypto.createHash("md5");
md5.update(data);
return md5.digest('hex');
}
// b4 1c 16 1c 9b 8d 83 e3 42 01 bd 3f 57 b7 33 ca -->qidian
var str = md5("qidian");
console.log(str);
// e10adc3949ba59abbe56e057f20f883e --> 123456
var str2 = md5("123456");
console.log(str2);
var data = fs.readFileSync("avator3.png");
// 84 92 a5 0b 16 95 c5 4d 43 62 12 69 5c f0 fc 52 16个字节的,--.Hex
str = md5(data);
console.log(str);
// 4bde5246df25451af00ecba580bdb581
data = fs.readFileSync("base64_img.png");
str = md5(data)
console.log(str);
// end
// d735871a64133ee062400659cf91b8234d1c1930
function sha1(data) {
var sha1 = crypto.createHash("sha1");
sha1.update(data);
return sha1.digest('hex');
}
str = sha1("HelloWorld!");
console.log(str);
定时器Timer
1: setImmediate: 事件循环的当前回合结束时要调用的函数;
2: setInterval 每隔一段时间调用一次;
3: setTimeout 一段时间后触发这个调用;
4:取消:clearImmediate/clearInterval/clearTimeout;对象都是有set系列的函数返回回来的;
fs.readFile("base64_img.png", function(err, data) {
if (err) {
return ;
}
console.log("begin setImmediate");
// 等本次事件执行完成后,再来掉这里设置的回掉函数
setImmediate(function() {
console.log("yes setImmediate called");
});
console.log("end setImmediate");
});
setTimeout(function(data) {
console.log(data);
}, 3000, 12345);
var timer = setInterval(function(data) {
console.log("setInterval called: ", data);
}, 3000, "Hello");
setTimeout(function(data) {
clearInterval(timer);
}, 10000, 12345);
mysql数据库操作
var mysql = require("mysql");
// 创建一个连接池,我们每次要发送 sql语句给mysql server都通过这个连接池来获得连接对象;
// 连接对象的有效性就有这个池来负责管理;
// 避免mysql在一段时间后,没有数据通讯的情况下会自动关闭连接通道;
var conn_pool = mysql.createPool({
host: "127.0.0.1", // 数据库服务器的IP地址
port: 3306, // my.cnf指定了端口,默认的mysql的端口是3306,
database: "my_database", // 要连接的数据库
user: "root",
password: "123456",
});
// callback 1: err, 2, rows, 3, 每个字段的说明
// 异步,执行完后腰等回掉,不是马上就有结果。
// 异步的mysql的操作能投,提升我们的服务器的吞吐量;
function mysql_exec(sql, callback) {
// getConnection 是从这个连接池里面获得mysql的连接通道,
// 异步获取的,如果有结果了,就会调用一个回掉函数;
// err, 是否有错误,如果没有错误err = null, 如果成功后面conn就是我们连接池
// 返回給我们的和mysql server 进行通讯的句柄
conn_pool.getConnection(function(err, conn) {
if (err) { // 如果有错误信息
if(callback) {
callback(err, null, null);
}
return;
}
// 发送数据库的cmd到mysql server;
// query向服务器发送sql语句的命令,有返回的话,就会调用我们的回掉函数;
conn.query(sql, function(sql_err, sql_result, fields_desic) {
if (sql_err) {
if (callback) {
callback(sql_err, null, null);
}
return;
}
// sql_result返回给我们的结果
// fields_desic 每个字段的描述
if (callback) {
callback(null, sql_result, fields_desic);
}
});
});
}
// select的查询结果就是 [包住结果(1条)]
var sql_cmd = "select * from uinfo limit 1";
mysql_exec(sql_cmd, function(err, sql_result, fields_desic) {
if (err) {
return;
}
if (sql_result) {
console.log(sql_result);
}
});
// end
// update一般不关心结果,但是他会给我们回一个我们update的结果的情况。
sql_cmd = "update uinfo set upwd = \"777777777\"";
mysql_exec(sql_cmd, function(err, sql_result, fields_desic) {
if (err) {
return;
}
if (sql_result) {
console.log(sql_result);
}
});
// 插入一条记录
sql_cmd = "insert into uinfo (uname, upwd) values(\"blake\", \"98989898\")";
mysql_exec(sql_cmd, function(err, sql_result, fields_desic) {
console.log("get result");
if (err) {
console.log(err);
return;
}
if (sql_result) {
console.log(sql_result);
}
});
redis数据库操作
1:内存数据库,同时也能够保存数据到磁盘;
2: 比其他的内存数据库有着更多的数据类型:
列表,集合,排序集合,哈希表等;
3:主从结构: 数据可以备份到从服务器;
4: Redis数据操作速度快;
5:所有的操作都是原子操作;
哈希表:
HMSET key 字段 value 字段 value …;
HGETALL key
HEXISTS key 字段
HGET key 字段
HKEYS key 返回所有的字段filed
HMGET key filed
列表:
LPUSH/RPUSH list_name v1
LRANGE list_name start stop
LPOP/RPOP list_name
有序集合: -->排行
ZADD key 权重 value
ZRANGE key start stop
ZRANGE key start stop WITHSCORES (显示权重)
var redis = require("redis");
// 创建一个client连接到了我们的redis server
var client = redis.createClient({
host: "127.0.0.1",
port: 6379,
db: 0,
});
// 表
// key--->value
// set, get
client.set("my_redis_class_key", "123456");
// err, data
client.get("my_redis_class_key", function (err, data) {
if (err) {
return;
}
console.log(data);
});
// end
// 哈希表, 用户表
client.hmset("00015_redis_class_user", {
uname: "blake",
upwd: "123456",
uemail: "1332222@qq.com",
}, function(err) {
});
client.hgetall("00015_redis_class_user", function(err, data) {
if (err) {
return;
}
console.log(data);
});
client.hget("00015_redis_class_user", "uname", function(err, data) {
if (err) {
return;
}
console.log(data);
});
// end
// 列表,数组
client.lpush("0000_1111_7777_data", "xiaohong");
client.rpush("0000_1111_7777_data", "xiaotian");
client.lpush("0000_1111_7777_data", "huangdong");
client.lpush("0000_1111_7777_data", "blake");
client.lrange("0000_1111_7777_data", 0, 10, function(err, data) {
if (err) {
return;
}
console.log(data);
});
client.lpop("0000_1111_7777_data", function(err, data) {
if (err) {
return;
}
console.log(data);
});
// end
// 排行耪的有序集合
client.zadd("0000_1111_rank", 500, "blake");
client.zadd("0000_1111_rank", 400, "huangdong");
client.zadd("0000_1111_rank", 300, "xiaotian");
client.zadd("0000_1111_rank", 100, "xiaoming");
client.zrange("0000_1111_rank", 0, 10, function(err, data) {
if (err) {
return;
}
console.log(data);
});
// end
// 事件
// 监听我们的error事件, 当有错误的时候,不会终止我们的node.js程序
// 如果链接服务器不上,我们会触发自动重连
client.on("error", function(err) {
console.log(err);
});
client.on("end", function() {
});
// end