read/write で入出力を行うときには、socket で得られたソケットディスクリプタという 整数値をそのまま利用しました。一方、printf や fgets の入出力の際は、 FILE 構造体が必要になります。そこで fdopen という、 ディスクリプタから FILE 構造体を生成する ライブラリ関数を使います。使い方は
FILE *fp; fp = fdopen(fd, "r");です。fd は open や socket で得られたディスクリプタ (int 型) です。 fopen の第2引数と同じで、"r" や "w" で入力モードか出力モードかを 指定できます。
一度 fdopen で FILE 構造体を作ってしまえば、後は
fprintf(fp, "hoge %d %s\n", num,str); fgets(buf, sizeof(buf), fp); fputs("fuga", fp);などと、おなじみの方法で入出力が可能になります (もちろん fread・fwrite も使えます)。
103: sprintf(send_buf, "GET %s HTTP/1.0\r\n", path); 104: write(s, send_buf, strlen(send_buf)); 105: 106: sprintf(send_buf, "Host: %s:%d\r\n", host, port); 107: write(s, send_buf, strlen(send_buf)); 108: 109: sprintf(send_buf, "\r\n"); 110: write(s, send_buf, strlen(send_buf)); 111: 112: /* あとは受信して、表示するだけ */ 113: while (1){ 114: char buf[BUF_LEN]; 115: int read_size; 116: read_size = read(s, buf, BUF_LEN); 117: if ( read_size > 0 ){ 118: write(1, buf, read_size); 119: } else { 120: break; 121: } 122: }そして、これが標準入出力ライブラリを使った例です。 両者を見比べてみて下さい。
21: FILE *fp; /* ソケット用の FILE 構造体 */ 22: char buf[BUF_LEN]; /* 受信用バッファ */
102: /* FILE 構造体を作成 */ 103: fp = fdopen(s, "r+"); 104: if ( fp == NULL ){ 105: fprintf(stderr, "fdopen に失敗しました。\n"); 106: return 1; 107: } 108: /* バッファリング OFF */ 109: setvbuf(fp, NULL, _IONBF, 0); 110: 111: /* サーバに送信 */ 112: fprintf(fp, "GET %s HTTP/1.0\r\n", path); 113: fprintf(fp, "Host: %s:%d\r\n", host, port); 114: fprintf(fp, "\r\n"); 115: 116: /* あとは受信して、表示するだけ */ 117: while (1){ 118: if ( fgets(buf, sizeof(buf), fp) == NULL ){ 119: break; 120: } 121: printf("%s", buf); 122: }
103: fp = fdopen(s, "r+");まず、入出力用の FILE 構造体を、ソケットディスクリプタから作成します。
FILE *fp_read; FILE *fp_write; fp_read = fdopen(s, "r"); fp_write = fdopen(s, "w");と、入力と出力を別々に作成する方法もあります。
109: setvbuf(fp, NULL, _IONBF, 0);バッファリングを OFF にします。 printf・fprintf・fgets などの標準入出力ライブラリには、 バッファリング機能が備わっています。バッファリングには
112: fprintf(fp, "GET %s HTTP/1.0\r\n", path); 113: fprintf(fp, "Host: %s:%d\r\n", host, port); 114: fprintf(fp, "\r\n"); 115: 116: /* あとは受信して、表示するだけ */ 117: while (1){ 118: if ( fgets(buf, sizeof(buf), fp) == NULL ){ 119: break; 120: } 121: printf("%s", buf); 122: }あとは、fprintf で送信用 FILE 構造体に送信し、 fgets でデータを読み込むだけです。 これは簡単に理解できますね。
以下は fgets・fprintf を使った HTTP クライアントの全ソースです。 ただし、上で説明した部分以外は、read・write 版との違いはありません。
1: /* $Id: http-client-2.c,v 1.3 2013/01/23 06:57:19 68user Exp $ */ 2: 3: #include <stdio.h> 4: #include <string.h> 5: #include <stdlib.h> 6: #include <sys/types.h> 7: #include <sys/socket.h> 8: #include <netdb.h> 9: #include <netinet/in.h> 10: #include <sys/param.h> 11: #include <sys/uio.h> 12: #include <unistd.h> 13: 14: #define BUF_LEN 256 /* バッファのサイズ */ 15: 16: int main(int argc, char *argv[]){ 17: int s; /* ソケットのためのファイルディスクリプタ */ 18: struct hostent *servhost; /* ホスト名と IP アドレスを扱うための構造体 */ 19: struct sockaddr_in server; /* ソケットを扱うための構造体 */ 20: struct servent *service; /* サービス (http など) を扱うための構造体 */ 21: FILE *fp; /* ソケット用の FILE 構造体 */ 22: char buf[BUF_LEN]; /* 受信用バッファ */ 23: 24: char host[BUF_LEN] = "localhost"; /* 接続するホスト名 */ 25: char path[BUF_LEN] = "/"; /* 要求するパス */ 26: unsigned short port = 80; /* 接続するポート番号 */ 27: 28: if ( argc > 1 ){ /* URLが指定されていたら */ 29: char host_path[BUF_LEN]; 30: 31: if ( strlen(argv[1]) > BUF_LEN-1 ){ 32: fprintf(stderr, "URL が長すぎます。\n"); 33: return 1; 34: } 35: /* http:// から始まる文字列で */ 36: /* sscanf が成功して */ 37: /* http:// の後に何か文字列が存在するなら */ 38: if ( strstr(argv[1], "http://") && 39: sscanf(argv[1], "http://%s", host_path) && 40: strcmp(argv[1], "http://" ) ){ 41: char *p; 42: 43: p = strchr(host_path, '/'); /* ホストとパスの区切り "/" を調べる */ 44: if ( p != NULL ){ 45: strcpy(path, p); /* "/"以降の文字列を path にコピー */ 46: *p = '\0'; 47: strcpy(host, host_path); /* "/"より前の文字列を host にコピー */ 48: } else { /* "/"がないなら=http://host という引数なら */ 49: strcpy(host, host_path); /* 文字列全体を host にコピー */ 50: } 51: 52: p = strchr(host, ':'); /* ホスト名の部分に ":" が含まれていたら */ 53: if ( p != NULL ){ 54: port = atoi(p+1); /* ポート番号を取得 */ 55: if ( port <= 0 ){ /* 数字でない (atoi が失敗) か、0 だったら */ 56: port = 80; /* ポート番号は 80 に決め打ち */ 57: } 58: *p = '\0'; 59: } 60: } else { 61: fprintf(stderr, "URL は http://host/path の形式で指定してください。\n"); 62: return 1; 63: } 64: } 65: 66: printf("http://%s%s を取得します。\n\n",host, path); 67: 68: /* ホストの情報(IPアドレスなど)を取得 */ 69: servhost = gethostbyname(host); 70: if ( servhost == NULL ){ 71: fprintf(stderr, "[%s] から IP アドレスへの変換に失敗しました。\n", host); 72: return 0; 73: } 74: 75: bzero((char *)&server, sizeof(server)); /* 構造体をゼロクリア */ 76: 77: server.sin_family = AF_INET; 78: 79: /* IPアドレスを示す構造体をコピー */ 80: bcopy(servhost->h_addr, (char *)&server.sin_addr, servhost->h_length); 81: 82: if ( port != 0 ){ /* 引数でポート番号が指定されていたら */ 83: server.sin_port = htons(port); 84: } else { /* そうでないなら getservbyname でポート番号を取得 */ 85: service = getservbyname("http", "tcp"); 86: if ( service != NULL ){ /* 成功したらポート番号をコピー */ 87: server.sin_port = service->s_port; 88: } else { /* 失敗したら 80 番に決め打ち */ 89: server.sin_port = htons(80); 90: } 91: } 92: /* ソケット生成 */ 93: if ( ( s = socket(AF_INET, SOCK_STREAM, 0) ) < 0 ){ 94: fprintf(stderr, "ソケットの生成に失敗しました。\n"); 95: return 1; 96: } 97: /* サーバに接続 */ 98: if ( connect(s, (struct sockaddr *)&server, sizeof(server)) == -1 ){ 99: fprintf(stderr, "connect に失敗しました。\n"); 100: return 1; 101: } 102: /* FILE 構造体を作成 */ 103: fp = fdopen(s, "r+"); 104: if ( fp == NULL ){ 105: fprintf(stderr, "fdopen に失敗しました。\n"); 106: return 1; 107: } 108: /* バッファリング OFF */ 109: setvbuf(fp, NULL, _IONBF, 0); 110: 111: /* サーバに送信 */ 112: fprintf(fp, "GET %s HTTP/1.0\r\n", path); 113: fprintf(fp, "Host: %s:%d\r\n", host, port); 114: fprintf(fp, "\r\n"); 115: 116: /* あとは受信して、表示するだけ */ 117: while (1){ 118: if ( fgets(buf, sizeof(buf), fp) == NULL ){ 119: break; 120: } 121: printf("%s", buf); 122: } 123: /* 後始末 */ 124: fclose(fp); 125: close(s); 126: 127: return 0; 128: }
ご意見・ご指摘は Twitter: @68user までお願いします。