2008/01/31

Recent entries from same category

  1. バッチ ジョブを終了しますか (Y/N)?
  2. Windowsで空文字をechoする。
  3. SQLite3のWin32シェル版を簡単に作る手順
  4. 私が良く使うキーボードショートカット

nayoyaグループ - naoyaの日記 - Gearmanのやつ#2
clouderさんは結局、MSG_WAITALLを使う方法を取られたようですね。個人な趣味としてはあまりMSG_WAITALLは使わないほうなので、きっと私の場合はループを回すかな。
理由は大した事ではなく、サーバ側で「Content-Length」を出力し、そのContent-Length数分データを送信するようなCGIを書いた場合、バグで「Content-Length」分満たない内に落ちてしまった場合に、クライアントの受信がMSG_WAITALLだとバッファ全部破棄されてしまいエラーハンドリングし辛いからです。
(※たとえばどこまで受信したかが分からないとか...)
今日は本題から外れますが、上記リンクの中にも出てきたソケットディスクリプタから「fdopen」する処理をWindowsではどう書くかをご紹介。

HTTPプロトコルを使ったクライアントアプリケーションのプログラミングでは、ヘッダ部分等で「¥n」が来るまで受信し続けるといった面倒臭い処理が必要になりますが、そんな場合にはUN*Xでは「fdopen」を使ってファイルディスクリプタからストリーム(FILE構造体ポインタ)へ変換します。
しかしソケットディスクリプタとファイルディスクリプタを同一視出来るUN*Xとは違い、Windowsの場合には小細工が必要です。
WIN32APIには、、OSのファイルハンドルからファイルディスクリプタを取得するAPI「_open_osfhandle/_get_osfhandle」があります。
これにsocketディスクリプタを渡し、その結果を「fdopen」すると「fgets」や「feof」といった高水準ファイル入出力関数が使える様になります。
以下例です。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <winsock.h>
#include <io.h>
#include <fcntl.h>
#pragma comment (lib, "ws2_32.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define closesocket(s)  close(s)
#endif

int
main(int argc, char* argv[]) {

    int sock = -1;
    struct sockaddr_in serv_addr;
    struct hostent* host_ent;
    FILE *fpread = NULL;
    FILE *fpwrite = NULL;
    char buf[BUFSIZ];
#ifdef _WIN32
    int sock_osfhandle = -1;
    int sockopt = SO_SYNCHRONOUS_NONALERT;
    {
        WSADATA wsaData;
        if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0)
            goto leave;
    }
    setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&sockopt, sizeof(sockopt));
#endif

    if (argc != 3) {
        fputs("usage: recvline hostname port\n", stderr);
        goto leave;
    }

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("create socket");
        goto leave;
    }

    if ((host_ent = gethostbyname(argv[1])) == NULL) {
        perror("gethostbyname");
        goto leave;
    }
    memset((char *)&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    memcpy(&serv_addr.sin_addr, host_ent->h_addr, host_ent->h_length );
    serv_addr.sin_port = htons((short)atoi(argv[2]));

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connect");
        goto leave;
    }

#ifdef _WIN32
    sock_osfhandle = _open_osfhandle(sock, _O_RDONLY);
    fpread = fdopen(sock_osfhandle,  "rb");
    fpwrite = fdopen(sock_osfhandle, "wb");
#else
    fpread = fdopen(sock, "rb");
    fpwrite = fdopen(sock, "wb");
#endif
    if (!fpread || !fpwrite) {
        perror("fdoepn");
        goto leave;
    }

    fprintf(fpwrite, "GET / HTTP/1.0\n");
    fprintf(fpwrite, "Host: www.google.co.jp\n");
    fprintf(fpwrite, "\n");
    fflush(fpwrite);

    while(fgets(buf, sizeof(buf), fpread)) {
        char* eol = strpbrk(buf, "\r\n");
        if (eol) *eol = '\0';
        puts(buf);
    }

leave:
    if (fpread) fclose(fpread);
    if (fpwrite) fclose(fpwrite);
    if (sock > 0) close(sock);
#ifdef _WIN32
    if (sock_osfhandle) close(sock_osfhandle);
    WSACleanup();
#endif
    return 0;
}
※注意:Windowsでしかコンパイルしてません
今までストリーム(FILE構造体ポインタ)を引数に動いていた関数に、ソケットのデータを受け渡したいなんて場合には便利ですね。
Posted at by | Edit