2013年5月14日火曜日

反転された文字列を返すreverseサーバー

前々回に紹介したechoサーバーと、前回紹介した文字列反転プログラムを組み合わせて、クライアントから送られてきた文字列を逆さにして返すreverseサーバーを作ってみましょう。


解答例:reverse_server.c


/* 受け取った文字列を逆にして返すreverseサーバー */

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

int main()
{
int sockfd, acceptfd; 
int len; 
char buf[256];
struct sockaddr_in server, client; 

/* ソケットを作る */
sockfd = socket(PF_INET, SOCK_STREAM, 0);

/* ソケットを初期化する */
memset(&server, 0, sizeof(server)); 
server.sin_family = PF_INET; 
server.sin_port   = htons(7000); 
server.sin_addr.s_addr = htonl(INADDR_ANY); 
bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr_in));

/* クライアントからの接続を待つ */
listen(sockfd, 5);

/* クライアントとの接続を確立する */
memset(&client, 0, sizeof(client)); 
len = sizeof(client); 
acceptfd = accept(sockfd, (struct sockaddr*)&client, &len);

/* データを読みとる */
read (acceptfd, buf, sizeof(buf));
printf("accept data: %s\n", buf);

/* 受け取った文字列を反転させる */
int i;
int length;
length = strlen(buf);
for(i = 0; i < (length/2); i++){
  char tmp;
  tmp = buf[i];
  buf[i] = buf[length - i - 1];
  buf[length - i - 1] = tmp;
}

/* データを書き込む */
write (acceptfd, buf, strlen(buf));
write (acceptfd, "\n", 1);

/* 接続を終了する */
close(acceptfd); 
close(sockfd); 

return (0); 

}


使い方はechoサーバーと同じです

文字列を反転するプログラム

文字列を逆さにするプログラムをC言語で作ってみたいと思います

 どのような手順を踏むのかというと……


  1. まず、適当な長さの文字列(文字型の配列)を用意する
  2. 標準入力から受け取ったデータを用意した配列に格納する
  3. 何文字分あるかを測る(n文字分あったとする)
  4. 1文字目とn文字目、2文字目とn-1文字目と、順番に交換していく
おおまかに言うとこのようになります


解答例:reverse.c

#include <stdio.h>
#include <string.h>

int main()
{
  char hoge[256];

  printf("文字列を入力してください\n"); scanf("%s", hoge);

  int i;
  int length;
  char tmp;
  length = strlen(hoge);
  for(i = 0; i < (length/2); i++){
    tmp = hoge[i];
    hoge[i] = hoge[length - i - 1];
    hoge[length - i - 1] = tmp;
  }

  printf("%s\n", hoge);

  return (0);
}


なお、文字列の長さを測る関数strlenは、NULL文字(\0)の直前までの長さを測ってくれるため、特別NULL文字について意識する必要はありません

C言語でechoサーバーをつくる

echoサーバーとは、「クライアントが送ってきた文字列をそのまま返す」サーバーのことです
特に驚くこともない、面白みのないプログラムのように思えますが、この単純なプログラムにはシステムプログラミングやネットワークプログラミングに必要なエレメントが詰まっています(と、思います)

おおまかな流れとしては

  1. ソケット(クライアントからの接続を受け付ける窓口のようなもの)をつくる
  2. ソケットを初期化する
  3. クライアントが繋いでくるのを待ち、接続を確立する
  4. クライアントから送られてきた文字列データを読み取る
  5. 読み取ったデータと全く同じものをクライアントの標準出力へ書き込む
  6. 接続を終了する
といったかんじです

それでは、実際にソースコードを見てみましょう


解答例:echo_server.c

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

int
main()
{
int sockfd, acceptfd; 
int len; 
char buf[1024];
struct sockaddr_in server, client; 

/* ソケットを作る */
sockfd = socket(PF_INET, SOCK_STREAM, 0);

/* ソケットを初期化する */
memset(&server, 0, sizeof(server)); 
server.sin_family = PF_INET; 
server.sin_port   = htons(7000); 
server.sin_addr.s_addr = htonl(INADDR_ANY); 
bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr_in));

/* クライアントからの接続を待つ */
listen(sockfd, 5);

/* クライアントとの接続を確立する*/
memset(&client, 0, sizeof(client)); 
len = sizeof(client); 
acceptfd = accept(sockfd, (struct sockaddr*)&client, &len);

/* データを読み込む */
read (acceptfd, buf, sizeof(buf));
printf("accept data: %s\n", buf); 

/* データを書き込む */
write (acceptfd, buf, strlen(buf));

/* 接続を閉じる */
close(acceptfd); 
close(sockfd); 

return (0); 

}

ちなみに、インクルードファイルそれぞれが何のために必要かを示すと……


<sys/types.h>  → socket(), listen(), accept(), read(), write()
<sys/socket.h> → socket(), listen(), accept(), read(), write()
<sys/uio.h>, <unistd.h>   → read(), write()
<netinet/in.h> → struct sockaddr_in 
<string.h> → memset()

上記のようになります


動作を確認するには、上のソースコードをコンパイルして生成された実行ファイルを起動した後、同じマシン上で

telnet localhost 7000

と打ち込み、telnetを起動すれば、echoサーバーとの接続が確立されます

C言語によるファイルコピーコマンド

C言語でファイルコピーコマンド(cpコマンド)を実装します
コマンドライン引数によって、コピー元ファイルとコピー先ファイルを指定しています
コピー先ファイルが存在しない場合は自動的に生成します

使い方は 実行ファイル名 [コピー元ファイル名] [コピー先ファイル名] です


解答例:mycp.c(実行ファイル名:mycp)


#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

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

  char c;

  if (argc < 3){
    printf("usage:./mycp [source_file] [target_file]\n");
  }

  i_file = open(argv[1], O_RDONLY);
  o_file = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);

  while (read(i_file, &c, 1) > 0)
    write(o_file, &c, 1);

  close(i_file);
  close(o_file);

  return (0);
}

C言語によるlsコマンド

前回に引き続き、代表的(?)なUNIXコマンドの一つであるlsをC言語で実装していきます
今回もなるべくシステムコールを使うこと前提で書いています
とくにオプションとかは付けずに、ディレクトリ内のファイル名一覧を表示するだけの簡単なものです


解答例:myls.c(実行ファイル名:myls)


/* 簡易lsコマンド */

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  DIR *dir;
  struct dirent *dp;
  char path[64];

  if(argc <= 1) {
    strcpy(path, ".");
  }else{
    strcpy(path,argv[1]);
  }

  dir = opendir(path);

  if(dir == NULL) {
    perror("opendir");
    exit(-1);
  }

  for(dp = readdir(dir); dp != NULL; dp = readdir(dir)) {   
    printf("%s\n",dp -> d_name); 
  }
  closedir(dir);

  return (0);
}

システムコールを使ったcatコマンド

ポピュラーなUNIXコマンドの一つであるcatコマンドをC言語で実装します
条件は……

  1. コマンドライン引数を使う
  2. システムコールを使う(open,read,write,close)
  3. printf,puts,fputs,fopen,fdopenなどのライブラリ関数の使用は禁止
ちなみに、引数としてとれるファイル名は1つだけです


解答例:mycat.c(実行ファイル名:mycat)


/* システムコールを使ったcatコマンド */

#include <stdio.h> 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
  int fd;
  char buf[256];
  int len;

  if (argc != 2) {
    printf("usage:./mycat [filename]\n");
    return (1);
  }

  fd = open(argv[1], O_RDONLY);

  if (fd == -1) {
    perror("open");
    return (1);
 }

  while (1) {
    len = read(fd,buf,sizeof(buf));

    if (len > 0){
      write (1, buf, len);
    } else if (len == 0){
      break;
    } else
    perror("read");
    return (1);
    }
  }
  close(fd);
  return (0);
}