こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

締切り済みの質問

C言語で作成するチャットについて

LinuxでC言語のチャットプログラムを作成しています。プログラムを作ってみましたがうまく動きません。悪い個所が分からないのでお力を貸して頂きたいです課題の提出が迫っているので、よろしくお願いいたします。
<server>
#include省略
#define PORT 5320
char *show_ip(char *ip_address);
int main(void){
int soc, acc, size, child[3],width,i,count,pos,ret;
char buffer[80];
struct sockaddr_in client, server;
struct sockaddr_storage from;
struct hostent *server_host;
socklen_t len;
fd_set mask;
char host_name[257];
int temp;
memset(host_name, 0, sizeof(host_name));
gethostname(host_name, 256);
server_host = gethostbyname(host_name);
printf("\n-------- informations of server ----------\n");
printf("Host name:%s\n", host_name);
printf("IP = %s\n", show_ip(server_host->h_addr));
printf("\n\n");
soc = socket(AF_INET, SOCK_STREAM, 0);
memset((char *)&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY);
server.sin_port = htons(PORT);
bind(soc, (struct sockaddr *)&server, sizeof(server));
size = sizeof(client);
listen(soc, 5);
for (i = 0; i < 3; i++){
child[i] = -1;
}
for (;;) {
FD_ZERO(&mask);
FD_SET(soc, &mask);
for (i = 0; i < 3; i++) {
if (child[i] != -1){
FD_SET(child[i], &mask);
if(child[i]+1 > width){
width = child[i]+1;
}
}
}
switch(select(soc+1, (fd_set *) &mask, NULL, NULL, NULL))
{
case -1:
perror("select");
break;
case 0:
break;
default:
if (FD_ISSET(soc, &mask)){
len = (socklen_t) sizeof(from);
if((acc = accept(child[i], (struct sockaddr *)&from, &len)) == -1){
if(errno != EINTR){
perror("accept");
}
}else{
if(child[0]&&child[1]&&child[2]!=-1){
fprintf(stderr,"child is full : cannot accept\n");
close(acc);
}else{
if(child[0] == -1){
child[0] = acc;
}else if(child[1] == -1){
child[1] = acc;
}else{
child[2] = acc;
}
}
}
}

for(i=0;i<3;i++){
if(child[i]!=-1){
if(FD_ISSET(child[i],&mask)){
memset(buffer, '\0', sizeof(buffer));
recv(child[i], buffer, 80, 0);
printf("%s> ", show_ip((char *)&client.sin_addr));
printf("%s", buffer);
if(strncmp(buffer, "exit", 4) == 0) break;
for(i=0;i<3;i++)
{
send(child[i],show_ip((char *)&client.sin_addr),80,0);
send(child[i],buffer,80,0);
close(child[i]);
}
(void) close(child[i]);
child[i] = -1;
}
}
break;
}
}
}
}
char *show_ip(char *ip_address){
static char ip[7];
char ipnum[4];
bcopy(ip_address, ipnum, 4);
sprintf(ip, "%u.%u.%u.%u",(unsigned char)ipnum[0], (unsigned char)ipnum[1], (unsigned char)ipnum[2],(unsigned char)ipnum[3]);
return ip;
}
<client>
#include省略
#define STDIN_FD 0
#define PORT 5320
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(AF_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) == 0){
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;
}

投稿日時 - 2013-01-24 22:02:44

QNo.7909131

すぐに回答ほしいです

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(2)

tsuduki123 さんによる指摘の他に気になったこととして、recvでのデータ受信を適切に行っていないように思われます。おそらく、1回のsendで送信されたデータは必ず1回のrecvで受信できる、という仮定のもとにプログラムを作成したのではないでしょうか。

このプログラムで使用しているTCPプロトコルでは、データがどこで切れるのかは保証されません。1回のsendで送信したデータが複数回のrecvに分割されて受信することもあれば、複数回のsendで送信されたものがまとめて1回のrecvで受信されることもあり得ます。
極端な例では、送信側で200バイトのデータをsendしたら受信側では1バイトのデータを200回recvすることになった、ということも可能性としてはゼロではないわけで、プログラムもそれを想定して作成する必要があります。

詳細については以下のURLを参照してください。
Windows Socket向けの解説ですが、LinuxでTCPソケットを使う場合も同じことが当てはまります。

Winsock 中級者向けの議論
3.4 - TCPのようなストリームプロトコルで、パケット単位の処理を強制するための正しい方法は?
http://www.kt.rim.or.jp/~ksk/wskfaq-ja/intermediate.html#packetscheme

TCP を有効に使うために
http://www.kt.rim.or.jp/~ksk/wskfaq-ja/articles/effective-tcp.html

ザ・間違いリスト
20.ストリームソケットで、メッセージフレームの区 切りが保持されると仮定すること
http://www.kt.rim.or.jp/~ksk/wskfaq-ja/articles/lame-list.html#item20

投稿日時 - 2013-01-25 22:40:26

ANo.1

うまく動かない内容がわからないので
ロジックじゃなくてぱっと見て、直した方がいいところの指摘だけ。。

server側
1. socket作成の時はgetadrinfo(3)を利用しましょう。こっちの方が簡単だし、都合いいこと多いです。
2. setsockopt()で SO_REUSEADDR を設定してあげた方がいいんじゃないかと思います。
3. select(2)に指定する最初の引数は待ち受けるfdの最大+1です。
acceptのところで最大を保存して利用するようにしましょう
4. acceptに指定する最初の引数はlistenしてbindで待ち受けているソケットです。
5. fd系は-1が帰ってきたとき、errnoがEINTRとEWOULDBLOCKかチェックしていずれかなら正常系でcontinueさせます。
6. チャットなら、クライアントからrecvしたバイト数を保持してsendするときに利用しないとおかしな文字列を送信することになると思います。
7. static char ip[7]; 桁がたりないんじゃないかな
8. サーバーからクライアントへ送信するとき、送信元のクライアントは除外してあげた方がいい気がする

client側
1. こちらもgetadrinfo(3)を利用しましょう。
2. サーバーから受信するまでサーバー送信できない気がする
selectの部分をもっと丁寧に

投稿日時 - 2013-01-25 00:19:41

あなたにオススメの質問