study

TCPを用いたチャットプログラム(C言語)

今回はTCPを用いたチャットプログラムを紹介したいと思います。

1.概要

TCPを用いたチャットプログラムを作成する。また独自の追加機能を実装させる。

2.プログラムの機能仕様

図1にclientのフローチャート、図2にserverのフローチャートを示す。

図1 clientのフローチャート

図2 serverのフローチャート

クライアントで行っていることは主にソケットの作成、接続相手の設定、接続、パケットの送受信である。今回はTCPでの接続のためソケットの第二引数にはSOCK_STREAMを用いる。ソケットを作成した後、図1で示しているように、scanfで入力したサーバーのIPアドレスをdestinationに代入する。connect関数を用いて、戻り値が0ならばサーバーと接続が完了して、チャットを始めることができる。
サーバーで行っていることは主にソケットの作成、接続待ちするIPアドレスとポート番号の設定、ソケットに名前を付ける(bindする)、接続待ちにする、クライアントからの接続を受け付ける、パケットの送受信である。サーバーと同じくソケットの第二引数にもSOCK_STREAMを用いる。ソケットを作成し、接続待ちにするIPアドレスとポート番号を設定した後、図2で示した流れになる。bind、listen、acceptの戻り値が-1でないときにパケットの送受信が可能になる。

図3 clientとserverのパケット送受信

図1で示したように、connect=0のとき、チャットが利用できるようになる。そのあとのパケットの送受信のやり取りを図3に示す。図3に示してあるように最初にクライアント側からメッセージを送信する。(1)クライアント側ではprintfを用いて、色の出力、文字の出力をする。(2)scanfを用いてbufに文字列を格納させる。(3)格納された文字列をsendを用いてserverに送信する。(4)server側でも色の出力、文字の出力を行う。(5)recvで受けっとった文字列bufをprintfを用いて出力させる。(6)scanfを用いてbufに新たな文字列を格納させる。(7)新たに格納された文字列をsendを用いてclientに送信する(8)client側で色の出力、文字の出力を行う。(9)recvで受け取った文字列bufをprintfを用いて出力させる。(1)~(9)の手順をwhile(1)を用いて無限ループさせる。

独自の機能としては、図1で示したように、サーバーのIPアドレスを選択して、接続することができる。また、図3で示したように、自分と相手が分かるように、printfで
”自分 ”、”相手 ”を出力させた。また、自分と相手が分かるように、色分けをした。自分の文字を入力した文字は青色(\x1b[44m)、相手が入力した文字は赤色(\x1b[41m)でprintfを用いて表示させた。

3.ソースコード

cliant6.cのソースコード

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

#define PORT 6789 

int main(){
  // IP アドレス,ソケット,sockaddr_in 構造体
  char destination[32];
  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){
    printf("\x1b[44m");
    printf("自分 ");
    scanf("%s",buf);
    //パケットの送信
    send(dstSocket, buf, 1024,0);
    //パケットの受信
    numrcv = recv(dstSocket, buf, 1024,0);
    printf("\x1b[41m");
    printf("相手 %s\n",buf);
  }
  close(dstSocket);
  return(0);
}

server6.cのソースコード

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>

#define PORT 6789

int main(){
  int i;
  int srcSocket; //自分
  int dstSocket; //相手
  // sockaddr_in 構造体
  struct sockaddr_in srcAddr;
  struct sockaddr_in dstAddr;
  int dstAddrSize = sizeof(dstAddr);
  // 各種パラメータ
  
  int numrcv;
  char buf[1024];

  while(1){
    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");
    dstSocket = accept(srcSocket, (struct sockaddr *)&dstAddr, &dstAddrSize);
    printf("%sから接続を受けました。\n",inet_ntoa(dstAddr.sin_addr));
    close(srcSocket);

    while(1){
      //パケットの受信
      numrcv = recv(dstSocket, buf, 1024,0);
      printf("\x1b[41m");
      printf("相手 %s\n",buf);
      printf("\x1b[44m");
      printf("自分 ");
      scanf("%s",buf);
      // パケットの送信
      send(dstSocket, buf, 1024,0);
    }
  }
  return(0);
}

 

4.マニュアル・利用例

clientとserverの実行例を図4,5に示す。また、clientとserverのファイル名はそれぞれcliant6.c、server6.cとする。

図4 cliant6.cの実行例

図5 server6.cの実行例

図4を見ても分かるようにサーバーのIPアドレスを入力することができる独自機能を加えた。図1ではサーバーのIPアドレスを127.0.0.3を使用しているが、127.0.0.2でも接続可能である。図5を見ても分かるように、clientのIPアドレスも表示させた。また、自分が入力した文字は青色に、相手が入力した文字は赤色に表示させた。

5.考察

工夫した点は二つある。一つ目が自分と相手を識別したことである。自分の入力した文字列にprinfを用いると同じ文字列が2回表示されるので、自分の入力した文字はprintfを用いずに、相手の文字列を表示させるときにprintfを用いた。また、図3で示したように色の識別では、printfで”自分 “を出力する前にprintfで青色を出力、同様にprintfで”相手 ”を出力する前にprintfで赤色を出力することで色による識別を可能にした。
二つ目が、IPアドレスの入力である。今回のプログラムではサーバーのIPアドレスを選択することができ、接続したときは、チャットをできるようにした。図1で示したようにconnect関数を用いることで、これらの機能を可能にした。
改良点としては、同時に2通以上送ることができない点である。図3に示したように文字列を入力したら、sendして相手にrecvさせて文字列を出力させる。次にrecvした側はscanfで文字列を入力してsendする。つまり、recvしないと文字入力ができずsendできないということである。この問題点によって、返信が返ってこないと返信することができないという問題が生じる。

6.まとめ

C言語を用いてtcpを用いたチャットプログラムを作成した。tcpによる接続なので、connect、socket、bind、listen、accept、recv、sendなどを用いた。また、自分と相手が分かりやすいように色の識別をした。サーバーのIPアドレスを入力して、接続するIPアドレスを選択できるようにした。問題点としては返信が返ってこないと返信することができないという点である。