Yuki Nakata's Blog

One color just reflects another

2016年06月

ラズベリーパイでechoサーバーを作ろう の続きです。

引き続きネットワークプログラミングです。ファイルを転送するプログラムです。

普通はftpとかfilezillaを使ってファイルを転送するわけですが、シンプルなものであればプログラミングで作れます。

前回は文字列を送受信するプログラムを作りました。

文字列をファイルに置き換えるとできます。

結局やっていることはソケットを使ってread, writeしているだけです。

ネットワークにつないでしまえば、後はどうにかなります。

サーバー側です。cpserver.c

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/stat.h>


#define PORT 9877

int main(){
  
  int i = 0;
  int srcSocket;
  int dstSocket;
  
  struct sockaddr_in srcAddr;
  struct sockaddr_in dstAddr;
  int dstAddrSize = sizeof(dstAddr);
  // 各種パラメータ
  int status;
  int numrcv;
  char buf[1024];
  int len = 0;
  int size = 0;
  char filename[64];
  int fp;
  unsigned long filesize = 0;
  unsigned long totalrcv = 0;

  // sockaddr_in 構造体のセット
  bzero((char *)&srcAddr, sizeof(srcAddr));
  srcAddr.sin_port = htons(PORT);
  srcAddr.sin_family = AF_INET;
  srcAddr.sin_addr.s_addr = INADDR_ANY;
    
  srcSocket = socket(AF_INET, SOCK_STREAM, 0);
  bind(srcSocket, (struct sockaddr *)&srcAddr, sizeof(srcAddr));
  listen(srcSocket, 1);
  // 接続の受付け
  printf("接続を待っています\nクライアントプログラムを動かして下さい\n");
  dstSocket = accept(srcSocket, (struct sockaddr *)&dstAddr, &dstAddrSize);
  printf("%s から接続を受けました\n",inet_ntoa(dstAddr.sin_addr));

  while(1) {
memset(filename, 0, sizeof(filename));
len = read(dstSocket, buf, 2);
if(len < 0) {
 printf("エラー: ファイル名のエラーです。\n");
 return -1;
}

size = buf[0] << 8;      
    size |= buf[1];
len = read(dstSocket, buf, 4);
if(len < 0) {
 printf("エラー: ファイルサイズのエラーです。\n");
 return -1;
}
filesize = buf[0] << 24;
filesize |= buf[1] << 16;
filesize |= buf[2] << 8;
filesize |= buf[3];
printf("filesize = %ld byte\n", filesize);

len = read(dstSocket, filename, size);
if (len < 0) {
 printf("エラー: ファイル名を取得できませんでした。");
 return -1;
} else if (!strcmp(filename, "quit")) {
 printf("プログラムを終了します。\n\n");
 return 0;
}
printf("filename = %s\n", filename);

/* 書き込みファイルを開く */
if ((fp = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) {
 printf("エラー ファイルを作成できません");
 return -1;
}

totalrcv = 0;
while(1){
 //パケットの受信
 numrcv = read(dstSocket, buf, 1024);
 //printf("%d %d\n", i, numrcv);
 if(numrcv <= 0 ){
break;
 }

 write(fp, buf, numrcv);
 totalrcv += numrcv;
 if(totalrcv >= filesize) {
printf("%s 転送完了\n\n", filename);
break;
 }
}
close(fp);
  }

  close(srcSocket);
  return(0);
}


クライアント側です。 cpclient.c
 
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>

#define PORT 9877 //サーバープログラムとポート番号を合わせてください
char buf[1024];
char filesize[4];

int main(){
  char destination[32] = "192.168.0.10";//サーバーのIPアドレスを入れて下さい。
  //ソケット,sockaddr_in 構造体
  int dstSocket;
  struct sockaddr_in dstAddr;
  struct hostent *hp;
  int    numrcv;
  char filename[100];
  int len = 0;
  int fp;
  int size;
  struct stat statBuf;

  //sockaddr_in 構造体のセット
  bzero((char *)&dstAddr, sizeof(dstAddr));
  dstAddr.sin_family = AF_INET;
  dstAddr.sin_port = htons(PORT);
  
  hp = gethostbyname(destination);
  bcopy(hp->h_addr, &dstAddr.sin_addr, hp->h_length);

  //ソケットの生成
  dstSocket = socket(AF_INET, SOCK_STREAM, 0);
  
  //接続
  if (connect(dstSocket, (struct sockaddr *)&dstAddr, sizeof(dstAddr)) < 0){
    printf("%s に接続できませんでした\n",destination);
    return(-1);
  }
  printf("%s に接続しました\n",destination);
  
  while(1) {
    len = 0;
    memset(filename, 0, sizeof(filename));

    printf("送信するファイル名を入力してください\n");
    printf("quitで終了します\n\n");
    scanf("%s",filename);

    if (!strcmp(filename, "quit")) {
      printf("->bye\n");
      write(dstSocket, "00quit", 6);
      close(dstSocket);
      break;
    }
    while(filename[len] != '\0') {
      len++;
    }
    len++;
    buf[0] = (len >> 8) & 0xFF;
    buf[1] = len & 0xFF;
  
    fp = open(filename, O_RDONLY);
    if (fp < 0) {
      printf("エラー: ファイルを開けません\n");
      return(-1);
    }
    
    
    fstat(fp, &statBuf);
    printf("filesize = %ld byte\n", statBuf.st_size);
    filesize[0] = (statBuf.st_size >> 24 ) & 0xFF;
    filesize[1] = (statBuf.st_size >> 16 ) & 0xFF;
    filesize[2] = (statBuf.st_size >> 8) & 0xFF;
    filesize[3] = statBuf.st_size & 0xFF;
    

    write(dstSocket, buf, 2);
    write(dstSocket, filesize, 4);
    write(dstSocket,filename, len);
    while (1){
      /* ファイルから読み込み */
      size = read (fp, buf, sizeof(buf));
      /* データが無ければループを抜ける */
      if (size <= 0) {
break;
      }
      //パケットの送信
      write(dstSocket, buf, size);
            
    }
    write(dstSocket,"filetransferred", sizeof("filetransferred"));
    printf("%s 転送完了しました\n\n",filename);
    close(fp);
  }
  close(dstSocket);
  return(0);
}

cpclientと同じディレクトリにファイルを置いておきます。
あとは実行してファイル名を指定するとをサーバー側にコピーすることができます。
 
実行例
./cpclient

送信するファイル名を入力してください
quitで終了します

crazy.mp3
filesize = 6211191 byte
crazy.mp3 転送完了しました
 

wgetのようなダウンロードプログラムを作ろう の続きです

ラズベリーパイを持っているなら、ネットワークプログラミングをしてみましょう。
echoサーバーとはネットワークにつながっているか確認するプログラムです。

TCP/IPネットワークプログラミング ここを改造しました。

変更点は何度も接続できるようにしたのと、クライエントからquit文字列を送ると切断できるようにしました。


echoserver.c 
ラズベリーパイで実行します。

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 9876

int main(){
    int i;
    int srcSocket;
    int dstSocket;
  
    struct sockaddr_in srcAddr;
    struct sockaddr_in dstAddr;
    int dstAddrSize = sizeof(dstAddr);
    // 各種パラメータ
    int status;
    int numrcv;
    char buf[1024];

  //sockaddr_in 構造体のセット
    bzero((char *)&srcAddr, sizeof(srcAddr));
    srcAddr.sin_port = htons(PORT);
    srcAddr.sin_family = AF_INET;
    srcAddr.sin_addr.s_addr = INADDR_ANY;
    
    srcSocket = socket(AF_INET, SOCK_STREAM, 0);
    bind(srcSocket, (struct sockaddr *)&srcAddr, sizeof(srcAddr));
    listen(srcSocket, 1);


while(1){
// 接続の受付け
    printf("接続を待っています\nクライアントプログラムを動かして下さい\n");
    dstSocket = accept(srcSocket, (struct sockaddr *)&dstAddr, &dstAddrSize);
    printf("%s から接続を受けました\n",inet_ntoa(dstAddr.sin_addr));
    
        
    while(1){
//パケットの受信
      numrcv = read(dstSocket, buf, 1024);
if(numrcv ==0 || numrcv ==-1 ){
close(dstSocket);
break;
}
if(!strcmp(buf, "quit")) {
write(dstSocket, "bye\n", 1024);
close(dstSocket);
break;
}
     
printf("変換前 %s", buf);
for (i = 0; i < numrcv; i++) {
if(isalpha(buf[i])) {
buf[i] = toupper(buf[i]);
}
}
      
// パケットの送信
    write(dstSocket, buf, 1024);
      fprintf(stdout,"> %s\n\n",buf);
}
}
close(srcSocket);
return(0);



echoclient.c 
PC側で実行します。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define PORT 9876 //サーバープログラムとポート番号を合わせてください

int main(){
char destination[32] = "192.168.0.10";//サーバーマシンのIPアドレスを入れます。

//ソケット,sockaddr_in 構造体
int dstSocket;
struct sockaddr_in dstAddr;

//struct sockaddr_in addr;
  struct hostent *hp;
  char   buf[1024];
int    numrcv;

// 相手先アドレスの入力と送る文字の入力
  //printf("サーバーマシンのIPは?:");
//scanf("%s", destination);
  
  //sockaddr_in 構造体のセット
bzero((char *)&dstAddr, sizeof(dstAddr));
dstAddr.sin_family = AF_INET;
dstAddr.sin_port = htons(PORT);
  
  hp = gethostbyname(destination);
bcopy(hp->h_addr, &dstAddr.sin_addr, hp->h_length);

//ソケットの生成
dstSocket = socket(AF_INET, SOCK_STREAM, 0);
  
  //接続
  if (connect(dstSocket, (struct sockaddr *)&dstAddr, sizeof(dstAddr)) < 0){
  printf("%s に接続できませんでした\n",destination);
    return(-1);
}
printf("%s に接続しました\n",destination);
printf("適当なアルファベットを入力してください\n");
  
while (1){
    scanf("%s",buf);
    //パケットの送信
    write(dstSocket, buf, 1024);
    //パケットの受信
    numrcv = read(dstSocket, buf, 1024);
if (!strcmp(buf, "bye\n")) {
printf("bye\n");
break;
}
    printf("→ %s\n\n",buf);
}
close(dstSocket);
return(0);
}



linuxにプログラマー用のフォントM+をインストールしよう

M+はシンプルで見やすい。これでいいです。

もう面倒くさいのでそのままそっくりコピペしておきます。


$sudo mkdir -p /usr/share/fonts/truetype/mplusipa

$wget http://jaist.dl.sourceforge.jp/mix-mplus-ipa/25997/mixfont-mplus-ipa-TrueType-20060520p1.tar.bz2

$tar jvxf mixfont-mplus-ipa-TrueType-20060520p1.tar.bz2

$cd mixfont-mplus-ipa-TrueType-20060520p1/opfc-ModuleHP-1.1.1_withIPAFonts_and_Mplus/fonts

$sudo cp *.ttf /usr/share/fonts/truetype/mplusipa

$sudo fc-cache -v  *.ttf

$sudo mkfontdir

$xset xp rehash

$fc-list


これでフォントのインストールの仕方も理解できました。

/usr/share/fonts/ 以下にttfをぶっこんで、登録すればいいんですね。


参考サイト
Ubuntu Linux 8.04 

いやー健康って大切です。年を取るにつれて実感するようになりました。

もう何があってもおかしくない年齢ですからね。

最近マインドフルネスというワードが注目を集めています。遅すぎかもしれませんが。

GoogleのようなIT企業が取り入れているわけですね。

人間のパフォーマンスを高めることができるようになります。

誰でも実践できます、しかも無料で。

石川善樹さんの動画が素晴らしいので張り付けておきます。


絶対見るべき。12分。以外な結末でした。

以下石川善樹さんのマインドフルネス動画4本

 
 

 

 

マインドフルネスは姿勢や呼吸を正す、睡眠時間をちゃんと取る、とか誰でもできる、ところが良いですね。

今後も要注目です。 

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

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を使いましょう、ということです。

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



 

↑このページのトップヘ