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 までお願いします。