node.js服务器基础

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 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/890006.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手撕数据结构 —— 带头双向循环链表(C语言讲解)

目录 0.前言 1.什么是带头双向循环链表 理解带头 ​编辑 理解双向 理解循环 2.带头双向循环链表的实现 List.h文件中接口总览 具体实现 结点的定义 申请结点 初始化 打印链表 尾插 尾删 头插 头删 ​编辑​编辑 获取大小 查找 在指定位置前插入 ​编辑…

数据结构--线性表双向链表的实现

目录 思路设计 总体思维导图 插入部分 头插法尾插法 任意位置插入 删除部分 头结点 尾节点 中间节点 只有头结点且删除的就是头结点 ​编辑 清空链表部分 遍历清空链表的所有节点 不遍历清空 各部分代码 Main部分 MyListedList部分 IndexOutOfException部分 …

YOLO11改进 | 注意力机制 | 结合静态和动态上下文信息的注意力机制

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 上下文Transformer&#xff08;CoT&…

WPF中的布局

布局原则 1、不显式设置元素大小。 2、不使用绝对定位。 元素应该根据容器的内容来进行排列。绝对定位在开发前期会带来一些便捷&#xff0c;但扩展性比较差。一旦显示器尺寸或分辨率发生改变&#xff0c;界面的显示效果可能会达不到预期的效果。 3、布局容器可以嵌套使用 常…

【Axure原型分享】标签管理列表

今天和大家分享通过标签管理列表的原型模板&#xff0c;包括增删改查搜索筛选排序分页翻页等效果&#xff0c;这个模板是用中继器制作的&#xff0c;所以使用也很方便&#xff0c;初始数据我们只要在中继器表格里填写即可&#xff0c;具体效果可以观看下方视频或者打开预览地址…

【经管】上市公司供应链金融数据(1990-2023年)

上市公司供应链金融是指上市公司利用其产业链核心地位&#xff0c;通过整合金融资源&#xff0c;为供应链上下游企业提供包括融资、结算、风险管理等在内的综合金融服务。为了衡量上市公司的供应链金融水平&#xff0c;参考周兰等&#xff08;2022&#xff09;的研究方法&#…

【C++入门篇 - 3】:从C到C++第二篇

文章目录 从C到C第二篇new和delete命名空间命名空间的访问 cin和coutstring的基本使用 从C到C第二篇 new和delete 在C中用来向系统申请堆区的内存空间 New的作用相当于C语言中的malloc Delete的作用相当于C语言中的free 注意&#xff1a;在C语言中&#xff0c;如果内存不够…

IBM Flex System服务器硬件监控指标解读

随着企业IT架构的日益复杂&#xff0c;服务器的稳定运行对于保障业务连续性至关重要。IBM Flex System作为一款模块化、可扩展的服务器解决方案&#xff0c;广泛应用于各种企业级环境中。为了确保IBM Flex System服务器的稳定运行&#xff0c;监控易作为一款专业的IT基础设施监…

git维护【.gitignore文件】

在工程下添加 .gitignore 文件【git忽略文件】 *.class .idea *.iml *.jar /*/target/更多&#xff1a; # Compiled class file *.class# Log file *.log *.imi *.lst# BlueJ files *.ctxt# Mobile Tools for Java (J2ME) .mtj.tmp/# Package Files # *.jar *.war *.nar *.ea…

【MySQL 保姆级教学】数据库基础(重点)(2)

目录 1. 什么是数据库1.1 数据库的定义1.2 mysql 和 mysqld1.3 文件和数据库 2. 数据库的分类3. 连接数据库3.1 数据库的安装3.2 连接服务器&#xff08;数据库&#xff09;3.3 服务器 数据库 表 三者的关系 4. 数据库-表 和目录-文件 的关系5. MySQL 框架6. SQL 分类7. 储存引…

DDoS攻击快速增长,如何在抗ddos防护中获得主动?

当下DDoS攻击规模不断突破上限。前段时间&#xff0c;中国首款3A《黑神话&#xff1a;悟空》也在一夜之内遭受到28万次攻击DDoS攻击&#xff0c;严重影响到全球玩家的游戏体验。Gcore发布的数据也显示了 DDoS攻击令人担忧的趋势&#xff0c;尤其是峰值攻击已增加到了令人震惊的…

CNN-GRU时序预测 | MATLAB实现CNN-GRU卷积门控循环单元时间序列预测

时序预测 | MATLAB实CNN-GRU卷积门控循环单元时间序列预测 目录 时序预测 | MATLAB实CNN-GRU卷积门控循环单元时间序列预测预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 本次运行测试环境MATLAB2020b 提出了一种基于卷积神经网络(Convolutional Neural Network…

生成式专题的第一节课---GAN图像生成

一、GAN的起源与发展 1.GAN的起源 GAN &#xff08;生成式对抗网络&#xff09;诞生于 2014 年&#xff0c;由 Ian Goodfellow 提出&#xff0c;是用于生成数据的深度学习模型&#xff0c;创新点是对抗性训练&#xff0c;即生成器与判别器的竞争关系&#xff0c;为图像生成、…

【网络安全】利用XSS、OAuth配置错误实现token窃取及账户接管 (ATO)

未经许可,不得转载。 文章目录 正文正文 目标:target.com 在子域sub1.target.com上,我发现了一个XSS漏洞。由于针对该子域的漏洞悬赏较低,我希望通过此漏洞将攻击升级至app.target.com,因为该子域的悬赏更高。 分析认证机制后,我发现: sub1.target.com:使用基于Cook…

DBA | 如何将 .mdf 与 .ldf 的数据库文件导入到SQL Server 数据库中?

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] 原文链接&#xff1a;DBA | 如何将 .mdf 与 .ldf 的数据库文件导入到SQL Server 数据库中? 如何将 (.mdf) 和 (.ldf) 的SQL Server 数据库文件导入到当前数据库中? Step 1.登录到 Sql Server 服…

Springboot——使用poi实现excel动态图片导入解析

文章目录 前言依赖引入导入实现方式一方式二 导出参考 前言 最近要实现一个导入导出的功能点&#xff0c;需要能将带图片的列表数据导出到excel中&#xff0c;且可以导入带图片的excel列表数据。 考虑到低代码平台的表头与数据的不确定性&#xff0c;技术框架上暂定使用Apach…

线性代数在大一计算机课程中的重要性

线性代数在大一计算机课程中的重要性 线性代数是一门研究向量空间、矩阵运算和线性变换的数学学科&#xff0c;在计算机科学中有着广泛的应用。大一的计算机课程中&#xff0c;线性代数的学习为学生们掌握许多计算机领域的关键概念打下了坚实的基础。本文将介绍线性代数的基本…

C++一个很好的计时方法

C一个很好的计时方法 //记时LARGE_INTEGER t1;LARGE_INTEGER t2;LARGE_INTEGER f;QueryPerformanceFrequency(&f);QueryPerformanceCounter(&t1);Sleep(100);QueryPerformanceCounter(&t2);double time;time (double)(t2.QuadPart-t1.QuadPart)/(double)f.QuadPar…

【Flutter】合并多个流Stream

1.说明 无意间发现了一个好用的库rxdart&#xff0c;它为 Dart 的 Stream 添加了额外的功能。 2.功能 &#xff08;1&#xff09;合并多个流Stream 借助Rx.combineLatest2()合并两个流stream1和stream2。 注意&#xff1a;如果dart文件中同时使用了getx&#xff0c;需要隐…