//server 异步服务端 import crypt; import crypt.bin; import wsock.tcp.asynHttpServer; namespace web.socket; class server{ ctor(){ this.heartbeatInterval = 1800; this.heartbeatData = ""; this.heartbeatType = 9; this.httpServer = ..wsock.tcp.asynHttpServer(); this.httpServer.keepAliveTimeout = -1; this._form = this.httpServer._form; this.httpServer.onRead = function(hSocket,err){ var client = this.httpServer.client(hSocket); if(err){ if( this.onError ) this.onError( hSocket,..wsock.err.lasterr(err) ); client.shutdown(); return; }; if( client.webSocketUpgraded ){ this.onWebSocketRead(client); } else { this.httpServer.onHttpRead(client); } } this.httpServer.onUpgradeProtocol = function(client,request,response){ if( request.headers["sec-websocket-version"] !="13" ) return response.errorStatus(400); if( request.headers["upgrade"] !="websocket" ) return response.errorStatus(400); var secKey = request.headers["sec-websocket-key"]; if(!secKey) return response.errorStatus(400); var protocol = request.headers["sec-websocket-protocol"]; response.headers["Sec-WebSocket-Protocol"] = protocol; if( this.onUpgradeToWebsocket ){ this.onUpgradeToWebsocket(client.hSocket ,request,response,protocol,request.headers.origin); if(response.closed) return; } response.keepAlive = true; response.headers["Upgrade"] = "websocket"; response.headers["Connection"] = "Upgrade"; response.headers["Sec-WebSocket-Accept"] = getSecAccept(secKey); response.headers["Content-Length"] = "0"; response.status = "101 Switching Protocols"; response.close(true); client.webSocketUpgraded = true; client.cacheFragment = {}; client.wsReadyState = 1; client.dontClearKeepAliveTimeout = true; if( this.onOpen ) this.onOpen( client.hSocket ); } this.httpServer.onClose = function(hSocket,err){ var client = this.httpServer.client(hSocket); if( client ){ if( !client.webSocketUpgraded ) return; client.wsReadyState = 3; if( this.onClose ) { if(err){ this.onClose(hSocket,{ code = 1006; reason = ..wsock.err.lasterr(err); } ); } else { this.onClose(hSocket,{ code = client.webSocketCloseCode : 1000; reason = client.webSocketCloseReason } ); } } client.webSocketCloseCode = null; client.webSocketCloseReason = null; client.close(); } else { if(err){ if( this.onError ) this.onError( hSocket,..wsock.err.lasterr(err) ); }; } } this.httpServer._beforeHttpServerStop = function(){ if(this.httpServer.heartbeatTimerId ) this.httpServer._form.clearInterval(this.httpServer.heartbeatTimerId); if( this.beforeStop ){ this.beforeStop(); }; if( this._beforeStop ){ this._beforeStop(); }; } this.httpServer._onClientClosed = function(hSocket){ if(this._onClientClosed) this._onClientClosed(hSocket); if(this.onClientClosed) this.onClientClosed(hSocket); } }; client = function(hSocket){ return this.httpServer.client(hSocket); }; close = function(hSocket,code,reason){ var client = this.httpServer.client(hSocket); if(client){ if( ( client.wsReadyState !=3 ) && ( client.wsReadyState !=2 ) ){ if( code && reason ){ this.sendData(hSocket,{ WORD code = ..raw.swap(code,"WORD"); BYTE reason[] = reason; },8); } else{ this.sendData(hSocket,"",8); }; client.wsReadyState = 2; client.shutdown(); } } }; isClosed = function(hSocket){ var c = this.httpServer.client(hSocket); if(!c) return true; if( !c.hSocket ) return true; if( c.wsReadyState == 3 ) return true; if( c.readyState == 3 ) return true; }; isConnected = function(hSocket){ var c = this.httpServer.client(hSocket); if(!c) return false; if( !c.hSocket ) return false; if( c.wsReadyState == 1 ) return true; }; _beginTranslateMessage = function(hSocket,msg){ if( !this._translateMessage || (! this._translateMessage(hSocket,msg) ) ){ if( this.onMessage ) { this.onMessage(hSocket,msg); }; } }; onWebSocketRead = function(client){ var msg = this._beginRecvMessage(client); if(!msg){ return; }; //control frames if( msg.type & 2#1000 ){ if( msg.type == 8 ){//close if(#msg.data>=2){ client.webSocketCloseCode = ..raw.swap( ..raw.convert(msg.data,{WORD code}).code ); client.webSocketCloseReason = ..raw.tostring(msg.data,3); } else { client.webSocketCloseCode = 1005; client.webSocketCloseReason = null; } client.shutdown(); } elseif( msg.type == 9){//ping //this.sendData(client.hSocket, msg.data,10); } elseif( msg.type == 10){//pong } return; } if( msg.fin/*final fragment*/ ){ if( msg.type ){ if( msg.type == 1 && #msg.data ) msg.data = ..raw.tostring(msg.data); this._beginTranslateMessage(client.hSocket,msg); } else { if( this.onFragment ){ this.onFragment(client.hSocket,msg); } else { var cacheFragment = client.cacheFragment; if( type(cacheFragment) != "table" )return; ..table.push(cacheFragment.data,msg.data); cacheFragment.length = cacheFragment.length + #(msg.data); cacheFragment.data = ..raw.join(cacheFragment.data); if( cacheFragment.type == 1 ) cacheFragment.data = ..raw.tostring(cacheFragment.data); this._beginTranslateMessage(client.hSocket,cacheFragment); } } } else { if( msg.type ){ if( this.onFragment ){ this.onFragment(client.hSocket,msg); } else { client.cacheFragment = msg; client.cacheFragment.data = {msg.data} client.cacheFragment.length = #(msg.data); } } else { if( this.onFragment ){ this.onFragment(client.hSocket,msg); } else { ..table.push(client.cacheFragment.data,msg.data); client.cacheFragment.length = client.cacheFragment.length + #(msg.data); } } } }; send = function(hSocket,data){ var t = type(data); if( t = type.string ) return this.sendData(hSocket,data,1); if( t = type.buffer ) return this.sendData(hSocket,data,2); if( t[["_struct"]] ) return this.sendData(hSocket,..raw.buffer(data),2); } publish = function(data){ var cs = this.httpServer.clients; for(h,c in cs){ if(c.wsReadyState == 1) this.send(h,data); } }; clientCount = function(){ var r = 0; var cs = this.httpServer.clients; for(h,c in cs){ if(c.wsReadyState) r++; } return r; }; sendData = function(hSocket,data,opcode = 1,fin=1,rsv1 = 0,rsv2 = 0,rsv3 = 0){ var client = this.httpServer.client(hSocket); if(!client) { return; }; if(client.wsReadyState != 1) { if( this.onError ) this.onError(hSocket, '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] = len; } elseif(len < (1 << 16) ) { buf[2] = 126; ..raw.convert({WORD v = ..raw.swap(len,"WORD"); },buf,2); w = 4; } else { buf[2] = 127; ..raw.convert({LONG v=..math.size64(len).swap(); },buf,2); w = 10; } ..raw.concat(buf,data,w); if(!client.writeBuffer(buf,w + len)){ if( this.onError ) this.onError( hSocket,..wsock.err.lasterr() ); client.shutdown(); return false; } return true; }; _endRecvMessage4 = function(client){ var len = client.wsRecvMessage.dataSize; if(len){ var buf = ..raw.buffer(len); if( ! client.readBuffer(buf) ){ return; } var maskKey = client.wsRecvMessage.maskKey; if( maskKey ){ for(i=1;len;1) buf[i] = buf[i] ^ maskKey[ i% 4 : 4 ]; } client.wsRecvMessage.data = buf; } else { client.wsRecvMessage.data = ""; } var msg = client.wsRecvMessage; client.wsRecvMessage = null; return msg; }; _recvMessageMaskKey3 = function(client){ var msg = client.wsRecvMessage; //上次的消息还没有接收完整,继续尝试 if( msg.maskKey ) return this._endRecvMessage4(client); if( msg.mask ){ msg.maskKey = client.read(4); if(!msg.maskKey) return; } return this._endRecvMessage4(client); } _recvMessageDataSize2 = function(client){ var msg = client.wsRecvMessage; //上次的消息还没有接收完整,继续尝试 if( msg.dataSize!==null ) return this._recvMessageMaskKey3(client); var len = msg.len; if (len == 126) { len = client.read({WORD v}); if(!len) return; msg.dataSize = ..raw.swap(len.v,"WORD"); } elseif(len == 127) { len = client.read({LONG size64 = ..math.size64() }); if(!len) return; msg.dataSize = tonumber( len.size64.swap() ); } else { msg.dataSize = len; } return this._recvMessageMaskKey3(client); } _beginRecvMessage = function(client){ //上次的消息还没有接收完整,继续尝试 if( client.wsRecvMessage ) return this._recvMessageDataSize2(client); var msg = {}; var h = client.read(2); if(!h) { return; //异止套接字读取数据不完整会自动退回缓冲区 } var h1,h2 = h[1],h[2]; client.wsRecvMessage = { 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(client); } getUrl = function(path,netIp){ var ip,port = this.httpServer.getLocalIp(); if(ip=="0.0.0.0"){ ip = netIp? ..wsock.tcp.client.getLocalIp() : "localhost"; } var url = ..string.format("ws://%s:%d",ip,port ); if( path ) { path = ..string.replace(path,"\\","/"); if(path[1]=='/'#) path = ..string.right(path,-2); url = url + "/" + path; }; return url; }; getLocalIp = function(){ return this.httpServer.getLocalIp(); }; getRemoteIp = function(hSocket){ return this.httpServer.getRemoteIp(hSocket); }; start = function(ip,port,backlog){ if( this.httpServer.start(ip,port,backlog) ){ if( this.heartbeatInterval > 0 ){ var d,t = this.heartbeatData,this.heartbeatType; if(this.httpServer.heartbeatTimerId ) this.httpServer._form.clearInterval(this.httpServer.heartbeatTimerId) this.httpServer.heartbeatTimerId = this.httpServer._form.setInterval( function(hwnd,msg,id,tick){ var now = tonumber(..time()); for(hSocket,client in this.httpServer.clients){ if(!client.webSocketUpgraded ) continue; if( now - client.lastActiveTime > this.heartbeatInterval ){ if(!this.sendData(hSocket,d,t)){ if( this.onError ) this.onError( hSocket,"Heartbeat failed" ); client.shutdown(); } } } },this.heartbeatInterval * 1000 ) } return true; } }; stop = function(){ return this.httpServer.stop(); }; } namespace server{ 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.server = 支持单线程异步的WebSocket服务端\n可直接在界面线程中使用,不会阻塞界面,不需要创建多线程\n支持服务端心跳(Ping/Pong帧),客户端单向心跳(Pong帧)机制,\n可调用close函数断线,并可调用connect函数实现重析连接服务器 web.socket.server() = !stdwebsocketserver. web.socket.server.sha1(__) = 使用sha1算法取哈希值,并使用Base64编码为普通文本 web.socket.server.getSecKey() = 获取WebSocket客户端密钥 web.socket.server.getSecAccept(__) = 获取WebSocket客户端配对密钥,\n参数指定服务端HTTP头中sec-websocket-accept返回的值 end intellisense**/ /**intellisense(!stdwebsocketserver) getLocalIp() = 返回当前绑定的 IP,端口号 getRemoteIp(hSocket) = 返回客户端IP地址,端口,\n@hSocket 参数指定客户端连接套接字句柄 start(.(IP,端口,请求队列大小) = 启动单线程异步 WebSocket 服务端,成功返回 true,失败返回 null,\n\n如果不写IP,则默认设为"0.0.0.0"也即监听本机所有IP,访问此服务端也不限制IP\n限制仅本机可以访问建议写127.0.0.1\n\n端口为0或省略则自动查找1025以后的空闲端口\n注意0-1023为系统通用服务保留端口,\n1024-49151为用户服务端口,其中大约%9已由IANA注册分配\n49152-65535为私有或临时端口 stop() = WebSocket服务端 send(hSocket,__) = 发送数据,支持字符串或 buffer、结构体\n字符串作为UTF8文本类型发送,其他以二进制类型发送,\n@hSocket 参数指定客户端连接套接字句柄,\n成功返回true publish(__) = 发送数据给所有的客户端,\n支持字符串或 buffer、结构体\n字符串作为UTF8文本类型发送,其他以二进制类型发送,\n成功返回true sendData(.(hSocket,data,opcode,fin,rsv1,rsv2,rsv3) = 发送WebSocket数据包\n参数@1支持支持字符串或 buffer、结构体\n除参数@1以外,所以参数可选\n一般应当调用send函数,而不是调用sendData函数\n\n如果一定要使用这个函数,请阅读此函数源码,以及 WebSocket 协议相关说明 close(.(hSocket,code,reason) = 关闭连接,\n@hSocket 参数指定客户端连接套接字句柄 client(.(hSocket) = 使用 @hSocket 参数指定的客户端连接套接字句柄\n获取 wsock.tcp.client 对象 isClosed(.(hSocket) = 连接是否已关闭,\n@hSocket 参数指定客户端连接套接字句柄 isConnected(.(hSocket) = 是否已连接并准备就绪可以发送数据,\n@hSocket 参数指定客户端连接套接字句柄 client() = !stdtcpaclient. clientCount() = 当前WebSocket客户端数目\n包含正在连接或正在断开连接的客户端\n不含已关闭的客户端 httpServer = 单线程异步 HTTP服务端,wsock.tcp.asynHttpServer对象\n浏览器组件发起异步 HTTP 请求支持 wsock.tcp.asynHttpServer。\n请不要用 inet.http 等阻塞请求同一线程创建的 asynHttpServer,\n这会导致 asynHttpServer 没有机会响应请求而导致死锁,\n如果确有这样的需求,可创建线程发起请求,\n或改用基于多线程的 wsock.tcp.simpleHttpServe,\n!stdasynHttpServer. onUpgradeToWebsocket = @.onUpgradeToWebsocket = function(hSocket,request,response,protocol,origin){\n __/*\n用户发送HTTP请求切换到WebSocket协议,\n可以在这里修改HTTP响应头中Sec-WebSocket-Protocol的值\n\n如果要阻止用户切换到WebSocket,用 response.close() 关闭请求,\n或调用 response.errorStatus() 函数返回错误代码即可,\n否则退出此函数后会继续切换到WebSocket协议。\n\n参数说明:\nhSocket为客户端套接字句柄\nrequest为HTTP请求对象\nresponse为HTTP应答对象\nprotocol为Sec-WebSocket-Protocol请求头的值*/\n} _onClientClosed = @._onClientClosed = function(hSocket){ __/*客户端连接已经关闭时触发,\nhSocket 参数为客户端连接套接字句柄,\n此函数在连接断开后一定会被触发,\n此回调函数是标准库保留接口,\n用户不应使用此回调*/ } onClientClosed = @.onClientClosed = function(hSocket){ __/*客户端连接已经关闭时触发,\nhSocket 参数为客户端连接套接字句柄,\n此函数在连接断开后一定会被触发*/ } onOpen = @.onOpen = function(hSocket){ __/*客户端已切换到WebSocket服务,\nhSocket 参数为客户端连接套接字句柄*/ } onClose = @.onClose = function(hSocket,err){ __/*连接被关闭,\nhSocket 参数为客户端连接套接字句柄,\nerr.code 为错误代码 err.reason 为错误原因\n客户端关闭连接并不保证一定会触发此消息\n但onClientClosed事件在关闭后一定会被触发*/ } onError = @.onError = function(hSocket,err){ __/*发生错误,\nhSocket 参数指定客户端连接套接字句柄,\nerr为错误信息*/ } onMessage = @.onMessage = function(hSocket,msg){ __/*收到服务端数据,\nhSocket 参数指定客户端连接套接字句柄,\nmsg.type 为 1 时 msg.data 为文本,\n否则 msg.data 为字节串(buffer 类型)*/ } onFragment = @.onFragment = function(hSocket,msg){ __/*收到分片数据,\nhSocket 参数指定客户端连接套接字句柄,\n第一个数据包使用msg.type指明类型,参考WebSocket协议规范\n后续数据包msg.type为0,最后一个数据包msg.fin为1\n\n如果不指定这个回调函数,则自动并接分片数据后触发onMessage事件*/ } beforeStop = @.beforeStop = function(){ __/*服务端关闭以前触发*/ } _beforeStop = @._beforeStop = function(){ __/*服务端关闭以前触发\n此回调函数是标准库保留接口\n用户不应使用此回调*/ } _translateMessage = 此回调函数的参数与onMessage相同,\n如果定义了这个回调函数,\n那么此函数将在调用onMessage之前被调用,\n如果此回调返回true则不再触发onMessage,\n这个函数提供了一个机会用于自动处理客户端消息\n,为其他需要扩展web.socket.server功能的库所预留,\n一旦定义将不能修改 heartbeatInterval = 服务端主动发送心跳Ping空帧间隔,默认为30分钟\n设为-1时禁用客户端心跳,一般不建议禁用\n这个值修改以后,只能在下次调用start函数才会生效 heartbeatData = 心跳帧发送的数据,默认为空数据\n这个值修改以后,只能在下次调用connect函数才会生效 heartbeatType = 心跳帧发送的的帧类型,\n默认为9,也就是Ping帧\n这个值修改以后,只能在下次调用start函数才会生效 getUrl(__) = 获了服务端网址,可选指定目录或文件路径\n注意参数第一个字符不需要指定斜杠\n如果参数@2为true,IP "0.0.0.0"替换为上网卡IP而不是localhost\n如果服务器启动失败不返回任何值 end intellisense**/