//asynClient 异步客户端 import wsock.tcp.client; import win.ui; namespace wsock.tcp; class asynClient{ ctor( hSocket ){ this = ..wsock.tcp.client(hSocket); this.readyState = 0; if(!hSocket){ this._form = ..win.form(); this._form.messageOnly(); this.asyncSelect(0x10/*_FD_CONNECT*/|0x1/*_FD_READ*/|0x20/*_FD_CLOSE*/|0x4/*_FD_OOB*/|0x2/*_FD_WRITE*/ ,0xACD1/*_WM_STD_SOCKET_NOTIFY*/,this._form.hwnd) this._form.wndproc ={ [0xACD1/*_WM_STD_SOCKET_NOTIFY*/] = function(hwnd,message,wParam,lParam){ var hSocket = topointer(wParam); var err,event = ::HIWORD(lParam),::LOWORD(lParam); if( hSocket != this.hSocket ) return; select(event) { case 0x10/*_FD_CONNECT*/{ if( !err) this.readyState = 1; if( this.onConnect ) this.onConnect(err); } case 0x1/*_FD_READ*/{ if( this.onReceive ) this.onReceive(err); else if( this.onRead ) { var ok,event = this.asyncSelect( this.lastSelectEvent & ~0x1/*_FD_READ*/ ); if(ok){ this.onRead(err); this.asyncSelect(event); }; }; } case 0x20/*_FD_CLOSE*/{ this.readyState = 2; if( this.onClose ) this.onClose(err); this.close(); this.readyState = 3; } case 0x2/*_FD_WRITE*/{ var sendbuffer10035 = this.sendbuffer10035; if(#sendbuffer10035){ while(#sendbuffer10035){ var task = sendbuffer10035[1]; var ret,err = this.writeBuffer(task.buffer,task.len,task.offset,task); if( !ret ) { this.sendbuffer10035 = {}; this.shutdown(); return; } if( err == 10035 ) break; ..table.shift(sendbuffer10035,1); } if( this.shuttingDown10035 !== null ){ ..wsock.shutdown(this.hSocket,this.shuttingDown10035); this.shuttingDown10035 = null; } } else{ if( this.onSend ) this.onSend(err); } } case 0x4/*_FD_OOB*/{ if( this.onOutOfBandData ) this.onOutOfBandData(err); } } } [0x2/*_WM_DESTROY*/] = function(){ this.close(); } } } this.asynTryReadTo = this.readTo; ..table.assign(this,__asynFunction__ ); } } namespace asynClient{ __asynFunction__ = { readTo = function(ends,wait){ var s,pendding = owner.asynTryReadTo(ends,wait); if(pendding){ owner.unRead(s); return; } if(!s){ owner.shutdown(); } return s; } read = function(len){ if( len === null ){ return owner.readTo('\r\n'); } if( len[["_struct"]] ){ var s = len; var l = ..raw.sizeof(s); var p,size = owner.readAlloc(l); if(p){ var result; if( size == l ) { result = ..raw.convert(p,s); } else { owner.unReadAlloc(p) return; } p = ..raw.realloc(0,p); return result; } owner.shutdown(); return; } var p,size = owner.readAlloc(len); if(p){ if( len !== null ){ len = ..math.abs(len); if( size != len ){ owner.unReadAlloc(p) if( 10035 !== ::Ws2_32.WSAGetLastError() ){ owner.shutdown(); } return; } } var s = ..raw.tostring(p,1,size); p = ..raw.realloc(0,p); return s; } owner.shutdown(); }; readBuffer = function(buffer,l){ if(!owner.hSocket) return null,"tcp客户端已断开"; if(buffer){ var m = #buffer; if( !l ) l = m; elseif( l > m ) l = m; elseif( l < 0 ) return; } var p,size = owner.readAlloc(l); if(p){ if( l!== null){ l = ..math.abs(l); if( l!= size ){ owner.unReadAlloc(p) if( 10035 !== ::Ws2_32.WSAGetLastError() ){ owner.shutdown(); } return; } }; if(!buffer) { buffer = ..raw.buffer(size,p); p = ..raw.realloc(0,p); return buffer; } else { ..raw.copy(buffer,p,size); p = ..raw.realloc(0,p); return size; } } else { if( 10035 !== ::Ws2_32.WSAGetLastError() ){ owner.shutdown(); } } } eachRead = function(remainSize){ var recvbuf,reads; var bufSize = owner.bufferSize; var hSocket = owner.hSocket; var this = owner; return function(){ if( remainSize !== null ){ if( remainSize < bufSize ){bufSize = remainSize;} if(!bufSize) return; } var reads,recvbuf = 0; while( ( reads==0 ) && this.hSocket){ reads,recvbuf = ..wsock.recv( hSocket,bufSize,bufSize,0); ..win.delay(100); } if( reads>0 ) { if( remainSize !== null ){remainSize -= reads;} if(reads0 ) { if( remainSize !== null ){remainSize -= reads;} return reads,remainSize } } }; } } /**intellisense() wsock.tcp.asynClient = TCP异步客户端\n创建监听器 wsock.tcp.asynClient(.() = 创建TCP异步客户端 wsock.tcp.asynClient(.(套接字句柄) = 绑定异步套接字返回客户端对象\n不创建事件监听器 wsock.tcp.asynClient() = !stdtcpaclient. end intellisense**/ /**intellisense(!stdtcpaclient) readyState = 套接字连接状态,\n0为等待连接,1为已连接,2为正在关闭,3为已关闭 isClosed() = 套接字是否已关闭 isConnected() = 套接字是否已连接 hSocket = 套接字句柄\n关闭对象后为空值\n此值应由对象自动维护,调用者不应修改此属性 bufferSize = 读写缓冲区大小,默认为64KB\n缓冲区如果设置的太小,会导致过于频繁的调用读写函数 readDelayInterval = 数据尚未到达时的等待时间\n此属性仅适用于界面线程异步套接字\n仅在所有read前缀的读数据函数中,读取长度为负值是有效,\neach前缀的迭代器函数也使用此属性指定的等待时间 _form = 用于处理套接字消息的窗口对象\n!winform. getSocketError() = 获取并同时清除套接字错误代码 bind(= 绑定 IP 端口。 bind(.(IP,端口) = 绑定 IP 端口。\n如果不指定 IP 则默认绑定 0.0.0.0。\n\n成功返回 true,\n失败返回 null,错误信息,错误代码。\n\n同一套接字重复绑定会返回 10022(_WSAEINVAL) 错误。\n重新绑定应当重新创建套接字。\n这就好比一张车票只能上一次车。\n不能在已经上车以后再要求车站修改车票上的上车地。 connect( = 创建连接。\n\n已关闭的套接字不能再使用,需要重连接请重新创建套接字。\n就好比用过的车票不能重复用,什么地方都有规则和限制。 connect(.(IP或域名,端口号) = 创建连接。\n也可以在参数 @1 中用一个字符串同时指定 IP 和端口号,IP 与端口号使用冒号分隔。\n异步套接字始终返回 null ,在 onOpen ,onError 回调事件中判断是否连接成功。 eachRead = @for(str,readSize,remainSize in ??.eachRead() ){ \n __/*可选指在eachRead参数中指定最大长度,\n读取长度不可指定负数\n\n迭代变量:\nstr是本迭代次读取的字符串,readSize是读取的长度, \nremainSize是剩余还没有读取的字节, \n如果限定了最大长度,remainSize为0时才表示读完所有数据, \n此函数不支持unRead送回的数据,\n异步客户端只能在onRead事件中使用\n\n如果套接字没有断开,并且出没有遇到错误,\n但是还没有数据到达,此函数会等待数据\n但不会阻塞消息循环*/\n} eachReadBuffer = @for(readSize,remainSize in ??.eachReadBuffer() ){ \n __/*可选指在eachReadBuffer参数中指定最大长度,\n读取长度不可指定负数\n\n迭代变量:\nreadSize是本次迭代读取的长度,buffer的实际长度可能大于readSize, \nremainSize是剩余还没有读取的字节, \n如果限定了最大长度,remainSize为0时才表示读完所有数据, \n此函数不支持unRead送回的数据,\n异步客户端只能在onRead事件中使用\n\n如果套接字没有断开,并且出没有遇到错误,\n但是还没有数据到达,此函数会等待数据\n但不会阻塞消息循环*/\n} readTo( = 读取直到以指定的字符串结束\n只能在onRead事件中使用\n\n此函数如果读取部分不完整数据会调用 unRead() 退回缓冲区\n读取不到任何数据会调用shutdown函数关闭连接 readTo(.('结束串') = 读取直到以指定的字符串结束,返回值不包含结束串,\n该函数每次仅读取一个字节,效率较低\n只能在onRead事件中使用 readTo(.('结束串',true) = 读取直到以指定的字符串结束,返回值不包含结束串,\n如果没有读取到数据,则循环等待,等待时继续处理界面消息\n此用法仅适用于界面线程异步套接字\n该函数每次仅读取一个字节,效率较低 read(.(读取长度) = 读取数据\n只能在onRead事件中使用\n参数可以指定长度,也可以使用接收数据的结构体作为参数\n此函数会循环读取数据,\n如果当前没有数据此函数会立即返回不会阻塞\n如果读取长度指定为负数,此函数会等待数据到达直到读取全部数据\n等待过程中不会阻塞消息循环\n不指定读取长度时表示读取一行,\n使用CRLF回车换行符分行\n\n此函数如果读取部分不完整数据会调用 unRead 函数退回缓冲区\n读取不到任何数据会调用shutdown函数关闭连接 readAll() = 读取全部数据\n只能在onRead事件中使用 writeBuffer(.(buffer,长度) = 发送数据,\n参数@1应使用 buffer 对象,\n可选使用参数2指定长度\n成功返回true readBuffer(.(buffer,读取长度) = 读取数据,返回 buffer 对象,\n省略所有参数则读取所有数据,\n参数@1可选指定一个使用 buffer 对象,\n省略读取长度时取缓冲区长度,\n\n如果参数@1指定了缓冲区,成功返回读取长度,\n否则成功返回缓冲区,\n失败返回null\n\n如果指定了长度,没有到完整的数据时,\n此函数会将数据退回缓冲区,并返回null\n如果读取出错,或没有读取到数据,此函数会自动调用shutdown关闭连接 peek(.(长度) = 读取但并不移除缓冲区的数据,返回字符串\n不指定参数则使用bufferSize指定的大小分配buffer并尝试读取 readOobByte() = 读取一个字节的紧急数据,返回字节码 readAlloc( = 循环读取数据到动态指针内,\n对于异步套接字,只能在onRead函数内使用此函数,\n动态指针的使用风险较大,如果不是非常熟悉其规则,\n建议不要使用此函数\n\n对象所有read前缀的成员函数底层基本都是调用这个函数\n此函数读取的数据支持调用unRead或unReadAlloc,\n撤消并退回到读缓冲区 readAlloc(.() = 循环读取数据,直到读取结束, readAlloc(.(读取长度) = 循环读取数据,直到达到参数中限定的最大长度\n无数据返回null,\n\n如果读取到数据则返回2个值:动态指针,内存长度\n调用者必须负责调用 raw.realloc(0,动态指针)\n释放返回值1返回的指针\n\n此函数直接操作内存,效率更好\n但一定要记住释放返回的内存指针 readAlloc(.(动态指针) = 循环读取数据并存入参数指定的动态指针,\n返回新的指针地址和内存长度,\n此函数可能更新指针地址或分配的内存大小,\n必须使用返回的新指针覆盖原来保存该动态指针的变量值 unReadAlloc(__) = 把readAlloc读取的动态指针退回缓存,\n\n注意退回数据的顺序是“后出先进”,\n最后读出的应当最先退回 unRead(__) = 把read,readTo,readBuffer等函数读出的数据退回缓存,\n注意退回数据的顺序是“后出先进”,\n最后读出的应当最先退回 write(.(->->->) = 发送数据,\n支持一个或多个参数,参数支持字符串、buffer (buffer)、数值、结构体\n成功返回true close() = 关闭TCP客户端。\n如果未显式调用此函数,在对象析构时将会自动调用。\n\n已断开连接的套接字不能再使用,需要重连接请重新创建套接字。\n就好比用过的车票不能重复用,什么地方都有规则和限制。 shutdown(__) = 断开连接。\n参数中指定 0 为仅停止收数据,指定 1 为停止发送数据。\n默认值为 2 表示停止收发数据。\n此函数并不销毁套接字句柄,也不会关闭事件监听器。\n\n已断开连接的套接字不能再使用,需要重连接请重新创建套接字。\n就好比用过的车票不能重复用,什么地方都有规则和限制。 setTimeouts(.(发送超时,接收超时) = 设置超时,以亳秒为单位(1秒为1000毫秒) getLocalIp() = 返回连接的本地IP,端口号 getRemoteIp() = 返回连接的远程IP,端口号 asyncSelect(.(event) = 检测到由event参数指明的网络事件后,\n参数@1使用_FD_前缀的常量指定,可使用位或操作符指定多个选项\n事件到达会触发相应的事件(参考on前缀的事件函数),\n\n失败返回null,以及错误信息,\n成功返回true,以及上次调用此函数指定的event参数 lastSelectMessageId = 最后一次调用asyncSelect应用的消息ID\n不可手动修改此属性,应由对象自动维护 lastSelectHwnd = 最后一次调用asyncSelect应用的窗口句柄\n不可手动修改此属性,应由对象自动维护 lastSelectEvent = 最后一次调用asyncSelect应用的事件\n不可手动修改此属性,应由对象自动维护 recv(.(最大接收长度) = 单次接收数据包\n如果参数不指定长度,则使用bufferSize指定的长度\n成功返回字符串,\n失败返回null,错误代码\n\nrecv,recvBuffer应在onReceive事件中使用,\n在onReceive里不能多次调用recv,recvBuffer。\nrecv,recvBuffer不保证每次一定会读完数据,所以在onClose事件里要继续读到没有任何数据。\n\n在 onRead事件中调用read前缀的系列函数可以循环读取所有接收的数据,\n这样就可以避免onClose以后还数据没有收完的问题 recvBuffer(.(缓冲区,读取长度) = 单次接收数据包\n参数@1指定 buffer 对象,\n参数@2可省略,默认为缓冲区长度,\n成功返回接收的长度,\n失败返回null,错误代码\n\nrecv,recvBuffer应在onReceive事件中使用,\n在onReceive里不能多次调用recv,recvBuffer。\nrecv,recvBuffer不保证每次一定会读完数据,所以在onClose事件里要继续读到没有任何数据。\n\n在 onRead事件中调用read前缀的系列函数可以循环读取所有接收的数据,\n这样就可以避免onClose以后还数据没有收完的问题 send(.(数据,长度) = 单次发送数据包 flush() = 兼容aardio标准流接口 setopt(_SO__) = 设置选项\n参数@1使用_SO_前缀的常量指定选项,参数@2使用结构体或数值指定值\n可选用参数@3指定设置层次,默认为SOL_SOCKET\n成功返回true getopt(_SO__) = 获取选项\n参数@1使用_SO_前缀的常量指定选项,参数@2使用结构体指定值\n如果不指定参数@2,则获取一个32位整型数值,\n可选用参数@3指定设置层次,默认为SOL_SOCKET\n成功返回读取的结构体 onConnect = @.onConnect = function(err){ __/*连接操作已完成,\n连接成功时err为0,且readyState为1,\n在这里可以开始发送数据*/ } onReceive = @.onReceive = function(err){ __/*收到数据,\n仅读取已到达的数据,\n定义了此事件就不应同时定义onRead事件*/ } onRead = @.onRead = function(err){ __/*收到数据,\n可阻塞读取数据,\n定义了此事件就不应同时定义onReceive事件*/ } onOutOfBandData = @.onOutOfBandData = function(err){ __/*收到紧急数据,\n即send函数最后一个flag参数设为_MSG_OOB时发送的1字节带外数据*/ } onSend = @.onSend = function(err){ __/*发送数据*/ } onClose = @.onClose = function(err){ __/*已断开连接,\n如果缓冲区中仍然有数据,\n这个事件可能在其他事件前面触发*/ } end intellisense**/