忍者ブログ
プログラミングとか日常とかの覚書っぽいなにか
[10] [9] [8] [7] [6] [5] [4] [3] [2] [1]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

2012/01/19 ポート番号をホストバイトオーダで指定していた問題を修正しました。イノビア様、ご指摘ありがとうございました。

HSPでソケットを使うにはhspsockなどの拡張プラグインがありますけど、せっかくHSP3でメッセージ割り込み処理が可能になっているというのに、ループを作ってその中で接続や受信をチェックして…などというのは無駄が多いと思うのですよ。
それよりも、WIndowsソケット(winsock)のWSAAsyncSelect関数による非同期ソケットのほうが相性が良いと思うのです。接続やら受信やらのイベント通知をウィンドウメッセージで受け取ることができますからね。

ということで、今回はHSPでの非同期ソケットに挑戦してみました。


サーバ 側とクライアント側を別々に作成します。
どちらの場合も、socket関数でソケットを生成したら直ちにWSAAsyncSelect関数に渡すことで、ソケットイベントがウィンドウメッセージとして通知されるようにしておきます。

まずはサーバから。最初のリスナソケット(listenを行うソケット)はWSAAsyncSelectに渡しますが、そのリスナソケットのaccept時に受け取るクライアント接続のソケットは自動的に非同期ソケットになっているので、こちらのほうはわざわざWSAAsyncSelectに渡す必要はありません。

ポート番号はネットワークバイトオーダ(TCP/IPの場合はビッグエンディアン)で指定しなければならないので、htons関数をかませてあります。ちなみに、本来なら2バイト値を返すhtonsに対しては
(htons(listener_port) & 0xFFFF)
という値を取るべきなんですが、wpokeが下位2バイトのみをセットしてくれるのでそのままにしています。

; Winsock通知時のウィンドウメッセージ
#define SOCK_NOTIFY_MESSAGE     0x1000

; 外部関数(WinAPI)定義
#uselib "ws2_32.dll"
#func WSAStartup "WSAStartup" int,int
#func WSACleanup "WSACleanup"
#cfunc htons "htons" int
#cfunc socket "socket" int,int,int
#func WSAAsyncSelect "WSAAsyncSelect" int,int,int,int
#func bind "bind" int,int,int
#func listen "listen" int,int
#cfunc accept "accept" int,int,int
#func closesocket "closesocket" int
#func send "send" int,int,int,int
#cfunc recv "recv" int,int,int,int
#func shutdown "shutdown" int,int

#define AF_INET         $00000002
#define SOCK_STREAM     $00000001
#define IPPROTO_TCP     $00000006
#define INVALID_SOCKET  $FFFFFFFF
#define FD_READ         $00000001
#define FD_ACCEPT       $00000008
#define ADDR_ANY        $00000000
#define SD_BOTH         $00000002
#define WSAEWOULDBLOCK  $00002733

    ; Winsockの初期化
    winsockver = 0x0002             ; バージョン2.0
    dim wsadata, 100
    WSAStartup winsockver, varptr(wsadata)

    onexit goto *cleanup

    ; リスンポート番号
    listener_port = 10000

    ; リスナーソケット生成
    hlistener = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP )

    ; ソケット通知設定
    WSAAsyncSelect hlistener, hwnd, SOCK_NOTIFY_MESSAGE, FD_READ | FD_ACCEPT
    oncmd gosub *on_sock_notify, SOCK_NOTIFY_MESSAGE

    ; sockaddr_in 構造体のセット
    dim sockaddr, 4
    wpoke sockaddr, 0, AF_INET
    wpoke sockaddr, 2, htons(listener_port)
    lpoke sockaddr, 4, ADDR_ANY
    ; バインド
    bind hlistener, varptr(sockaddr), 16
    if stat != 0 : dialog "バインドエラー" : goto *cleanup

    ; リスン開始
    listen hlistener, 5
    stop

*on_sock_notify
    sock_event = lparam & 0xFFFF            ; winsockイベント種別
    sock_error = (lparam >> 16) &0xFFFF     ; winsockエラー
    switch sock_event
    case FD_ACCEPT      ; クライアントからの接続要求
        hclient = accept( hlistener, 0, 0 )
        mes "接続" + hclient
        swbreak
    case FD_READ        ; 受信データ読み込み可能
        hclient = wparam
        ; ソケットから読み込み(1バイトだけ)
        sdim recv_data, 4
        recv_size = recv( hclient, varptr(recv_data), 1, 0 )
        ; 受信完了時
        if recv_size >= 0 {
            mes "受信:" + recv_data
            ; ソケットに書き込み(1バイトだけ)
            send_data = "A"
            send hclient, varptr(send_data), 1, 0
            mes "応答送信:" + send_data
            ; クローズ
            shutdown hclient, SD_BOTH
            closesocket hclient
        }
        swbreak
    swend
    return

*cleanup
    ; リスナーソケットをクローズ
    closesocket hlistener

    ; Winsockのクリーンアップ
    WSACleanup
    end
クライアントから接続要求を受けたときはFD_ACCEPTを受けるので、そのときにacceptを行います。
受信データ時にFD_READを受け取るので、そのときにrecvを行えばOK。ここでは要求データ受信直後に応答データ「A」1文字を送信しています。

今回は簡略化のため1バイトデータのやり取りのみを行っています。というのも、TCPは1回のsendされたデータを1回のrecvで受け取るとは限らず、パケットが結合されたり分割されたりするので、本来はそれに備えた処理を入れる必要があるのですが、それを省くために1バイトデータのみのやり取りとしました。
また、今回は必要最低限のエラー処理のみ入れておきますが、実際には他のエラー処理も入れる必要があります。

次にクライアント。connectで接続要求をした後はFD_CONNECT通知を待つことになります。
FD_CONNECT通知をエラーなしで受け取った場合は書き込み。接続確立後にデータ「R」1文字を送信し、応答を待ちます。
応答を受信するとFD_READ通知を受けるので、recvで受信するようにしています。

; Winsock通知時のウィンドウメッセージ
#define SOCK_NOTIFY_MESSAGE     0x1000

; 外部関数(WinAPI)定義
#uselib "ws2_32.dll"
#func WSAStartup "WSAStartup" int,int
#func WSACleanup "WSACleanup"
#cfunc htons "htons" int
#cfunc socket "socket" int,int,int
#func WSAAsyncSelect "WSAAsyncSelect" int,int,int,int
#func connect "connect" int,int,int
#func closesocket "closesocket" int
#func send "send" int,int,int,int
#cfunc recv "recv" int,int,int,int
#func shutdown "shutdown" int,int
#cfunc inet_addr "inet_addr" sptr
#cfunc WSAGetLastError "WSAGetLastError"

#define AF_INET         $00000002
#define SOCK_STREAM     $00000001
#define IPPROTO_TCP     $00000006
#define INVALID_SOCKET  $FFFFFFFF
#define FD_READ         $00000001
#define FD_CONNECT      $00000010
#define SD_BOTH         $00000002
#define WSAEWOULDBLOCK  $00002733

    ; Winsockの初期化
    winsockver = 0x0002     ; Winsockバージョン2.0
    dim wsadata, 100
    WSAStartup winsockver, varptr(wsadata)

    onexit goto *cleanup

    ; 接続先(自分自身のポート10000)
    target_addr = "127.0.0.1"
    target_port = 10000

    ; ソケット生成
    hsock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP )

    ; ソケット通知設定
    WSAAsyncSelect hsock, hwnd, SOCK_NOTIFY_MESSAGE, FD_READ | FD_CONNECT
    oncmd gosub *on_sock_notify, SOCK_NOTIFY_MESSAGE

    ; sockaddr_in 構造体のセット
    dim sockaddr, 4
    wpoke sockaddr, 0, AF_INET
    wpoke sockaddr, 2, htons(target_port)
    lpoke sockaddr, 4, inet_addr(target_addr)
    ; 接続
    connect hsock, varptr(sockaddr), 16
    if stat != 0 {
        if WSAGetLastError() != WSAEWOULDBLOCK {
            dialog "接続できませんでした"
            goto *cleanup 
        }
    }
    stop

*on_sock_notify     ; winsock通知メッセージ受信時
    sock_event = lparam & 0xFFFF            ; winsockイベント種別
    sock_error = (lparam >> 16) &0xFFFF     ; winsockエラー番号
    switch sock_event
    case FD_CONNECT         ; 接続完了(or接続エラー)通知
        if (sock_error != 0) {
            dialog "接続できませんでした"
            goto *cleanup 
        }
        mes "接続完了"
        ; ソケットに書き込み(1バイトだけ)
        send_data = "R"
        send hsock, varptr(send_data), 1, 0
        mes "送信:" + send_data
        swbreak
    case FD_READ            ; 受信データ読み込み可能
        ; ソケットから読み込み(1バイトだけ)
        sdim recv_data, 4
        recv_size = recv( hsock, varptr(recv_data), 1, 0)
        if recv_size >= 0 {
            mes "受信:" + recv_data
            shutdown hsock, SD_BOTH
            closesocket hsock
        }
        swbreak
    swend
    return

*cleanup
    ; Winsockのクリーンアップ
    WSACleanup
    end
各種Winsock APIについての説明は省いていますので、分からなければ検索してみてください。



(いずれ専用の解説ページ作りたいとは思っていますが、いかんせん時間が…)

拍手

PR

コメント


コメントフォーム
お名前
タイトル
文字色
メールアドレス
URL
コメント
パスワード
  Vodafone絵文字 i-mode絵文字 Ezweb絵文字


トラックバック
この記事にトラックバックする:


忍者ブログ [PR]
プロフィール
HN:
はむぱい
職業:
ソフト作ったりしてる人
Twitter
最新CM
[06/09 replica rolex oyster perpetual datejust]
[06/09 bracelets imitation cartier love]
[06/09 replica the oyster perpetual datejust]
[06/09 datejust rolex oyster perpetual]
[06/09 replica gold love bangle]
カレンダー
02 2024/03 04
S M T W T F S
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
ブログ内検索
あ~いい漢字