チャットプログラムについて
Posted: 2013年2月07日(木) 03:04
現在チャットプログラムを作っているのですが、複数のクライアントから一つのサーバに文字を送り、それを受け取ったサーバが接続しているクライアント全員に送信するにはこのプログラムをどう改良したらよいでしょうか?read_and_replyのままでは1人にしか返さないのは理解できるのですが、具体的にどう改良したらよいのかわかりませんのでアドバイスをお願いします。
サーバー側
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <time.h>
#include <netdb.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define BUF_LEN 256 /* バッファのサイズ */
/* クライアントの情報を保持する構造体 */
typedef struct CLIENT_INFO {
char hostname[BUF_LEN]; /* ホスト名 */
char ipaddr[BUF_LEN]; /* IP アドレス */
int port; /* ポート番号 */
time_t last_access; /* 最終アクセス時刻 */
} CLIENT_INFO;
CLIENT_INFO client_info[FD_SETSIZE];
int listening_socket;
struct sockaddr_in sn;
/*-----------------------------------------------------
引数でリスニングソケットを受け取り、accept し、
client_info に新しいクライアントの情報を登録する。
戻り値は新しいクライアントのソケットディスクリプタ。
ただしエラー発生時は -1 を返す。
-----------------------------------------------------*/
int
accept_new_client(int sock){
int len;
int new_socket;
struct hostent *peer_host;
struct sockaddr_in peer_sin;
len = sizeof(sn);
new_socket = accept(listening_socket, (struct sockaddr *)&sn, &len);
if ( new_socket == -1 ){
perror("accept");
exit(1);
}
if ( new_socket > FD_SETSIZE-1 ){
return -1;
}
/* ここから先はデバッグ用の情報取得 */
len = sizeof(peer_sin);
getpeername(new_socket,
(struct sockaddr *)&peer_sin, &len);
peer_host = gethostbyaddr((char *)&peer_sin.sin_addr.s_addr,
sizeof(peer_sin.sin_addr), AF_INET);
/* ホスト名 */
strncpy(client_info[new_socket].hostname, peer_host->h_name,
sizeof client_info[new_socket].hostname);
/* IP アドレス */
strncpy(client_info[new_socket].ipaddr, inet_ntoa(peer_sin.sin_addr),
sizeof client_info[new_socket].ipaddr);
/* ポート番号 */
client_info[new_socket].port = ntohs(peer_sin.sin_port);
/* 現在時刻を最終アクセス時刻として記録しておく */
time(&client_info[new_socket].last_access);
printf("接続: %s (%s) ポート %d ディスクリプタ %d 番\n",
client_info[new_socket].hostname,
client_info[new_socket].ipaddr,
client_info[new_socket].port,
new_socket);
return new_socket;
}
/*-----------------------------------------------------
引数でソケットディスクリプタを受け取り、そのソケットから
read(2) で文字列を読み込み、文字列をそのままクライアントに
送信する。read(2) の戻り値をそのまま返す。
-----------------------------------------------------*/
int
read_and_reply(int sock){
int read_size;
char buf[BUF_LEN];
read_size = read(sock, buf, sizeof(buf)-1);
if ( read_size == 0 || read_size == -1 ){
printf("%s (%s) ポート %d ディスクリプタ %d 番からの接続が切れました。\n",
client_info[sock].hostname,
client_info[sock].ipaddr,
client_info[sock].port,
sock);
close(sock);
client_info[sock].last_access = 0;
} else {
/* 文字列終端を \0 で terminate */
buf[read_size] = '\0';
printf("%s (%s) ポート %d ディスクリプタ %d 番からのメッセージ: %s",
client_info[sock].hostname,
client_info[sock].ipaddr,
client_info[sock].port,
sock,
buf);
int i;
for(i = 0 ;i<FD_SETSIZE; i++);
{
write(sock(i), buf, strlen(buf));
time(&client_info[sock].last_access);
} }
return read_size;
}
int
main(){
fd_set target_fds;
fd_set org_target_fds;
int sock_optval = 1;
int port = 5000;
/* リスニングソケットを作成 */
listening_socket = socket(AF_INET, SOCK_STREAM, 0);
/* ソケットオプション設定 */
if ( setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR,
&sock_optval, sizeof(sock_optval)) == -1 ){
perror("setsockopt");
exit(1);
}
/* アドレスファミリ・ポート番号・IPアドレス設定 */
sn.sin_family = AF_INET;
sn.sin_port = htons(port);
sn.sin_addr.s_addr = htonl(INADDR_ANY);
if ( bind(listening_socket, (struct sockaddr *)&sn, sizeof(sn)) < 0 ){
perror("bind");
exit(1);
}
if ( listen(listening_socket, SOMAXCONN) == -1 ){
perror("listen");
exit(1);
}
printf("ポート %d を見張ります。\n", port);
/* 監視対象のディスクリプタ一覧をゼロクリア */
FD_ZERO(&org_target_fds);
/* リスニングソケットを監視対象に追加 */
FD_SET(listening_socket, &org_target_fds);
while (1){
int i;
time_t now_time;
struct timeval waitval; /* select に待ち時間を指定するための構造体 */
waitval.tv_sec = 2; /* 待ち時間に 2.500 秒を指定 */
waitval.tv_usec = 500;
/* org_target_fds を target_fds にコピー */
memcpy(&target_fds, &org_target_fds, sizeof(org_target_fds));
select(FD_SETSIZE, &target_fds, NULL, NULL, &waitval);
/* ソケットが読み出し可能か順にチェック */
for ( i=0 ; i<FD_SETSIZE ; i++ ){
if ( FD_ISSET(i, &target_fds) ){
printf("ディスクリプタ %d 番が読み込み可能です。\n", i);
if ( i == listening_socket ){
int new_sock;
/* 新しいクライアントがやってきた */
new_sock = accept_new_client(i);
if ( new_sock != -1 ){
/* 監視対象に新たなソケットを追加 */
FD_SET(new_sock, &org_target_fds);
}
} else {
int read_size;
read_size = read_and_reply(i);
if ( read_size == -1 || read_size == 0 ){
/* 切断したソケットを監視対象から削除 */
FD_CLR(i, &org_target_fds);
}
}
}
}
time(&now_time); /* 現在時刻を取得 */
for ( i=0 ; i<FD_SETSIZE ; i++ ){
/* 監視対象でないソケットはスキップ */
if ( ! FD_ISSET(i, &org_target_fds) ) continue;
/* リスニングソケットはスキップ */
if ( i == listening_socket ) continue;
if ( now_time-60 > client_info.last_access ){
printf("%s (%s) ポート %d ディスクリプタ %d 番から60秒以上アクセスがありません。切断します。\n",
client_info.hostname,
client_info.ipaddr,
client_info.port,
i);
close(i);
FD_CLR(i, &org_target_fds);
}
}
}
close(listening_socket);
return 0;
}
クライアント側
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define STDIN_FD 0
int select_func(int sockfd);
void err_func(char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
int sockfd, len;
char buf[BUFSIZ];
struct sockaddr_in serv;
unsigned short port;
if(argc != 3)
{
printf("usage: progname serv_ip serv_port\n");
exit(EXIT_FAILURE);
}
if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
err_func("socket");
serv.sin_family = PF_INET;
port = (unsigned short)atoi(argv[2]);
serv.sin_port = htons(port);
inet_aton(argv[1], &(serv.sin_addr));
if(connect(sockfd, (struct sockaddr *)&serv, sizeof(struct sockaddr_in)) < 0)
err_func("connect");
do
{
if(select_func(sockfd) == sockfd)
{
len = recv(sockfd, buf, BUFSIZ, 0);
buf[len] = '\0';
printf("<- %s\n", buf);
}
else
{
len = read(STDIN_FD, buf, BUFSIZ);
len = send(sockfd, buf, len, 0);
}
}while(strncmp(buf, "EXIT\r\n", 6) != 0 && strncmp(buf, "EXIT\n", 5) != 0);
close(sockfd);
return 0;
}
int select_func(int sockfd)
{
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);
FD_SET(STDIN_FD, &rfds);
if(select(sockfd+1, &rfds, NULL, NULL, NULL) < 0)
err_func("select");
if(FD_ISSET(STDIN_FD, &rfds))
return STDIN_FD;
return sockfd;
}
サーバー側
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <time.h>
#include <netdb.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define BUF_LEN 256 /* バッファのサイズ */
/* クライアントの情報を保持する構造体 */
typedef struct CLIENT_INFO {
char hostname[BUF_LEN]; /* ホスト名 */
char ipaddr[BUF_LEN]; /* IP アドレス */
int port; /* ポート番号 */
time_t last_access; /* 最終アクセス時刻 */
} CLIENT_INFO;
CLIENT_INFO client_info[FD_SETSIZE];
int listening_socket;
struct sockaddr_in sn;
/*-----------------------------------------------------
引数でリスニングソケットを受け取り、accept し、
client_info に新しいクライアントの情報を登録する。
戻り値は新しいクライアントのソケットディスクリプタ。
ただしエラー発生時は -1 を返す。
-----------------------------------------------------*/
int
accept_new_client(int sock){
int len;
int new_socket;
struct hostent *peer_host;
struct sockaddr_in peer_sin;
len = sizeof(sn);
new_socket = accept(listening_socket, (struct sockaddr *)&sn, &len);
if ( new_socket == -1 ){
perror("accept");
exit(1);
}
if ( new_socket > FD_SETSIZE-1 ){
return -1;
}
/* ここから先はデバッグ用の情報取得 */
len = sizeof(peer_sin);
getpeername(new_socket,
(struct sockaddr *)&peer_sin, &len);
peer_host = gethostbyaddr((char *)&peer_sin.sin_addr.s_addr,
sizeof(peer_sin.sin_addr), AF_INET);
/* ホスト名 */
strncpy(client_info[new_socket].hostname, peer_host->h_name,
sizeof client_info[new_socket].hostname);
/* IP アドレス */
strncpy(client_info[new_socket].ipaddr, inet_ntoa(peer_sin.sin_addr),
sizeof client_info[new_socket].ipaddr);
/* ポート番号 */
client_info[new_socket].port = ntohs(peer_sin.sin_port);
/* 現在時刻を最終アクセス時刻として記録しておく */
time(&client_info[new_socket].last_access);
printf("接続: %s (%s) ポート %d ディスクリプタ %d 番\n",
client_info[new_socket].hostname,
client_info[new_socket].ipaddr,
client_info[new_socket].port,
new_socket);
return new_socket;
}
/*-----------------------------------------------------
引数でソケットディスクリプタを受け取り、そのソケットから
read(2) で文字列を読み込み、文字列をそのままクライアントに
送信する。read(2) の戻り値をそのまま返す。
-----------------------------------------------------*/
int
read_and_reply(int sock){
int read_size;
char buf[BUF_LEN];
read_size = read(sock, buf, sizeof(buf)-1);
if ( read_size == 0 || read_size == -1 ){
printf("%s (%s) ポート %d ディスクリプタ %d 番からの接続が切れました。\n",
client_info[sock].hostname,
client_info[sock].ipaddr,
client_info[sock].port,
sock);
close(sock);
client_info[sock].last_access = 0;
} else {
/* 文字列終端を \0 で terminate */
buf[read_size] = '\0';
printf("%s (%s) ポート %d ディスクリプタ %d 番からのメッセージ: %s",
client_info[sock].hostname,
client_info[sock].ipaddr,
client_info[sock].port,
sock,
buf);
int i;
for(i = 0 ;i<FD_SETSIZE; i++);
{
write(sock(i), buf, strlen(buf));
time(&client_info[sock].last_access);
} }
return read_size;
}
int
main(){
fd_set target_fds;
fd_set org_target_fds;
int sock_optval = 1;
int port = 5000;
/* リスニングソケットを作成 */
listening_socket = socket(AF_INET, SOCK_STREAM, 0);
/* ソケットオプション設定 */
if ( setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR,
&sock_optval, sizeof(sock_optval)) == -1 ){
perror("setsockopt");
exit(1);
}
/* アドレスファミリ・ポート番号・IPアドレス設定 */
sn.sin_family = AF_INET;
sn.sin_port = htons(port);
sn.sin_addr.s_addr = htonl(INADDR_ANY);
if ( bind(listening_socket, (struct sockaddr *)&sn, sizeof(sn)) < 0 ){
perror("bind");
exit(1);
}
if ( listen(listening_socket, SOMAXCONN) == -1 ){
perror("listen");
exit(1);
}
printf("ポート %d を見張ります。\n", port);
/* 監視対象のディスクリプタ一覧をゼロクリア */
FD_ZERO(&org_target_fds);
/* リスニングソケットを監視対象に追加 */
FD_SET(listening_socket, &org_target_fds);
while (1){
int i;
time_t now_time;
struct timeval waitval; /* select に待ち時間を指定するための構造体 */
waitval.tv_sec = 2; /* 待ち時間に 2.500 秒を指定 */
waitval.tv_usec = 500;
/* org_target_fds を target_fds にコピー */
memcpy(&target_fds, &org_target_fds, sizeof(org_target_fds));
select(FD_SETSIZE, &target_fds, NULL, NULL, &waitval);
/* ソケットが読み出し可能か順にチェック */
for ( i=0 ; i<FD_SETSIZE ; i++ ){
if ( FD_ISSET(i, &target_fds) ){
printf("ディスクリプタ %d 番が読み込み可能です。\n", i);
if ( i == listening_socket ){
int new_sock;
/* 新しいクライアントがやってきた */
new_sock = accept_new_client(i);
if ( new_sock != -1 ){
/* 監視対象に新たなソケットを追加 */
FD_SET(new_sock, &org_target_fds);
}
} else {
int read_size;
read_size = read_and_reply(i);
if ( read_size == -1 || read_size == 0 ){
/* 切断したソケットを監視対象から削除 */
FD_CLR(i, &org_target_fds);
}
}
}
}
time(&now_time); /* 現在時刻を取得 */
for ( i=0 ; i<FD_SETSIZE ; i++ ){
/* 監視対象でないソケットはスキップ */
if ( ! FD_ISSET(i, &org_target_fds) ) continue;
/* リスニングソケットはスキップ */
if ( i == listening_socket ) continue;
if ( now_time-60 > client_info.last_access ){
printf("%s (%s) ポート %d ディスクリプタ %d 番から60秒以上アクセスがありません。切断します。\n",
client_info.hostname,
client_info.ipaddr,
client_info.port,
i);
close(i);
FD_CLR(i, &org_target_fds);
}
}
}
close(listening_socket);
return 0;
}
クライアント側
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define STDIN_FD 0
int select_func(int sockfd);
void err_func(char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
int sockfd, len;
char buf[BUFSIZ];
struct sockaddr_in serv;
unsigned short port;
if(argc != 3)
{
printf("usage: progname serv_ip serv_port\n");
exit(EXIT_FAILURE);
}
if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
err_func("socket");
serv.sin_family = PF_INET;
port = (unsigned short)atoi(argv[2]);
serv.sin_port = htons(port);
inet_aton(argv[1], &(serv.sin_addr));
if(connect(sockfd, (struct sockaddr *)&serv, sizeof(struct sockaddr_in)) < 0)
err_func("connect");
do
{
if(select_func(sockfd) == sockfd)
{
len = recv(sockfd, buf, BUFSIZ, 0);
buf[len] = '\0';
printf("<- %s\n", buf);
}
else
{
len = read(STDIN_FD, buf, BUFSIZ);
len = send(sockfd, buf, len, 0);
}
}while(strncmp(buf, "EXIT\r\n", 6) != 0 && strncmp(buf, "EXIT\n", 5) != 0);
close(sockfd);
return 0;
}
int select_func(int sockfd)
{
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);
FD_SET(STDIN_FD, &rfds);
if(select(sockfd+1, &rfds, NULL, NULL, NULL) < 0)
err_func("select");
if(FD_ISSET(STDIN_FD, &rfds))
return STDIN_FD;
return sockfd;
}