UWSCでWinsockを使う

stuncloudさんがUWSCでもWinsock簡単だよー、と言ってたので、挑戦してみた。
UWSCでIRCクライアント | たっぷす庵


まあまあなモジュールにできた。
現在同期版のため、使用の際には注意が必要。

追記 20131110
nonblocking版を作成したので、このスクリプトは不要かな。
UWSCでnonblocking版Winsockを使う - じゅんじゅんのきまぐれ

スクリプト

Winsock.uws

OPTION EXPLICIT

IFB GET_UWSC_NAME = "Winsock.uws" THEN
	Winsock.Startup()
	DIM s = Winsock.Create(), res = NULL
	IFB s <> Winsock.SOCKET_ERROR THEN
		IFB MSGBOX("server?", BTN_YES OR BTN_NO) = BTN_YES THEN
			DIM cs = Winsock.SOCKET_ERROR
			IF Winsock.BindS(s, 12345) = 0 AND Winsock.ListenS(s) = 0 THEN cs = Winsock.AcceptS(s)
			WHILE cs <> Winsock.SOCKET_ERROR
				res = Winsock.RecvS(cs)
				IF res <> Winsock.SOCKET_ERROR THEN res = Winsock.SendS(cs, res)
				IFB res = Winsock.SOCKET_ERROR THEN
					Winsock.Close(cs)
					cs = Winsock.SOCKET_ERROR
					IF MSGBOX("Listen?", BTN_YES OR BTN_NO) = BTN_YES THEN cs = Winsock.AcceptS(s)
				ENDIF
			WEND
		ELSE
			IFB Winsock.ConnectS(s, 12345, "127.0.0.1") = Winsock.SOCKET_ERROR THEN
				MSGBOX("connect fail.")
				res = EMPTY
			ENDIF
			WHILE res <> EMPTY
				res = INPUT("?")
				IFB LENGTH(res) THEN
					res = Winsock.SendS(s, res)
					IF res = Winsock.SOCKET_ERROR THEN res = EMPTY ELSE PRINT "recv:" + Winsock.RecvS(s)
				ENDIF
			WEND
		ENDIF
		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   = 128
	CONST INADDR_ANY  = 0
	CONST IPPROTO_TCP = 6

	CONST SOCKADDR_LENGTH = 16
	CONST SOCKET_ERROR = $FFFFFFFF

	DEF_DLL socket(int, int, int): DWORD: ws2_32
	DEF_DLL closesocket(DWORD): int: ws2_32
	DEF_DLL bind(DWORD, DWORD[], int): int: ws2_32
	DEF_DLL gethostbyname(string): DWORD: ws2_32
	DEF_DLL listen(DWORD, int): int: ws2_32
	DEF_DLL accept(DWORD, DWORD[], var int): DWORD: ws2_32
	DEF_DLL connect(DWORD, DWORD[], long): DWORD: ws2_32
	DEF_DLL recv(DWORD, var string, long, long): long: ws2_32
	DEF_DLL send(DWORD, string, long, long): long: 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


	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

	FUNCTION Cleanup()
		DEF_DLL WSACleanup(): int: ws2_32
		RESULT = WSACleanup()
	FEND

	FUNCTION Create(type=SOCK_STREAM)
		RESULT = socket(AF_INET, type, 0)
	FEND

	FUNCTION Close(s)
		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 ConvAddr(addr, port)
		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 BindS(s, port, addr=INADDR_ANY)
		RESULT = bind(s, ConvAddr(addr, port), SOCKADDR_LENGTH)
	FEND

	FUNCTION ListenS(s, backlog=SOMAXCONN)
		RESULT = listen(s, backlog)
	FEND

	FUNCTION AcceptS(s)
		DIM port, addr
		RESULT = AcceptSEx(s, port, addr)
	FEND
	FUNCTION AcceptSEx(s, var port, var addr)
		DIM tar = ConvAddr(0, 0)
		DIM len = SOCKADDR_LENGTH
		RESULT = accept(s, tar, len)
		IF len >= 4 THEN port = ntohs(INT(tar[0] / $10000))
		IF len >= 8 THEN addr = inet_ntoa(tar[1])
	FEND

	FUNCTION ConnectS(s, port, addr)
		RESULT = connect(s, ConvAddr(addr, port), SOCKADDR_LENGTH)
	FEND

	FUNCTION SendS(s, msg)
		RESULT = send(s, msg, LENGTHB(msg), 0)
	FEND

	FUNCTION RecvS(s, size=1024)
		DIM buf = FORMAT(CHR(0), size)
		RESULT = recv(s, buf, size, 0)
		IF RESULT <> SOCKET_ERROR THEN RESULT = COPYB(buf, 1, RESULT)
	FEND

ENDMODULE

使い方

サンプルコードを見てください。
基本的には、2つのUWSCプロセスで通信します。
スクリプトを起動すると、「server?」と聞いてきます。


注意!サーバーは、不完全です。
不完全なので、UWSC本体を起動してから実行してください。
そうしないと、止める手段がプロセスキルしかなくなります。


次にクライアントを実行。(「server?」でいいえ)
クライアント側にINPUTが表示されるので、何か入力してOK。
すると、サーバーからの折り返しを受信してPRINTします。
そのループ。
やめるときは、INPUTにCANCELしてください。


クライアントが終了しても、サーバーが受信待ちで返ってきません。
「STOP」して止めてください。
なお、「STOP」するとListenポートが開きっぱなしになるので、再度実行はできません。
UWSCを終了してください。

備考

同期版は、かなりの行動が同期します。
このため、随所でブロックして、UWSCが応答なしに落ちる罠が多いです。
注意すれば、なんとか使えるレベルとなっています。
本格的にやるなら、やはり非同期版が必須。