Socket implementation - NULL-HDU/Barrage_Frontend GitHub Wiki

By yummyLcj on 30th December 2016 @NULL TEAM

##socket传输数据格式 为了尽量解决网速慢的问题,减少数据的传输量,因此采用二进制来传输数据 ###dataview 双方数据为二进制,websocket的binaryType为arraybuffer,js新建dataview来传输二进制。

###封装dataview

####dataview对于本系统的不足 js自带的dataview需制定下标来存/取,不利于代码的修改与版本更新,因此重新将dataview自带的api封装来方便代码的修改。

####封装后dataview的设计 模仿栈的实现,实现先进先出,并且为了适应dataview的存取方法,因此为了能存取不同的位数,需要封装多个方法

####属性

  • dv - dataview的内容,原dataview
  • dvLength - dv的长度
  • head - dv的头指正

####方法

#####构造函数 通过初始化时传进来的参数(长度或dataview)进行判断,可新创建dataview,或通过已有的dataview来穿件dataview。新创建的用来向里面写入东西,已有的用来读数据。 #####读函数 通过get方法和head来读数据,同时判断head是否会超过dvlength,读后head向前移 如:

pop8(){
	this.isEnough(1);
	let returnElement = this.dv.getUint8(this.head-1);
	return returnElement;
}

#####存函数 通过set方法和head来存数据,同时判断head是否会超过dvlength,存后head向前移 如:

push8(content){
	this.isLegal(1,content);
	this.dv.setUint8( this.head-1,content );
}

#####读dataview 一字节一字节的读dv,来获取dv的所有二进制内容

getDetail(){
	var str = ""
	for(var i=0;i<this.dv.byteLength;i++){
    	var char = this.dv.getUint8(i).toString(16);
    	if(char.length<2){
    		char = "0"+char;
    	}
    	str=str+char+" ";
    	// str+=char;
    }
	return str;
}

###解析数据 因为传递过来的数据的具体内容不可预知,因此需要加一个固定格式的数据头来定义具体的内容信息 在将传过来的二进制数据收取完成后,通过指定的格式来拆封各个数据,通过头的type字段来判断传递的数据的数据格式,按照固定的位数来确认具体解析方法。 如:

export function receiveMessage(message) {
let dv = new dataview(message.data);
let length = dv.pop32();
let timestamp = dv.popFloat64();
let type = dv.pop8();
let body = "Sorry!!!I can't analyis!!!";
switch (type) {
	case 212:
		body = userIdToMes(dv);
		break;
	case 6:
		body = fillConnectToMes(dv);
		break;
	case 7:
		// case 12 :
		body = groundToMes(dv);
		gamemodel.collisionCache = body.collisionSocketInfosArray;
		writeTobackendControlData(body.displacementInfoArray);
		writeNewBallInf(body.newBallsInfoArray);
		break;
	case 9:
		body = connectToMes(dv);
		break;
	case 10:
		body = specialToMes(dv);
		break;
	case 11:
		body = overToMes(dv);
		break;
}

###解析数据完成后操作

####场地信息 解析场地信息后,将collision直接写入到gamemodel,从gamemodel中更新newBall和displacement信息

#####newBall更新gamemodel 在写入newBall的同时,根基roldId给每个ball分配相应的skinId

function writeNewBallInf(newBall) {
let backend = gamemodel.data.backendControlData;
let bullet = gamemodel.resourceRecord.bulletTable;
let airPlane = gamemodel.resourceRecord.airPlaneTable;
for (let i in newBall) {
	let roleId = newBall[i].roleId;
	let skinId = null;
	if (newBall[i].ballType === CommonConstant.AIRPLANE) {
		skinId = airPlane[roleId]["skinId"];
	} else {
		skinId = bullet[roleId]["skinId"];
	}
	newBall[i]["skinId"] = skinId;
	if (newBall[i].userId == 0) {
		backend.food.push(newBall[i]);
	} else {
		if (newBall[i].id == 0) {
			backend.airPlane.push(newBall[i]);
		} else {
			backend.bullet.push(newBall[i]);
		}
	}
}

}

#####displacement更新gamemodel 将displacement的信息由array转化为json,然后将gamemodel的airplace与bullet去除,遍历,通过userId与id在json中查找元素,若不存在则从gamemodel删除,存在则更新。

并且为了解决初始化时没有newBall,因此第一次应将displacement中的信息加入到newBall中。

function arrayToJson(arr) {
if( /\[(\.*:{.*\})*\]/.test( JSON.stringify(arr) ) ){
console.error(arr)
console.error("is illegal!!");
return undefined;
}
let json = {};
for (let i in arr) {
	let userId = arr[i]["userId"];
	let id = arr[i]["id"];
	if (json[userId] === undefined)
		json[userId] = {};
	json[userId][id] = arr[i];

}
return json;
}  

通过正则表达式来判断是否正确,然后将array转换用userId和id作为key的json

function writeTobackendControlData(message) {
if (times++ === 0) {
	writeNewBallInf(message);
	return;
}
message = arrayToJson(message);
let backend = gamemodel.data.backendControlData;
let airPlane = backend.airPlane;
let bullet = backend.bullet;
let disappear = gamemodel.disappearCache;
// let block = backend.block;
backend.airPlane = airPlane.filter((ap) => {
	let userId = ap.userId;
	if (message[userId] != undefined && message[userId][0] != undefined) {
		Object.assign(ap, message[userId][0]);
		return true;
	}

	disappear.push(ap);
	return false;
});

backend.bullet = bullet.filter((b) => {
	let userId = b.userId;
	let id = b.id;
	if (message[userId] != undefined && message[userId][id] != undefined) {
		Object.assign(b, message[userId][id]);
		return true;
	}

	disappear.push(b);
	return false;
});

}

同时需要注意的是,应为用in遍历,所有不能直接更新gamemodel的整个数组,用filter来过去数组,完成所需的任务。不可用assign更新整个数组。

####登陆信息 解析成功以后,向airPlace中写入获得的userId,并将socket状态改为申请登陆(1)

####成功登陆 解析成功以后,将socket状态改为成功登陆(2)

为防止在获得id或者登陆成功之前进行下一步骤,因此添加了状态量

####错误信息 解析成功以后,将二进制转化为文字并输出

function specialToMes(dv) {
let length = dv.pop8();
let body = "";
for (let i = 0; i < length; i++) {
	let letter = String.fromCodePoint(dv.pop8());
	body = body + letter;
}
return body;

}

String.fromCodePoint是es6的新方法

###发送信息 获取要发送的信息后,根据不同的信息,准备不同的type,并计算对应的信息长度,然后根据不同的type将信息写入dataview

####socket send函数封装 为了确保socket send时在连接状态,因此封装了send函数

sendMessage(dv, times = 0) {
	// console.log(this.ws.readyState);
	if (this.ws.readyState === 1) {
		this.ws.send(dv);
		return true;
	} else {
		if (times == 10) {
			return false;
		}
		let that = this;
		window.setTimeout(function() {
			that.sendMessage(dv, ++times);
		}, 200);
	}
}

####写入头数据

#####场地信息

export function playgroundInfoAnalyis() {
let socketCache = gamemodel.socketCache;
let newBallsInfoArray = socketCache.newBallInformation;
socketCache.newBallInformation = [];
let lengthOfNewBallsInfos = newBallsInfoArray.length;

let balls = gamemodel.data.engineControlData;
let displacementInfoArray = [];
Array.prototype.push.apply(displacementInfoArray, balls.bullet);
if (balls.airPlane != undefined)
	displacementInfoArray.push(balls.airPlane);
let lengthOfDisplacementInfos = displacementInfoArray.length;
if (typeof(socketCache.damageInformation) == "undefined")
	socketCache.damageInformation == [];
let collisionSocketInfoArray = socketCache.damageInformation;
socketCache.damageInformation = [];
let lengthOfCollisionSocketInfos = collisionSocketInfoArray.length;

let disappearInfoArray = socketCache.disapperBulletInformation;
socketCache.disapperBulletInformation = [];
let lengthOfDisappearInfos = disappearInfoArray.length;
let messageBody = {
	newBallsInfos: {
		length: lengthOfNewBallsInfos,
		content: newBallsInfoArray
	},
	displacementInfos: {
		length: lengthOfDisplacementInfos,
		content: displacementInfoArray
	},
	collisionSocketInfos: {
		length: lengthOfCollisionSocketInfos,
		content: collisionSocketInfoArray
	},
	disappperInfos: {
		length: lengthOfDisappearInfos,
		content: disappearInfoArray
	}
}
let length = 32 + 32 + 32 + 32 + calculcateBallsLength(newBallsInfoArray) + calculcateBallsLength(displacementInfoArray) + lengthOfCollisionSocketInfos * 128 + lengthOfDisappearInfos * 16;

let messageLength = (32 + 64 + 8 + length) / 8;
let message = {
	length: messageLength,
	timestamp: new Date().getTime(),
	type: 12,
	body: messageBody
}
return fillDv(message);

}

需要注意的是,在读的过程中读完cache需要重新初始化,并且每个ball的长度不一定,需要通过name的长度计算。

#####计算ball长度

function calculcateBallsLength(balls) {
balls = balls.filter((e) => typeof(e) != "undefined" && !(e === null));
let length = balls.length;
let totalLength = 0;
for (let i = 0; i < length; i++) {
	let nameLength = balls[i].name.length;
	totalLength = totalLength + 224 + nameLength * 8;
}
return totalLength;

}

#####登陆信息

export function loginAnalyis(roomNumber) {
let messageBody = {
	userId: receiver.userId,
	roomNumber: roomNumber,
}
let messageLength = (32 + 64 + 8 + 32 + 32) / 8;
let message = {
	length: messageLength,
	timestamp: new Date().getTime(),
	type: 9,
	body: messageBody
}
return fillDv(message);

}

####写入头文件完成之后操作 通过不同type对应的model来填写dataview 如:

#####写入newBall

function fillBallArrayToDv(dv, content) {
let length = content.length;
for (let i = 0; i < length; i++) {
	dv.push32(content[i].camp);
	dv.push32(content[i].userId);
	dv.push16(content[i].id);
	dv.push8(content[i].name.length);
	let name = analyisUnnumber(content[i].name);
	for (let i in name) {
		dv.push8(name[i]);
	}
	dv.push8(content[i].ballType);
	dv.push8(content[i].hp);
	dv.push8(content[i].damage);
	dv.push8(content[i].roleId);
	dv.push16(content[i].special);
	dv.push16(content[i].radius || 15);
	dv.pushFloat32(content[i].attackDir);
	dv.push8(content[i].status);
	dv.push16(content[i].locationCurrent.x);
	dv.push16(content[i].locationCurrent.y);
}

}

#####将非数字转化为数字

function analyisUnnumber(obj) {
//get a array for name coding by Unicode
let getObj = obj.split("").map((e) => e.codePointAt(0));
//you can get name by getObj.map( (e)=>fromCodePoint(e) )
return getObj;
}