たまにはプログラミングでもしてみまっか。

linuxユーザーでしたらwgetを使ったことがあると思います。

mywgetを作ってみましょう。30分コースです。

ネットワークプログラミングなわけですが、socketを使います。

普通linuxでファイルを扱うときにはopenしてread, writeします。socketはそのネットワーク版です。

socketを使うとネットワーク越しにread, writeできるわけです。linuxでは何でもファイル扱いです。

ハガキを送ることを考えてみましょう。相手の住所と名前が必要です。

socketの場合にはIPアドレスとポート番号が必要です。

yahoo.co.jpなどのドメインはgethostbyname関数一発でIPアドレスに変換できます。

ポート番号は80で固定です。


あとは普通アドレスはhttp://なんちゃらという形をしています。

これはhttpプロトコルを使うよ、ということです。

httpプロトコルではGETコマンドを送るとデータを返してくれるようになっています。

ネットワークプログラミングといってもこんなもんです。

さっそくソースコードを見てみましょう。
/* mywget

./mywget http://xxx/aaa.html
html, htm, zip, jpg, gifなどのようにファイル名を指定してください。

./mywget http://xxx/aaa/
スラッシュで終わる場合にはファイル名の関係からエラーになるようにしています。

*/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netdb.h>
#include<unistd.h>
#include<fcntl.h>


#define BUF_LEN 1024

int main(int argc, char *argv[])
{
int fd;
int s;
int i = 0;
long int diff = 0;
char *p;
char host[BUF_LEN];
char path[BUF_LEN];
char filename[BUF_LEN];

char host_path[BUF_LEN];
char send_buf[BUF_LEN];

struct hostent *servhost;
struct sockaddr_in server;
unsigned short port = 80;

if (argc != 2) {
printf("URLを指定してください。\n");

return -1;
}

if (strstr(argv[1], "http://") && sscanf(argv[1], "http://%s", host_path)) {
p = strchr(host_path, '/');

if( p != NULL) {
strcpy(path, p);
*p = '\0';
}

strcpy(host, host_path);

p = strrchr(path, '/');
if(p != NULL) {
strcpy(filename, ++p);
} else {
strcpy(filename, path);
}
} else {
printf("URLはhttp://host/pathの形式で指定してください。\n");
return -1;
}

printf("\nhost = %s\n", host);
printf("path = %s\n", path);
printf("filename = %s\n", filename);

servhost = gethostbyname(host);
if(servhost == NULL) {
printf("[%s]からIPアドレスの変換に失敗しました。\n", host);
return -1; 
}

printf("IP Address %d.%d.%d.%d\n", (u_char)servhost->h_addr[0], (u_char)servhost->h_addr[1], (u_char)servhost->h_addr[2], (u_char)servhost->h_addr[3]);
bzero(&server, sizeof(server));

server.sin_family = AF_INET;

bcopy(servhost->h_addr, &server.sin_addr, servhost->h_length);

server.sin_port = htons(port);
 
/* ソケット生成 */
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("ソケットの生成に失敗しました。\n");
return -1;
}

/* サーバーに接続 */
if ( connect(s, (struct sockaddr *)&server, sizeof(server)) == -1) {
printf("connectに失敗しました\n");
return -1;
}

/* httpプロコトル コマンド送信 */
sprintf(send_buf, "GET %s HTTP/1.0\r\n", path);
write(s, send_buf, strlen(send_buf));

sprintf(send_buf, "Host: %s:%d\r\n",  host, port);
write(s, send_buf, strlen(send_buf));

sprintf(send_buf, "\r\n");
write(s, send_buf, strlen(send_buf));

fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
if (fd < 0) {
printf("open error\n");
return -1;
}

p = NULL;
while(1) {
char buf[BUF_LEN];
int read_size;

read_size = read(s, buf, BUF_LEN);

if (read_size > 0) {
if( i == 0) {
if(strstr(buf, "200") && strstr(buf, "OK")) {
p = strstr(buf, "\r\n\r\n");
if ( p != NULL) {
diff = p - buf;
p += 4;
write(fd, p, read_size - diff - 4);
}
} else {
break;
printf("http error\n");
}
} else {
write(fd, buf, read_size);
}
} else {
break;
}
i++;
}
close(fd);
close(s);
return 0;


return 0;
}

面倒くさいのがGETコマンドを送るとレスポンスヘッダ(ゴミ)とデータ(これが欲しい)を返してきます。

レスポンスヘッダとは問題ないよ、とか時刻とかファイルサイズとかファイルタイプ(jpgだよー)とかから構成されています。

レスポンスヘッダとデータを一緒に送ってきます。なのでレスポンスヘッダとデータを分離する必要があります。

それがwhile文の中のi = 0のときの処理です。

"\r\n\r\n"がレスポンスヘッダの終わりのしるしなので、それ以前を除去しています。

愛着のあるmywgetを使いましょう、ということです。

ネットワークプログラミング 続きます。