UWSCでnonblocking版Winsockを使う
単純なblocking版は以下。
UWSCでWinsockを使う - じゅんじゅんのきまぐれ
しかし、ブロックするとUWSCが落ちかねないし、recv中に切れると返ってこないとか、悲惨。
非同期版にしようかとも思ったけど、ブロックしない版でないと、UWSCのスレッドが耐えられない。
ということで、ブロックしない版を作成してみました。
なんか、、、落ちまくりだったのはなんだったのだろう、というほど素直に動く。
あ、listenしたままスクリプトを止めちゃうと、プロセス終了して解放されるまでlistenできなくなるから注意ね。
スクリプト
OPTION EXPLICIT IFB GET_UWSC_NAME = "Winsock.uws" THEN Winsock.Startup() DIM s = Winsock.Create(), res = 0, cs = s, mode = "client", port, addr, x = 300, y = 0 IFB s <> Winsock.SOCKET_ERROR THEN IFB MSGBOX("server?", BTN_YES OR BTN_NO) = BTN_YES THEN mode = "server" x = 0 ENDIF WHILE res <> Winsock.SOCKET_ERROR SELECT SLCTBOX(SLCT_BTN, 0, x, y, mode + "処理", "接続", "送信", "受信", "切断") CASE SLCT_1 port = VAL(INPUT("port", "12345", FALSE, x, y)) addr = INPUT("addr", "127.0.0.1", FALSE, x, y) IFB mode = "server" THEN res = Winsock.ListenN(s, port, addr) IF res = 0 THEN cs = Winsock.AcceptB(s) IF cs = Winsock.SOCKET_ERROR THEN res = cs ELSE res = Winsock.ConnectB(cs, port, addr) IF res = Winsock.SOCKET_ERROR THEN MSGBOX("connect fail.", BTN_OK, x, y) ENDIF CASE SLCT_2 res = INPUT(mode, "", FALSE, x, y) IF LENGTH(res) THEN res = Winsock.SendB(cs, res) CASE SLCT_3 res = Winsock.RecvB(cs) IFB res <> Winsock.SOCKET_ERROR THEN IFB res = 0 THEN MSGBOX(mode + "<#CR> timeout", BTN_OK, x, y) ELSE MSGBOX(mode + "<#CR> recv: " + res, BTN_OK, x, y) ENDIF ENDIF CASE SLCT_4 res = Winsock.SOCKET_ERROR SELEND WEND MSGBOX(mode + "<#CR> fin.", BTN_OK, x, y) IF cs <> s THEN Winsock.Close(cs) Winsock.Close(s) ENDIF Winsock.Cleanup() ENDIF MODULE Winsock CONST AF_INET = 2 //* internetwork: UDP, TCP, etc. */ CONST SOCK_STREAM = 1 //* stream socket */ CONST SOCK_DGRAM = 2 //* datagram socket */ CONST SOMAXCONN = $7FFFFFFF CONST INADDR_ANY = 0 CONST IPPROTO_TCP = 6 CONST IPPROTO_UDP = 17 CONST FD_SETSIZE = 64 CONST FIONBIO = $8004667E //* set/clear non-blocking i/o */ CONST WAIT_UNIT = 100 CONST WAIT_NUM = 600 CONST SOCKADDR_LENGTH = 16 CONST SOCKET_ERROR = -1 DEF_DLL socket(int, int, int): int: ws2_32 DEF_DLL closesocket(int): int: ws2_32 DEF_DLL bind(int, DWORD[], int): int: ws2_32 DEF_DLL gethostbyname(string): DWORD: ws2_32 DEF_DLL listen(int, int): int: ws2_32 DEF_DLL accept(int, DWORD[], var int): int: ws2_32 DEF_DLL connect(int, DWORD[], int): int: ws2_32 // NULLも送受信するなら、stringではダメ //DEF_DLL recv(int, var string, int, int): int: ws2_32 //DEF_DLL send(int, string, int, int): int: ws2_32 DEF_DLL recvfrom(int, var string, int, int, var DWORD[], var int): int: ws2_32 DEF_DLL sendto(int, string, int, int, DWORD[], int): int: ws2_32 DEF_DLL select(int, var DWORD[], var DWORD[], var DWORD[], long[]): int: ws2_32 //DEF_DLL __WSAFDIsSet(int, var DWORD[]): int: ws2_32 DEF_DLL ioctlsocket(int, DWORD, var DWORD): int: ws2_32 //DEF_DLL htonl(DWORD): DWORD: ws2_32 //DEF_DLL ntohl(DWORD): DWORD: ws2_32 DEF_DLL htons(WORD): WORD: ws2_32 DEF_DLL ntohs(WORD): WORD: ws2_32 //DEF_DLL inet_addr(string): DWORD: ws2_32 DEF_DLL inet_ntoa(DWORD): string: ws2_32 //DEF_DLL RtlMoveMemory(var string, DWORD, DWORD): kernel32 //DEF_DLL RtlFillMemory(DWORD, DWORD, BYTE): kernel32 HASHTBL _socks // return: 0:ok SOCKET_ERROR:error FUNCTION Startup(verReq=$0202) DEF_DLL WSAStartup(WORD,var WORD[]): int: ws2_32 DIM WSAData[2+(256+128+2)/2+4] RESULT = WSAStartup(verReq, WSAData) FEND // return: 0:ok SOCKET_ERROR:error FUNCTION Cleanup() WHILE LENGTH(_socks) Close(_socks[0, HASH_KEY]) WEND DEF_DLL WSACleanup(): int: ws2_32 RESULT = WSACleanup() FEND // return: SOCKET_ERROR:error other:socket FUNCTION Create(type=SOCK_STREAM, af=AF_INET, protocol=0) RESULT = socket(af, type, protocol) IF RESULT <> SOCKET_ERROR THEN _socks[RESULT] = "" FEND // return: 0:ok SOCKET_ERROR:error FUNCTION Close(s) RESULT = _socks[s, HASH_REMOVE] RESULT = closesocket(s) FEND FUNCTION GetAddrByName(name) DIM ptr = gethostbyname(name), hostent[3], addr[0] DEF_DLL RtlMoveMemory(var DWORD[], DWORD, DWORD): kernel32 IF ptr THEN RtlMoveMemory(hostent, ptr, LENGTH(hostent) * 4) RESULT = 0 IFB INT(hostent[2] / $10000) > 0 THEN RtlMoveMemory(addr, hostent[3], 4) RtlMoveMemory(addr, addr[0], 4) RESULT = addr[0] ENDIF FEND FUNCTION ConvFromPort(port, addr=INADDR_ANY) RESULT = SAFEARRAY(0, 3) RESULT[0] = AF_INET + htons(port) * $10000 IF VARTYPE(addr) = VAR_BSTR THEN addr = GetAddrByName(addr) RESULT[1] = addr FEND FUNCTION ConvToPort(tar[], len, var port, var addr) IF len >= 4 THEN port = ntohs(INT(tar[0] / $10000)) IF len >= 8 THEN addr = inet_ntoa(tar[1]) RESULT = tar[1] FEND // return: 0:ok SOCKET_ERROR:error FUNCTION BindN(s, port, addr=INADDR_ANY) DIM tar = ConvFromPort(port, addr) RESULT = bind(s, tar, SOCKADDR_LENGTH) IF RESULT = 0 THEN _socks[s] = "B:" + port + ":" + addr FEND // return: 0:ok SOCKET_ERROR:error FUNCTION ListenN(s, port=0, addr=INADDR_ANY, backlog=SOMAXCONN) RESULT = 0 IF port THEN RESULT = BindN(s, port, addr) IF RESULT = 0 THEN RESULT = listen(s, backlog) IF RESULT = 0 THEN _socks[s] = _socks[s] + ",L" FEND // return: 0:no request SOCKET_ERROR:error other:socket FUNCTION AcceptB(s, c=WAIT_NUM, t=WAIT_UNIT) DIM port, addr RESULT = 0 WHILE RESULT = 0 AND c > 0 RESULT = AcceptN(s, port, addr, t) c = c - 1 WEND FEND FUNCTION AcceptN(s, var port, var addr, t=WAIT_UNIT) RESULT = Check(s, 1, t) IFB RESULT <> SOCKET_ERROR AND RESULT <> 0 THEN DIM tar = ConvFromPort(0, 0), len = SOCKADDR_LENGTH RESULT = accept(s, tar, len) IFB RESULT <> SOCKET_ERROR THEN ConvToPort(tar, len, port, addr) _socks[RESULT] = "A:" + port + ":" + addr ENDIF ENDIF FEND // return: 0:processing SOCKET_ERROR:error other:! FUNCTION Check(s, m=1, t=WAIT_UNIT) DIM fds[FD_SETSIZE], tm[1] tm[0] = INT(t / 1000) // sec tm[1] = t * 1000 MOD 1000000 // microseconds SETCLEAR(fds, 0) fds[0] = 1 fds[1] = s SELECT m CASE 1 RESULT = select(0, fds, NULL, NULL, tm) CASE 2 RESULT = select(0, NULL, fds, NULL, tm) DEFAULT RESULT = select(0, NULL, NULL, fds, tm) SELEND FEND // return: 0:processing SOCKET_ERROR:error other:connect FUNCTION ConnectB(s, port, addr, c=WAIT_NUM, t=WAIT_UNIT) RESULT = ConnectN(s, port, addr, t) WHILE RESULT = 0 AND c > 0 RESULT = IsConnect(s, t) c = c - 1 WEND FEND FUNCTION IsConnect(s, t=WAIT_UNIT) RESULT = Check(s, 2, t) IFB RESULT = 0 THEN RESULT = Check(s, 3, t) IF RESULT <> 0 THEN RESULT = SOCKET_ERROR // fail. ENDIF FEND FUNCTION ConnectN(s, port, addr, t=WAIT_UNIT) DIM argp = 1 RESULT = ioctlsocket(s, FIONBIO, argp) IFB RESULT = 0 THEN DIM tar = ConvFromPort(port, addr) RESULT = connect(s, tar, SOCKADDR_LENGTH) // 本当はWSAGetLastErrorを確認すべき RESULT = IsConnect(s, t) IFB RESULT <> SOCKET_ERROR THEN IF LENGTH(_socks[s]) THEN _socks[s] = _socks[s] + "," _socks[s] = _socks[s] + "C:" + port + ":" + addr ENDIF ENDIF FEND // return: 0:can't send SOCKET_ERROR:error other:sent FUNCTION SendB(s, msg, c=WAIT_NUM, t=WAIT_UNIT) RESULT = 0 IF LENGTHB(msg) = 0 THEN RESULT = SOCKET_ERROR WHILE RESULT = 0 AND c > 0 RESULT = SendN(s, msg, t) c = c - 1 WEND FEND FUNCTION SendN(s, msg, port=0, addr=0, t=WAIT_UNIT) RESULT = Check(s, 2, t) IF LENGTHB(msg) = 0 THEN RESULT = 0 IFB RESULT <> SOCKET_ERROR AND RESULT <> 0 THEN DIM tar = ConvFromPort(port, addr) RESULT = sendto(s, msg, LENGTHB(msg), 0, tar, SOCKADDR_LENGTH) ENDIF FEND // return: 0:no data SOCKET_ERROR:error other:recv FUNCTION RecvB(s, size=1024, c=WAIT_NUM, t=WAIT_UNIT) RESULT = 0 DIM port, addr WHILE RESULT = 0 AND c > 0 RESULT = RecvN(s, port, addr, size, t) c = c - 1 WEND IF RESULT = "" THEN RESULT = SOCKET_ERROR FEND FUNCTION RecvN(s, var port, var addr, size=1024, t=WAIT_UNIT) RESULT = Check(s, 1, t) IFB RESULT <> SOCKET_ERROR AND RESULT <> 0 THEN DIM buf = FORMAT(CHR(0), size), tar = ConvFromPort(0, 0), len = SOCKADDR_LENGTH RESULT = recvfrom(s, buf, size, 0, tar, len) IFB RESULT <> SOCKET_ERROR THEN RESULT = COPYB(buf, 1, RESULT) ConvToPort(tar, len, port, addr) ENDIF ENDIF FEND ENDMODULE
Nのついてる関数が、nonblocking版。
BはNを結果が出るまで呼び出す版。(≒blocking版)
実際には、引数t * 引数c msec待つ。(初期値では1分)
適当な値に変更すれば、適当にあきらめてくれるので便利かと。
引数cは、1以上の値を指定してください。(初期値600)
引数tは、0.001以上で指定可能ですが、15未満はあまり意味がなく、5000以上は危険です。
API内で長いことブロックすると、UWSCが固まってしまうのです。
引数tは100のままで、引数cだけ変えるのが推奨です。
Connectだけは0が返るケースは「あきらめ」でなく「処理中」。
IsConnect関数でどうなっているのか確認してください。
Connectやめるなら、Closeしてください。
Close関数の処理には、shutdownを入れた方が良い気がするけど、忘れとく。
FD_ISSET(__WSAFDIsSet)は、そもそも一つソケットしか指定しないので、忘れることにした。
文字列を送受信する想定。
NULLとかもやりたい場合、DEF_DLL定義と処理も変える必要あり。
長さ0の文字列は送れない。指定したらエラー。
SendNは、送信先が指定できるけど、TCPでは無意味。
RecvNも、送信元情報を取得できるけど、TCPでは無効。
サンプルスクリプトは、TCP通信の例。
UDP通信の場合は、Winsock.Create(Winsock.SOCK_DGRAM)でソケットを作って、
まず受信するなら、BindNして、RecvN。
送信元が判らなくても良いなら、RecvBでも可。
送信が先なら、BindNはしなくてもよくて、SendNで送信先を指定して送信。
続いての受信には、やっぱりBindNは不要(送信したI/Fで受信する)
送信で送信元ポートを固定したい場合は、BindNが必要。