UWSCでIRCクライアント

社内にIRCサーバーが出来まして
利用条件は一つ、参加者が自分でクライアントを作ること!
今のところ6人くらいですがみんな面白いもの作ってる

僕は当然UWSCで作る

createforemでUIを作って、通信はwinsock使いました
しかし、createforemとthreadが仲悪いのでそのへん面倒だったり

まだ改良中ということもあって記事にはコード載せないけど、代わりにPINGPONGサンプルを載せときます

IRCってサーバーが定期的にPINGコマンドを飛ばしてくるので、すかさずPONGを返さないとログアウトさせられちゃうんですね

dim serverIP = "xxx.xxx.xxx.xxx"
dim port     = 6667

dim WSADATA[198]
dim sin_zero[7]
SETCLEAR(sin_zero, 0)
dim s = SOCKET_ERROR
logprint(TRUE, 100, 100, 800, 600)

try
    print "初期化"
    if WSAStartup($202, WSADATA) then
        msgbox("WSAStartup失敗")
        exit
    endif
    print "ソケット作成"
    s = socket(AF_INET, SOCK_STREAM, 0)
    if s = SOCKET_ERROR then
        msgbox("ソケット作れませんでした")
        exit
    endif

    print "サーバーに接続"
    if connect(s, AF_INET, htons(port), inet_addr(serverIP), sin_zero, SOCKADDR_LENGTH) = SOCKET_ERROR then
        msgbox("接続に失敗しました")
        exit
    endif

    print "ニックネーム送信"
    msg = "NICK stuncloud"
    msg = msg + chr(10)
    if send(s, msg, lengthb(msg), 0) = SOCKET_ERROR then
        msgbox("送信失敗")
        exit
    endif
    print "ユーザー名送信"
    msg = "USER Joey hostname servername :Joey Takahashi"
    msg = msg + chr(10)
    if send(s, msg, lengthb(msg), 0) = SOCKET_ERROR then
        msgbox("送信失敗")
        exit
    endif

    print "受信待機"
    while TRUE
        recvmsg = format(chr(0), 1024)
        if recv(s, recvmsg, lengthb(recvmsg), 0) = SOCKET_ERROR then
            msgbox("受信失敗")
            exit
        endif
        print recvmsg
        if pos("PING", recvmsg) = 1 then
            print "PINGが来たのでPONGを返す"
            msg = replace(recvmsg, "PING", "PONG")
            msg = msg + chr(10)
            if send(s, msg, lengthb(msg), 0) = SOCKET_ERROR then
                msgbox("送信失敗")
                exit
            else
                msgbox("PONGを送信しました")
            endif
            break
        endif
    wend
finally
    if s <> SOCKET_ERROR then closesocket(s)
    WSACleanup()
endtry

const AF_INET             = 2
const SOCK_STREAM         = 1
const SOMAXCONN           = 128
const INADDR_ANY          = $0
const SOCKET_ERROR        = $FFFFFFFF
const IPPROTO_TCP         = 6

const SOCKADDR_LENGTH  = 16

def_dll socket(Long, Long, Long):dword:ws2_32.dll
def_dll closesocket(dword):dword:ws2_32.dll
def_dll connect(dword, {word, word, dword, byte[]}, long):dword:ws2_32.dll
def_dll recv(dword, var string, Long, Long):Long:ws2_32.dll
def_dll send(Long, string, Long, Long): Long:ws2_32.dll
def_dll htonl(dword):dword:ws2_32.dll
def_dll ntohl(dword):dword:ws2_32.dll
def_dll htons(word):word:ws2_32.dll
def_dll ntohs(word):word:ws2_32.dll
def_dll inet_addr(String):dword:ws2_32.dll
def_dll inet_ntoa(dword):String:ws2_32.dll

def_dll WSAStartup(word, var word[]):Long:ws2_32.dll
def_dll WSACleanup():Long:ws2_32.dll

createformとthreadで困った話
recvがスクリプトを止めちゃうので、threadで実行したいんですよ
で、メインスレッドでcreateformするとrecvスレッドからフォームにアクセス出来ないんですよ
しょうがないのでフォームもrecvスレッドで作ると一見うまくいくんですけどこんどはoleeventが出来ないんですよ
oleevent呼ぶのははメインスレッドじゃないとダメなようです
なので結局recvだけ別スレッドにして、受信したメッセージを共用変数に入れることでどうにかしました
変数になんか入ったらメイン側で処理、変数を空にしたらrecv再開みたいな
ただ、なんか危なっかしいのでもちょっとどうにかしたいなーと思ったてたところでしゅんさんがタイムリーな記事を書いてださいました
これ参考にちょっとキューを実装してみようと思います

あとね、winsockそんなに難しくないのでUWSCでもわりと気楽に使えます
これ使えば複数のPCでやりとりするようなものも書けますし、覚えておくと便利よ

UWSCでIRCクライアント」への3件のフィードバック

  1. WSAStartupの第二引数は、199個のWORD配列へのポインターで良いと思いますよー。
    スクリプトだと一見足りてなさそうですが、VirtualAllocの最低確保サイズがページ(4k)単位なので問題ないですが。
    sin_zeroは多分不要ですが、そこにメモリーが必要なはずなので、今のままではいけない気がします、、、。
    未確認ですみませんが。

    とはいえ、Winsockサンプル、ありがとうございます!

  2. >199個のWORD配列へのポインター
    なおしました
    szDescriptionとszSystemStatusがポインタだと思ってて2+2+4+4+2+2+4で20バイトにしてました
    sin_zeroもなんかそんな感じでやってました
    とりあえずこっちもbyte配列にしました

    >sin_zeroは多分不要
    sockaddrを{word, word, dword}と展開してみたらなんだか普通に動きますね
    そもそもいらなかったのか…
    sockaddr_inとして使う場合は後ろが余るからスペーサとして入ってるような認識ではあったんですけど、なんだかよくわかりません、sin_zero

  3. 素早いですね。

    せっかくなのでWinsockモジュールを作ろうと苦闘中ですが、いらない子に思えるsin_zero、acceptの際には何か関係しているのかもしれません。
    なしだと動かない様子、、、。(確定ではありませんが)
    結局、DWORD4個の配列でAF_INETとポート番号を同居させる方向でやったら、上手くいきました。
    しかし、同期版だと、異常系で応答なしに陥りやすいので、公開は考え中、、、。
    非同期まで手を出すか、、、。

コメントを残す