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が必要。