ソケットを用いたチャットプログラム(複数クライアント対応)

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
rock

ソケットを用いたチャットプログラム(複数クライアント対応)

#1

投稿記事 by rock » 7年前

c言語でチャットができるプログラムを作成しています.
現在このソースでは,サーバとクライアント1対1でしかメッセージのやり取りができません.
実行する際はターミナルを3つ開き,サーバ,クライアントno.1,クライアントno.2として実行し,
クライアントno.1からサーバにメッセージを送信→サーバがメッセージを受け通る→クライアントno.2にメッセージを転送
という風に動作するように修正を加えたいです.
また,3つ目のクライアントがサーバに接続した際,すでに参加しているクライアントに『no.3が参加しました』というメッセージと,そのクライアントの情報(ホスト名,IPアドレス,ポート番号)を通知し,あるクライアントが接続を解除した際にもそのクライアントの情報を残りのクライアントに通知できるように修正したいのですが,どのように変更してよいかわからず質問させていただきました.
修正するのはサーバのプログラムのみです.
宜しくお願いします.


サーバ側プログラム

コード:

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

#define PORT_NO 8001      
#define MAX_CLIENTS 10   
#define Err(x) {fprintf(stderr,"server.- ");perror(x);exit(0);}
#define MAX_MSG 400
#define MAX_HOSTNAME 20

/* クライアント構造体:接続したクライアントの情報を格納する構造体 */
typedef struct client {
  struct client *next;     /* 次の要素へアドレス */
  int so;                  /* 接続確立したクライアントのソケットディスクリプタ */
  struct sockaddr_in addr; /* クライアントのIPアドレス,ポート番号 */
  char * h_name;           /* クライアントのホスト名    */
} Client;

/* クライアントリストメタ情報:クライアント構造体を要素とするリスト構造の制御情報 */
typedef struct meta {
  Client *pclient_head; /* 情報を格納している要素リスト(クライアントリスト)の先頭アドレス */
  Client *pclient_tail; /* 情報を格納している要素リスト(クライアントリスト)の終端アドレス */
  int num_clients;      /* 情報を格納している要素数(=接続クライアント数)*/
} Meta_Info;

typedef enum _ftype {
  UsrMsg, AdminMsg
} ftype;

typedef struct _Frame {
  ftype type;
  char hostname[MAX_HOSTNAME];
  char msg[MAX_MSG];
} Frame;

Meta_Info *init_MetaInfo(void);
void sockfd_set(fd_set *, int, Meta_Info *);
void accept_to_connect(int, fd_set *, Meta_Info *);
void receive_data(fd_set *, Meta_Info *);
void echo_back(int, Frame *, int);
char *getclientname(struct sockaddr_in *);

int main(void){

  Meta_Info *pmeta;
  int connsd;                /* 接続要求受信用のソケットディスクリプタ*/
  fd_set readfds;            /* 読み込み用のファイルディスクリプタの集合体 */
  struct sockaddr_in saddr;  /* サーバのbind用の構造体 */
  struct sockaddr_in caddr;  /* 接続要求したクライアントのアドレス情報 */                 
  int n;

  /* クライアントリストメタ情報の初期化 */
  pmeta = init_MetaInfo();

  /* 接続要求受信用のソケットの生成 */
  if((connsd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
    perror("socket");
    exit(1);
  }

  /*saddrの初期化*/
  bzero((char *)&saddr, sizeof(saddr));
  
  /* 利用するネットワークの設定  */
  saddr.sin_family=AF_INET;                /* IP */
  saddr.sin_addr.s_addr=htonl(INADDR_ANY); /* すべてのIPネットワーク*/
  saddr.sin_port = htons(PORT_NO);         /* ポート番号*/

  if(bind(connsd,(struct sockaddr *)&saddr,sizeof(saddr))<0){
    perror("bind");
    exit(1);
  }

  /* 接続要求を受けるバッファを用意する listen() */
  /* 第2引数は大きめの値にする */
  if(listen(connsd, 5) == -1){
    perror("listen");
    exit(1);
  }

  /* メインループ */
  while(1) {

    /* 
   接続要求受信用ソケットディスクリプタと接続クライアントからの
   データ受信用ソケットディスクリプタをreadfdsにセットする
    */
    sockfd_set(&readfds, connsd, pmeta);

    /*
     * 接続要求受信とデータ受信を確認指示をする
     */
    if((n = select(FD_SETSIZE,&readfds,NULL,NULL,NULL)) == -1){
      perror("select");
      exit(1);
    }

    /* 先ず,優先して接続要求があれば,接続処理を行う */
    accept_to_connect(connsd, &readfds, pmeta);
	    
    /* 次に,データ受信があれば,データ受信処理を行う */
    receive_data(&readfds, pmeta);
 
  }

}


Meta_Info *init_MetaInfo(void) 
{
  Client *p;
  Meta_Info *m;
  int i;

  m = (Meta_Info *)malloc(sizeof(Meta_Info));
  if (m == NULL) {
    perror("malloc");
    exit(-1);
  }

  /* Meta_Infoの初期化 */
  m->pclient_head = NULL;
  m->pclient_tail = NULL;
  m->num_clients = 0;

  return(m); /* 確保したクライアント構造体メタ情報のアドレスを返す */

}

void sockfd_set(fd_set *rfds, int connsd, Meta_Info *pmeta) 
{
  Client *p, *pprev;
  int i;
  /* readfdsの初期化*/
  FD_ZERO(rfds);

  /* 最初に,接続要求受信用ソケットディスクリプタをセットする */
  FD_SET(connsd, rfds);

  /* 接続クライアントとのソケットディスクリプタをセットする */
  for(p = pmeta->pclient_head; p != NULL; p = p->next) {
    FD_SET(p->so, rfds);
  }

}

void accept_to_connect(int connsd, fd_set *rfds, Meta_Info *pmeta)
{
  int len, i;
  int so;
  struct sockaddr_in caddr;
  struct in_addr in;
  Client *p;

  if (FD_ISSET(connsd, rfds)) { /* 接続要求の受信確認 */

    len=sizeof(caddr);
    so=accept(connsd, (struct sockaddr *)&caddr, (socklen_t *)&len);

    if (pmeta->num_clients < MAX_CLIENTS) {/* 接続クライアント数のチェック */

      /* accept()でデータ送受信用のソケットディスクリプタが返されるのでこれをクライアント構造体に保存する */
      p = (Client *)malloc(sizeof(Client));      /* クライアント構造体の領域を確保 */
      if (p == NULL) {
	perror("malloc");
	exit(-1);
      }
      p->so = so;                                 /* データ送受信用ソケットディスクリプタ*/
      memcpy(&(p->addr), &caddr, sizeof(caddr));  /* クライアントのIPアドレス,ポート番号*/
      p->h_name = getclientname(&caddr);          /* クライアントのホスト名 */

      /* クライアントリストの終端に新規接続クライアント構造体を繋ぐ */
      if (pmeta->pclient_tail != NULL) {
	p->next = pmeta->pclient_tail->next; 
	pmeta->pclient_tail->next = p;
      } else {
	p->next = pmeta->pclient_tail;
      }
      pmeta->pclient_tail = p;
      /* pclient_head == NULL の場合(クライアントリストが空),pclient_headに繋ぐ */
      if (pmeta->pclient_head == NULL) {
	p->next = pmeta->pclient_head;
	pmeta->pclient_head = p;
      }
      pmeta->num_clients++;                  /* 接続クライアント数の更新 */
      memcpy(&in.s_addr, &(caddr.sin_addr), 4);
      fprintf(stdout, "Join client: %s IP=%s Port=%d)\n", p->h_name, inet_ntoa(in), ntohs(caddr.sin_port));
    } else {
      memcpy(&in.s_addr, &(caddr.sin_addr), 4);
      fprintf(stderr, "Reject client: IP=%s Port=%d\n", inet_ntoa(in), ntohs(caddr.sin_port));
      close(so);
    }
  }

}


void receive_data(fd_set *rfds, Meta_Info *pmeta) 
{
  Client *p, *q, *prev;
  int msglen;
  struct in_addr in;
  Frame buf;

  /* クライアントリストに繫がっている各クライアント構造体のソケットディスクリプタからのデータ受信を確認する.*/
  for(p = pmeta->pclient_head, prev = NULL; p != NULL; p = p->next){
    if (FD_ISSET(p->so, rfds)) {
      bzero(&buf, sizeof(buf));
      msglen = read(p->so, &buf, sizeof(buf));

      switch(msglen) {
      case -1:
	perror("read");
	exit(-1);
      case 0: /* クライアントが接続を切断 */
	memcpy(&in.s_addr, &(p->addr.sin_addr), 4);
	fprintf(stdout, "Leave client: %s IP=%s Port=%d\n",
		p->h_name, inet_ntoa(in), ntohs(p->addr.sin_port));
	close(p->so);

	/* クライアントリストから削除 */
	if (prev == NULL) { /* リストの先頭の場合 */
	  pmeta->pclient_head = p->next;
	} else {
	  prev->next = p->next;
	}
	if (p == pmeta->pclient_tail) { /* リストの終端の場合 */
	  pmeta->pclient_tail = prev;
	}

	pmeta->num_clients--;
	free(p->h_name);
	free(p);
	p = prev; /* pが示す領域はリストから削除されて領域が解放されたので
                     pをクライアントリストの前方クライアントの領域を示すようにする*/
	break;
      default: /* データ受信成功 */
	memcpy(&in.s_addr, &(p->addr.sin_addr), 4);
	fprintf(stdout, "client %s IP=%s Port=%d:\"%s:%s\"\n",
		p->h_name, inet_ntoa(in), ntohs(p->addr.sin_port), buf.hostname, buf.msg);
	echo_back(p->so, &buf, msglen);

	break;
      }
    }
    if (p == NULL) break; /* 領域解放してリストから空になる場合 */
    prev = p; /* 次のループの処理のためにリスト前方のクライアントとして保持 */
  }
}

/*修正すべき?と思われる箇所*/
void echo_back(int so, Frame *buf, int cbuf) 
{
  
  write(so, buf, cbuf);
  
}

char Unknown[]="Unknown";

char *getclientname(struct sockaddr_in *addr)
{
  struct hostent *host;
  char *rc;

  host = gethostbyaddr(&addr->sin_addr, 4, AF_INET);

  if (host == NULL) return Unknown; 

  if ((rc = (char *)malloc(strlen(host->h_name)+1)) == NULL) {
    perror("malloc");
    exit(-1);
  }
  strcpy(rc, host->h_name);

  return(rc);

}


クライアント側プログラム

コード:

/*イベント案内通知(INETドメインーコネクション型)*/
/*各種定義*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <ncursesw/curses.h>
#include <string.h>

#define PORT_NO 8001
#define MAX_MSG 400
#define MAX_HOSTNAME 20

typedef struct _Cwindow {
  WINDOW *inWin;
  WINDOW *outWin;
  int y, x, line;
  int M_color, U_color, A_color;
} Cwindow;

typedef enum _ftype {
  UsrMsg, AdminMsg
} ftype;

typedef struct _Frame {
  ftype type;
  char hostname[MAX_HOSTNAME];
  char msg[MAX_MSG];
} Frame;

void cliepro(int);
Cwindow *initScreen(void);
void setStringToTopWin(Cwindow *, char *);
void getStringFromTopWin(Cwindow *, char *, int);
void setStringToBodyWin(Cwindow *, char *);
void setFrameToBodyWin(Cwindow *, Frame *);
int sendFrame(int, char *, Frame *);
void setErrorToBodyWin(Cwindow *, char *);

/*メインルーチン(クライアント)*/
int main(int argc, char **argv)
{

  int sofd;                   /* ソケット記述子*/
  struct hostent *shost;      /* hostent構造体*/
  struct sockaddr_in sv_addr; /* sockaddr_in構造体 */

  /*ソケットの作成(TCP) */
    
  sofd=socket(AF_INET,SOCK_STREAM,0);
  if(sofd<0) {
    perror("socket");
    exit(-1);
  }

  /*サーバのアドレスを取得 */
  shost = gethostbyname(argv[1]);
  if (shost == NULL) {
    perror("gethostbyname");
    exit(-1);
  }

  /*サーバのアドレスを設定*/
  bzero((void *)&sv_addr,sizeof(sv_addr));
  sv_addr.sin_family = AF_INET;
  sv_addr.sin_port = htons(PORT_NO);
  memcpy((void *)&sv_addr.sin_addr,(void *)shost->h_addr,shost->h_length);
  
  /*ソケットの接続要求*/
  if (connect(sofd, (struct sockaddr *)&sv_addr, sizeof(sv_addr))<0) {
    perror("connect");
    exit(-1);
  }

  cliepro(sofd);
  
  close(sofd);
  exit(0);
}

/*処理ルーチン(クライアント)*/
void cliepro(int sofd)
{
  int cc,nbyte, MAXRMSG;
  char smsg[MAX_MSG];
  Frame sndFrame, rcvFrame;
  fd_set readfds;
  int n;
  Cwindow *Cwin;
  char hostname[MAX_HOSTNAME];

  if (gethostname(hostname, sizeof(hostname)) < 0) {
    perror("gethostname");
    exit(-1);
  }
  
  Cwin = initScreen();

  while(1) {
    /* readfdsを初期化 */
    FD_ZERO(&readfds);
    /* 標準入力をreadfdsにセットする*/
    FD_SET(0, &readfds);
    /* ソケットをreadfdsにセットする */
    FD_SET(sofd, &readfds);

    /* 入力要求 */
    setStringToTopWin(Cwin, "Enter Message: ");

    /* 標準入力とソケットのデータを検査する */
    if ((n = select(FD_SETSIZE, &readfds, NULL, NULL, NULL)) == -1) {
      endwin();
      perror("select");
      exit(-1);
    }

    /* 標準入力からのデータがあれば,データを処理する */
    if (FD_ISSET(0, &readfds)) {
      memset(&sndFrame, 0, sizeof(sndFrame));
      getStringFromTopWin(Cwin, sndFrame.msg, sizeof(sndFrame.msg));
      setStringToBodyWin(Cwin, sndFrame.msg);
      if (sendFrame(sofd, hostname, &sndFrame) < 0) {
	setErrorToBodyWin(Cwin, "Connection Down");
	endwin();
	return;
      }
    }

    /* ソケットにデータ受信があれば,データを処理する */
    if (FD_ISSET(sofd, &readfds)) {
      memset(&rcvFrame, 0, sizeof(rcvFrame));
      cc = recv(sofd, &rcvFrame, sizeof(rcvFrame), 0);
      switch(cc) {
      case -1:
	endwin();
	perror("recv");
	exit(-1);
      case 0:
	setErrorToBodyWin(Cwin, "Connection Down");
	endwin();
	return;
      default:
	setFrameToBodyWin(Cwin, &rcvFrame);
      }
    }

  }
	
}

Cwindow *initScreen(void)
{
  Cwindow *Cwin;
  WINDOW *H1,*H2;

  if (initscr() == NULL) {
    fprintf(stderr, "initscr() error\n");
    exit(-1);
  }

  Cwin = (Cwindow *)malloc(sizeof(Cwindow));
  if (Cwin == NULL) {
      perror("malloc");
      exit(-1);
  }

  start_color();

  getmaxyx(stdscr, Cwin->y, Cwin->x);

  H1 = newwin(1, Cwin->x, 0, 0);
  Cwin->inWin = newwin(2, Cwin->x, 1, 0);
  H2 = newwin(1, Cwin->x, 3, 0);
  Cwin->outWin = newwin(Cwin->y - 4, Cwin->x, 4, 0);
  scrollok(Cwin->inWin, TRUE);
  scrollok(Cwin->outWin, TRUE);

  wattrset(H1, A_BOLD);
  wattrset(H2, A_BOLD);
  mvwprintw(H1, 0, 0, "----[Send Message]-----------");
  mvwprintw(H2, 0, 0, "----[Chat Messages]----------");
  wrefresh(H1);
  wrefresh(H2);

  Cwin->M_color = 1;
  init_pair(Cwin->M_color, COLOR_WHITE, COLOR_BLACK);
  Cwin->U_color = 2;
  init_pair(Cwin->U_color, COLOR_GREEN, COLOR_BLACK);
  Cwin->A_color = 3;
  init_pair(Cwin->A_color, COLOR_RED, COLOR_BLACK);

  Cwin->line = 0;

  return Cwin;

}

void setStringToTopWin(Cwindow *Cwin, char *msg)
{

  werase(Cwin->inWin);
  mvwprintw(Cwin->inWin, 0, 0, msg);
  wrefresh(Cwin->inWin);

}

void getStringFromTopWin(Cwindow *Cwin, char *msg, int msg_len)
{

  wgetnstr(Cwin->inWin, msg, msg_len);
  wrefresh(Cwin->inWin);

}

void setStringToBodyWin(Cwindow *Cwin, char *msg)
{

  wattrset(Cwin->outWin, COLOR_PAIR(Cwin->M_color));

  if (Cwin->line < Cwin->y - 4) {
    mvwprintw(Cwin->outWin, Cwin->line, 0, "own:%s", msg);
    Cwin->line++;
  } else {
    wscrl(Cwin->outWin, 1);
    mvwprintw(Cwin->outWin, Cwin->y - 5, 0, "own:%s", msg);
  }
  wrefresh(Cwin->outWin);

}

void setFrameToBodyWin(Cwindow *Cwin, Frame *rcvFrame)
{

  switch(rcvFrame->type) {
  case UsrMsg:
    wattrset(Cwin->outWin, COLOR_PAIR(Cwin->U_color));
    break;
  case AdminMsg:
    wattrset(Cwin->outWin, COLOR_PAIR(Cwin->A_color));
    break;
  default:
    wattrset(Cwin->outWin, A_NORMAL);
  }

  if (Cwin->line < Cwin->y - 4) {
    mvwprintw(Cwin->outWin, Cwin->line, 0, "%s:%s", rcvFrame->hostname, rcvFrame->msg);
    Cwin->line++;
  } else {
    wscrl(Cwin->outWin, 1);
    mvwprintw(Cwin->outWin, Cwin->y - 5, 0, "%s:%s", rcvFrame->hostname, rcvFrame->msg);
  }
  wrefresh(Cwin->outWin);

}

int sendFrame(int sofd, char *hostname, Frame *sndFrame)
{
  int rc;

  sndFrame->type = UsrMsg;
  strcpy(sndFrame->hostname, hostname);
  rc = send(sofd, sndFrame, sizeof(Frame), 0);

  return rc;

}

void setErrorToBodyWin(Cwindow *Cwin, char *err)
{

  wattrset(Cwin->outWin, A_STANDOUT);
  if (Cwin->line < Cwin->y - 4) {
    mvwprintw(Cwin->outWin, Cwin->line, 0, "Error:%s", err);
    Cwin->line++;
  } else {
    wscrl(Cwin->outWin, 1);
    mvwprintw(Cwin->outWin, Cwin->y - 5, 0, "Error:%s", err);
  }
  wrefresh(Cwin->outWin);

  sleep(1);

}


“C言語何でも質問掲示板” へ戻る