(转)clientX 异步客户端,支持SOCKS5

By admin at 2018-12-06 • 0人收藏 • 1243人看过

整理自: aar群 感谢 ҉k҉o҉m҉(znkee)提供

以下代码未经测试!

//clientX 异步客户端,支持SOCKS5

import crypt;
import crypt.bin; 
import inet.url;
import web;
import wsock.tcp.asynClient;

namespace web.socket;

class clientX{
	ctor(){
		this.readyState = 0;
		this.secKey = getSecKey();
		this.heartbeatInterval = 30;
		this.heartbeatData = "";
		this.heartbeatType = 0xA;
		this.userAgent = "Mozilla/5.0";
		..table.gc(this,"close");
	};
	close = function(code, reason){
		if(this.socket){
			if( ( this.readyState !=3 ) && ( this.readyState !=2 ) ){
				
				if( code && reason ){
					this.sendData({
						WORD code = ..raw.swap(code,"WORD");
						BYTE reason[] = reason;
					},8);
				} 
				else{
					this.sendData("",8);
				};
				
				this.readyState = 2;
			}
			this.socket.shutdown();
		}
	};
	_upgradeRequest = function(){
		var host = this.uri.host;
		if( this.uri.port!=80 ) host =  host +":"+ this.uri.port;
		
		if(!this.originUrl ){
			this.originUrl = "http://" + host +"/"; 
		}
		
		var headers = ..web.joinHeaders({
			["Connection"] = "Upgrade";
			["Host"] = host;
			["User-Agent"] = this.userAgent;
			["Pragma"] = "no-cache";
			["Accept"] = "*/*;";
			["Upgrade"] = "websocket";
			["Connection"] = "Upgrade"; 
			["Origin"] =  this.originUrl;
			["Sec-WebSocket-Version"] = "13";
			["Sec-WebSocket-Protocol"]  = this.protocol : "chat"; 
			["Sec-WebSocket-Key"] = this.secKey; 
			["Sec-WebSocket-Origin"] = this.url;
    		["Cache-Control"] = "no-cache";
    		["Accept-Language"] = "zh-CN,zh;q=0.9";
    		["Sec-WebSocket-Extensions"] = "permessage-deflate; client_max_window_bits";
		},this.headers)
				
		var reqPath = this.url : "/";
		if( this.uri.extraInfo ) reqPath = reqPath + this.uri.extraInfo;
		
		var ret  = this.socket.write('GET '+ reqPath  +' HTTP/1.1\r\n'+ headers +'\r\n')
	
		if(!ret){
			if( this.onError ) this.onError( 'Handshake failure'); 
			this.close();
			return false;
		}
		return true; 
	}
	
	connect = function(url,proxy){
		if(proxy){
			var tmp=..string.split(proxy,':')
			if(#tmp==2){
				this.proxy = {
					host = tmp[1];
					port = tonumber(tmp[2])
				};
				this.connectStep=1;	
			}
		}
		
		if(!url) url = this.url;
		if(!url) error("请指定连接网址,例如 ws://localhost:7511");
		
		if( this.isConnected() ) {
			this.close();
			..win.delay(100);
		};
		 
		this.readyState = 0;
		this.socket = ..wsock.tcp.asynClient(); 
		this.socket.bufferSize = 1024*10;
		
		this.webSocketCloseCode = null;	
		this.webSocketCloseReason = null;
				
		this.uri = ..inet.url.split(url);
		this.port = this.port:80;
		if( ..string.cmp(this.uri.scheme,"ws") !=0 ){
			error("错误的URL协议:" + this.uri.scheme,2);
		}
		this.url = url;
		
		this.socket.onConnect = function(err){
			if(err){
				if( this.onError ) this.onError( ..wsock.err.lasterr(err) ); 
				this.close();
				return; 
			};
			var ret;
			if(this.proxy){
				var tab = {
					_struct_aligned = 1;
					BYTE ver=5;
					BYTE num=1;
					BYTE method=0;
				}
				var ret  = this.socket.write(tab)
				if(!ret){
					if( this.onError ) this.onError( 'SOCKS5 认证失败 1'); 
					this.close();
					return;
				}
			}else {
				if(!this._upgradeRequest()){
					return ; 
				}
			}
		}
		
		if(this.proxy){
			this.socket.onRead = this._onSOCKS5Data;
		}else {
			this.socket.onRead = this._onReadHttpHeader;
		}
		
		this.socket.onClose = function(err){
			if( this.socket == owner ){
				this.readyState = 2;
				
				if( this.onClose ) {
					if(err){
						this.onClose( 
							code = 1006;
							reason = ..wsock.err.lasterr(err);
						); 
					}
					else {
						this.onClose( 
							code = this.webSocketCloseCode : 1000;
							reason = this.webSocketCloseReason 
						);
					}
				}
				
				if(( this.socket == owner )&&(this.readyState == 2)){
					this.webSocketCloseCode = null;	
					this.webSocketCloseReason = null;
					this.readyState = 3; 
					this.socket = null;	
				}
			}
			
			/*
			这里不能用this.socket.close(),
			因为这个套接字关闭事件被触发时,可能已经创建新的this.socket了
			*/
			owner.close();
		}
		if(this.proxy){
			return this.socket.connect(this.proxy.host, this.proxy.port);
		}else {
			return this.socket.connect(this.uri.host, this.uri.port:80);
		}	
	};
	isClosed = function(){
		if( this.readyState == 3 ) return true;
		if( !this.hSocket ) return true;
	};
	isConnected = function(){
		return ( this.readyState == 1 ) && ( owner.hSocket );
	};
	waitForConnected = function(hwnd,timeout){ 
		..win.delay(1000);
		return ..win.wait( 
			function(){
				if( this.readyState > 1 ) return false;
				if( this.readyState == 1 ) return true;
			},hwnd,timeout,1000
		);	 
	}; 
	_beginTranslateMessage = function(msg){ 
		if( this.onMessage ) { this.onMessage(msg); };	
		if( this._translateMessage ) { this._translateMessage(msg); };
	};
	_onReadMessage = function(err){
		if(err){
			if( this.onError ) this.onError( ..wsock.err.lasterr(err) );
			this.close();
			return;	
		}; 
		
		var msg = this._beginRecvMessage();
		if(!msg){ 
			return;
		};

		//control frames
		if( msg.type & 2#1000 ){
			if( msg.type == 8 ){//close
				if(#msg.data>=2){
					this.webSocketCloseCode = ..raw.swap( ..raw.convert(msg.data,{WORD code}).code );
					this.webSocketCloseReason = ..raw.tostring(msg.data,3);
				}
				else {
					this.webSocketCloseCode = 1005;	
					this.webSocketCloseReason = null;
				}
				
            	this.close();
        	}
        	elseif( msg.type == 9){//ping
            	this.sendData(msg.data,10);
        	}
        	elseif( msg.type == 10){//pong
        	}
			return;
		}
		
		if( msg.fin ){
			if( msg.type ){
				if( msg.type == 1 ) msg.data = ..raw.tostring(msg.data);
				this._beginTranslateMessage(msg);
			}
			else {
				if( this.onFragment ){
					this.onFragment(msg);
				}
				else {
					..table.push(this.cacheFragment.data,msg.data);
					this.cacheFragment.length = this.cacheFragment.length + #(msg.data);
					
					if( this.cacheFragment.type == 1 ) this.cacheFragment.data = ..string.join(this.cacheFragment.data);
					else {
						var len = 0;
						var buffer = ..raw.buffer(this.cacheFragment.length); 
						var data = this.cacheFragment.data;
						for(i=1;#data;1){
							..raw.concat(buffer,data[i],len)
							len = len + #data[i];
						}
						this.cacheFragment.data = buffer;
					}
					this._beginTranslateMessage(this.cacheFragment);
				}
			}
		}
		else {
			if( msg.type ){
				if( this.onFragment ){
					this.onFragment(msg);
				}	
				else {
					this.cacheFragment = msg;
					this.cacheFragment.data = {msg.data}
					this.cacheFragment.length = #(msg.data);
				}
				
			}
			else {
				if( this.onFragment ){
					this.onFragment(msg);
				}
				else {
					..table.push(this.cacheFragment.data,msg.data);
					this.cacheFragment.length = this.cacheFragment.length + #(msg.data);
				}
			}
		}
	};
	_onSOCKS5Data = function(err){
		if(err){
			if( this.onError ) this.onError( ..wsock.err.lasterr(err) );
			this.close();
			return;	
		};
		select(this.connectStep) {
			case 1 {
				this.connectStep++
				var tab = this.socket.read({
					_struct_aligned = 1;
					BYTE ver;
					BYTE method;
				})
				//清空缓存
				this.socket.readAll()
				if(tab.ver==5 and tab.method==0){
					//如果是域名
					var host = this.uri.host
					var port = this.uri.port
					if(..string.match(host,"\a")){
						tab = {
							_struct_aligned = 1;
							BYTE ver=5;
							BYTE cmd=1;
							BYTE resverd=0;
							BYTE type=3;
							BYTE len=#host;
							BYTE domain[]=host;
							WORD port=..raw.swap(port,"WORD");
						}	
					}else {
					//ip
						tab = {
							_struct_aligned = 1;
							BYTE ver=5;
							BYTE cmd=1;
							BYTE resverd=0;
							BYTE type=1;
							INT ip=..raw.swap(..wsock.htonl(..wsock.inet_addr(host)));
							WORD port=..raw.swap(port,"WORD");
						}
					}
					var ret  = this.socket.write(tab)
					if(!ret){
						if( this.onError ) this.onError( 'SOCKS5 认证失败 2'); 
						this.close();
					}
				}
			}
			case 2 {
				var tab = this.socket.read({
					_struct_aligned = 1;
					BYTE ver;
					BYTE code;
				})
				//清空缓存
				this.socket.readAll()
				if(tab.code==0){
					
					//重新绑定onRead
					this.socket.onRead = this._onReadHttpHeader;
					//发送协议升级
					this._upgradeRequest()	
				}else {
					if( this.onError ) this.onError( 'SOCKS5 认证失败 3'); 
				}
			}
		}
	}
	
	_onReadHttpHeader = function(err){
		if(err){
			if( this.onError ) this.onError( ..wsock.err.lasterr(err) );
			this.close();
			return;	
		};

		var responseHead = this.socket.readTo('\r\n\r\n');//HTTP头以两个回车换行结束
		if(!responseHead){ 
			return;	
		};
	
		var httpHeaders = ..string.split(responseHead,'<\r\n>');
		var statusLine = httpHeaders[1] ? ..string.splitEx(httpHeaders[1],"\s+",3); 
		if( ..string.startWith(statusLine[1],"HTTP/",true) ){
			this.status = statusLine[2];
			if( this.status == "101" ){
				..table.shift(httpHeaders,1); 
				this.responseHeaders = {};
				for(i=#httpHeaders;1;-1){
					var h = ..string.splitEx(httpHeaders[i],"\:\s*",2); 
					h[1] = ..string.trim(h[1]); h[2] = ..string.trim(h[2]);
					this.responseHeaders[ ..string.lower(h[1])] = h[2];  
				} 

				if( this.responseHeaders["sec-websocket-accept"] != getSecAccept(this.secKey) ){
					if( this.onError ) this.onError( 'Incorrect "Sec-WebSocket-Accept" header value'); 
					this.close();
					return;
				}
				this.readyState = 1;//连接成功
				if( this.onOpen ) this.onOpen();
				this.socket.onRead = this._onReadMessage;
				
				if( this.heartbeatInterval > 0 ){
					var d,t = this.heartbeatData,this.heartbeatType;
					this.socket.heartbeatTimerId = this.socket._form.addtimer( 
						this.heartbeatInterval * 1000,function(hwnd,msg,id,tick){
							if(!this.sendData(d,t)){//单向心跳
								if( this.onError ) this.onError( "Heartbeat failed" ); 
								this.socket.shutdown();
							}
						} 
					)
				}
			}
			else {
				if( this.onError ) this.onError( 'Unexpected response code:' + this.status);
				this.close();;
			}	
		}
		else {
			if( this.onError ) this.onError( 'Invalid status line');
			this.close();;
		}
	}
	send = function(data){
		var t = type(data);
		
		if( t = type.string )
			return this.sendData(data,1);

		if( t = type.buffer )
			return this.sendData(data,2);
			
		if( t[["_struct"]] )
			return this.sendData(..raw.buffer(data),2);
	}
	sendData = function(data,opcode = 1,fin=1,mask=1,rsv1 = 0,rsv2 = 0,rsv3 = 0){ 
		if(this.readyState != 1) {
			if( this.onError ) this.onError( 'Failed to execute "send/sendData"');
			return;
		};
		
		if( data[["_struct"]] ) data = ..raw.buffer(data);
			 
		var len = #data; 
		var buf = ..raw.buffer( len + 14);
		buf[1] = (fin << 7) | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) | opcode;

   		var w  = 2;
    	if (len <= 125) {
        	buf[2] = (mask << 7) | len; 
    	}
    	elseif(len < (1 << 16) ) {
        	buf[2] = (mask << 7) | 126;
        	..raw.convert({WORD v = ..raw.swap(len,"WORD"); },buf,2);
        	w = 4;
    	} 
    	else {
        	buf[2] = (mask << 7) | 127;
        	..raw.convert({LONG v=..math.size64(len).swap(); },buf,2);
        	w = 10;
    	}
    	
    	if(mask){
        	var k = ..string.random(4);
        	..raw.concat(buf,k,w);
        	w = w + 4;
        
        	for(i=1;len;1) buf[w+i] = data[i]  ^ k[ i% 4 : 4 ];
    	}
    	else {
    		..raw.concat(buf,data,w);
    	}
    
    	if(!this.socket.writeBuffer(buf,w + len)){
    		if( this.onError ) this.onError( ..wsock.err.lasterr() );
    		this.socket.shutdown();
    		return false;
    	}
    	return true;
	};
	_endRecvMessage4 = function(){
		var len = this.recvMessage.dataSize;
		
		if(len){
        	var buf = ..raw.buffer(len);
       		if( ! this.socket.readBuffer(buf) ){ 
				return;
       		}  
       		
        	var maskKey = this.recvMessage.maskKey;
        	if( maskKey ){ 
            	for(i=1;len;1) buf[i] = buf[i]  ^ maskKey[ i% 4 : 4 ];
        	}
        	this.recvMessage.data = buf;
        }
        else {
        	this.recvMessage.data = "";
        }
        
        
        var msg = this.recvMessage;
        this.recvMessage = null;
        return msg;
	};
	_recvMessageMaskKey3 = function(){
		var msg = this.recvMessage;
		
		//上次的消息还没有接收完整,继续尝试
		if( msg.maskKey ) 
			return this._endRecvMessage4();
 
        if( msg.mask ){
            msg.maskKey = this.socket.read(4); 
            if(!msg.maskKey) return;
        }
        
        return this._endRecvMessage4();    
	}
	_recvMessageDataSize2 = function(){
		var msg = this.recvMessage;
		
		//上次的消息还没有接收完整,继续尝试
		if( msg.dataSize!==null ) 
			return this._recvMessageMaskKey3();
			
		var len = msg.len;
        if (len == 126) {
            len = this.socket.read({WORD v});
            if(!len)  return; 
            msg.dataSize = ..raw.swap(len.v,"WORD");
        }
        elseif(len == 127) {
            len = this.socket.read({LONG size64 = ..math.size64() });
            if(!len)  return; 
            
            msg.dataSize = tonumber( len.size64.swap() ); 
        }
        else {
        	msg.dataSize = len;
        }
         
        return this._recvMessageMaskKey3();     
	}
	_beginRecvMessage = function(){
		if(!this.socket) return;
		
		//上次的消息还没有接收完整,继续尝试
		if( this.recvMessage ) 
			return this._recvMessageDataSize2();
		
		var msg = {}; 
	    var h = this.socket.read(2);
	    if(!h) {
	    	return; //异止套接字读取数据不完整会自动退回缓冲区
	    }
	    
        var h1,h2 = h[1],h[2];
        this.recvMessage = {
        	type = h1 & 2#1111;
            fin = (h1 >> 7) & 1;
            rsv1 = (h1 >> 6) & 1;
            rsv2 = (h1 >> 5) & 1;
            rsv3 = (h1 >> 4) & 1;
            mask = (h2 >> 7) & 1;
            len = h2 & 2#1111111;
        } 	
        
        return this._recvMessageDataSize2();
	}
}

namespace clientX{
    import console
	sha1 = function(data){
		var cr = ..crypt();
		var hash = cr.createHash( 0x8004/*_CALG_SHA1*/,data ); 
		var data = hash.getValue();
		hash.destroy();
		cr.release();
		
		return ..crypt.bin.encodeBase64(data);
	}
	
	getSecAccept = function(data){
		return sha1(..string.trim(data)+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
	}
	
	getSecKey = function(){
		return ..crypt.bin.encodeBase64(..string.random(16) );
	}
}

/**intellisense()
web.socket.clientX = 支持单线程异步的WebSocket客户端\n可直接在界面线程中使用,支持SOCKS5代理,不会阻塞界面,不需要创建多线程\n支持服务端心跳(Ping/Pong帧),客户端单向心跳(Pong帧)机制,\n可调用close函数断线,并可调用connect函数实现重新连接服务器
web.socket.clientX() = !stdwebsocketclientX.
web.socket.clientX.sha1(__) = 使用sha1算法取哈希值,并使用Base64编码为普通文本
web.socket.clientX.getSecKey() = 获取WebSocket客户端密钥
web.socket.clientX.getSecAccept(__) = 获取WebSocket客户端配对密钥,\n参数指定服务端HTTP头中sec-websocket-accept返回的值
end intellisense**/

/**intellisense(!stdwebsocketclientX)
socket = 异步套接字对象\n在关闭连接状态下此属性的值为null\n应由对象自动打开或删除套接字对象,调用者不可改动此属性的值\n!stdtcpaclient.
connect("ws://__","ip:port") = 重新连接到WebSocket服务端\n参数指定WebSocket服务端网址,例如 "ws://localhost:7511"\n如果不指定参数,则获取上次调用此函数指定的网址参数,\n第二个参数是SOCKS5代理\n如果之前也没有指定网址则抛出异常
waitForConnected(.(关联窗口句柄,超时) = 等待连接到WebSocket服务端\n所有参数可选,超时以毫秒为单位,\n\n连接成功返回true,失败返回false或null
url = 上次成功连接的网址\n也可以用于指定下次连接的默认网址
close() = 关闭连接\n可选增加2个参数指定发送给服务器的关闭帧附加数据:\n参数@1为数值类型的错误代码,参数@2为字符串类型错误描述
send(__) = 发送数据,支持字符串或缓冲区(buffer)、结构体\n字符串作为UTF8文本类型发送,其他以二进制类型发送,\n成功返回true
sendData(.(data,opcode,fin,mask,rsv1,rsv2,rsv3) = 发送WebSocket数据包\n参数@1支持支持字符串或缓冲区(buffer)、结构体\n除参数@1以外,所以参数可选\n一般应当调用send函数,而不是调用sendData函数\n\n如果一定要使用这个函数,请阅读此函数源码,以及WebSocket协议相关说明
onOpen = @.onOpen = function(){
	??.send("已连接到WebSocket服务");
}
onClose = @.onClose = function(e){
	__/*连接被关闭\ne.code为错误代码e.reason为错误原因*/	
}
onError = @.onError = function(err){
	__/*发生错误,err为错误信息*/
}
onMessage = @.onMessage = function(msg){
    __/*收到服务端数据\nmsg.type为1时msg.data为文本,\n否则msg.data为字节数组(buffer类型)*/
	
}
onFragment = @.onFragment = function(msg){
   __/*收到分片数据\n第一个数据包使用msg.type指明类型,参考WebSocket协议规范\n后续数据包msg.type为0,最后一个数据包msg.fin为1\n\n如果不指定这个回调函数,则自动并接分片数据后触发onMessage事件*/	
}
_translateMessage = 此回调函数的参数与onMessage相同,\n如果定义了这个回调函数,\n那么此函数将在调用onMessage以后被调用,\n这个函数提供了一个机会用于自动处理服务器消息\n,为其他需要扩展web.socket.client功能的库所预留,\n一旦定义将不能修改
headers = 其他HTTP请求头\n值可以是文本或数组、或键值对组成的表\n请求时会调用 web.joinHeaders()函数拼接并转换HTTP头\n该函数支持的类型和格式这个属性都可以支持
responseHeaders = 服务端响应的HTTP头\n这是一个表对象,键名都已转为小写
readyState = 连接状态,\n0为等待连接,1为已连接并准备就绪,2为正在关闭,3为已关闭\n只有成功通过WebSocket协议握手以后readyState才会被置为1\n这与socket.readyState连接成功就会置为1是不同的
isClosed() = 套接字是否已关闭
isConnected() = 套接字是否已连接并准备就绪(已与服务器握手成功)
secKey = 连接密钥,不可改动
heartbeatInterval = 客户端主动发送单向心跳(默认发送Pong空帧)间隔,默认为30秒\n设为-1时禁用客户端心跳,一般不建议禁用\n这个值修改以后,只能在下次调用connect函数才会生效
heartbeatData = 单向心跳发送的数据,默认为空数据\n这个值修改以后,只能在下次调用connect函数才会生效
heartbeatType = 单向心跳发送的的帧类型,\n默认为0xA,也就是Pong帧\n这个值修改以后,只能在下次调用connect函数才会生效
originUrl = 浏览器启动WebSocket客户端的网址\n一些WebSocket服务器根据这个判断是不是允许连接,\n所以有时候设置这个很重要\n默认使用WebSocket网址,并把 前面的ws://改为http://
userAgent = 客户端应用程序代理头\n默认为"Mozilla/5.0"
protocol = 应用程序支持的协议列表,默认为"chat"
end intellisense**/


登录后方可回帖

登 录
信息栏
 私人小站

本站域名

ChengXu.XYZ

投诉联系:  popdes@126.com



快速上位机开发学习,本站主要记录了学习过程中遇到的问题和解决办法及上位机代码分享

这里主要专注于学习交流和经验分享.
纯私人站,当笔记本用的,学到哪写到哪.
如果侵权,联系 Popdes@126.com

友情链接
Aardio官方
Aardio资源网


才仁机械


网站地图SiteMap

Loading...