ページ 11

初心者です。C言語のネットワークプログラムの問題なのですが、この時点からどう改善すれば正しく動くかわかりません。

Posted: 2011年7月13日(水) 04:26
by サスケ
こんにちは。初めてここを利用させていただきます。ほとんどC言語初心者なのですが、課題がわからなくて困っています。
正しく理解したいのですが、提出期限が迫っているため、皆様の協力をいただき、以下の問題を解いてほしいです。
課題を提出した後、ゆっくりと理解したいと考えたいと思っていますので今はこれを解決していただけるでしょうか。

以下、問題と例のプログラムになります。
回答よろしくお願いします。

サーバ,あるいはクライアント起動時にハンドルネームを登録し,そのハンドル@マシン名" がメッセージ前に表示されるようにしてください.例えば,zenith にいるユーザが「Sasuke」と登録した場合,
\Sasuke@zenith: "
と全てのプロセスウィンドウに表示されるようにします.ただし,自分の打ち込んだメッセージの前には何も表示しません.
さらに、セッションの途中でもハンドルネームを変更できるようにしてください.例えば,ユーザtaro が"ch jiro " と打つとハンドルネームがjiro となるようにします.
 
ということなのですが、どこを改造すればいいでしょうか。よくわからないので申し訳ないですけど、詳しい回答よろしくおねがいします。

以下、改造しきれていないプログラムになります。

\\\\mchat_server.c\\\\

コード:

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

#include "echo2_common.h"


#define ERROR(x) do {fprintf(stderr, "-"); perror(x); exit(1);} while(0)
#define NUMCLIENT 5
#define ERR -1
#define UNUSED (-1)

void usage(void);
char *command_name;

int main(int argc, char *argv[])
{
int reqsd;
int sd[NUMCLIENT+1];
struct sockaddr_in server; 
struct sockaddr_in client;
int fromlen;
int port;
char recv_buf[BUFSIZE];
char send_buf[BUFSIZE];
struct timeval timeout;
fd_set rfds;
int num_used_socket = 0;
int i, j;
int temp = 1;
int res;
int ret;

if ((command_name = rindex(argv[0], '/')) !=NULL)
command_name++;
else
command_name = argv[0];

if(argc != 2)
usage();

if( (reqsd = socket(PF_INET, SOCK_STREAM, 0)) == ERR) 
ERROR("server: socket");


if (setsockopt(reqsd, SOL_SOCKET, SO_REUSEADDR, (void *)&temp, sizeof(temp)))
ERROR("server:setsockopt"); 

memset((void*)&server, 0, sizeof(server));
server.sin_family = PF_INET;
sscanf(argv[1], "%d", &port); 
server.sin_port = htons(port);
server.sin_addr.s_addr = htonl(INADDR_ANY);

if(bind(reqsd, (struct sockaddr *)&server, sizeof(server)) == ERR) 
ERROR("server: bind");

if(listen(reqsd, 5) == ERR) 
ERROR("server: listen"); 

for (i=0; i<=NUMCLIENT; i++)
sd[i] = UNUSED;

do{
memset(recv_buf, '\0', BUFSIZE); 
memset(send_buf, '\0', BUFSIZE); 

FD_ZERO(&rfds);

if (reqsd != UNUSED ) FD_SET(reqsd, &rfds);
FD_SET(0, &rfds);
for (i=0; i<num_used_socket; i++){
if (sd[i] != UNUSED) FD_SET(sd[i], &rfds);
}

timeout.tv_sec = 0;
timeout.tv_usec = 1;

res = select(FD_SETSIZE, &rfds, NULL, NULL, &timeout);
if (res == ERR) ERROR("select");

if (FD_ISSET(reqsd, &rfds)){

memset((void *)&client, 0, sizeof(client));
fromlen = sizeof(client);

if ((sd[num_used_socket]
= accept(reqsd, (struct sockaddr *)&client, &fromlen))
== ERR )
ERROR("server: accept");
if (num_used_socket < NUMCLIENT)
num_used_socket++;
else {
strcpy(send_buf, "Server is too busy.\n");
WriteLine(sd[num_used_socket], send_buf);
WriteLine(sd[num_used_socket], "bye\n");
close(sd[num_used_socket]);
sd[num_used_socket] = UNUSED;
}
}

for(i=0; i<num_used_socket; i++){
if (sd[i] != UNUSED && FD_ISSET(sd[i],&rfds)){
ret = ReadLine(sd[i], recv_buf);

if (ret == ERR){
sprintf(send_buf, "client %d: connection closed.\n", i);
close(sd[i]);
sd[i] = UNUSED;
} else {

if (!strcmp(recv_buf, "quit\n")
|| !strcmp(recv_buf, "QUIT\n") ) {
strcpy(send_buf, recv_buf);
}

else if (!strcmp(recv_buf, "bye\n")
|| !strcmp(recv_buf, "BYE\n") ) {
sprintf(send_buf, "client %d: connection closed.\n", i);
close(sd[i]);
sd[i] = UNUSED; 
}
else sprintf(send_buf, "from client %d: %s", i, recv_buf);
}

if (strlen(send_buf) > 0 ) {


WriteLine(1, send_buf);

for (j=0; j<num_used_socket; j++){
if ((j != i) && (sd[j] != UNUSED)) {
WriteLine(sd[j], send_buf);
}
}
}

if (!strcmp(recv_buf, "quit\n")
|| !strcmp(recv_buf, "QUIT\n") ) {
for (j=0; j<num_used_socket; j++){
close(sd[j]); 
sd[j] = UNUSED;
}
close(reqsd);
exit(0);
}
}
}

if (FD_ISSET(0, &rfds)) {

if (ReadLine(0, recv_buf) == ERR) strcpy(recv_buf, "quit\n");

if (!strcmp(recv_buf, "quit\n") || !strcmp(recv_buf, "QUIT\n") )
strcpy(send_buf, recv_buf);
else
sprintf(send_buf, "from server: %s", recv_buf);

if (strlen(send_buf) > 0) {
for (j=0; j<num_used_socket; j++){
if (sd[j] != UNUSED)
WriteLine(sd[j], send_buf);
}
}

if (!strcmp(recv_buf, "quit\n") || !strcmp(recv_buf, "QUIT\n") ) {
for (j=0; j<num_used_socket; j++){
close(sd[j]);
sd[j] = UNUSED;
}
close(reqsd);
exit(0);
}
}
} while (1);

exit(0);
}


void usage(void)
{
fprintf(stderr, "Usage: %s PORT\n", command_name);
exit(1);
}
\\\\mchat_client.c\\\\

コード:

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

#include "echo2_common.h"


#define ERROR(x) do {fprintf(stderr, "-"); perror(x); exit(1);} while(0)
#define ERR -1


void usage(void);
char *command_name;

int main(int argc, char *argv[])
{ 
int sd;
struct sockaddr_in server;
struct hostent *hp;
int len;
int port;
char buf[BUFSIZE];
struct timeval timeout;
fd_set rfds; 
int ret; 

if ((command_name = rindex(argv[0], '/')) != NULL)
command_name++;
else
command_name = argv[0];

if (argc != 3)
usage();

if ((sd = socket(PF_INET, SOCK_STREAM, 0)) == ERR)
ERROR("client: socket");


memset((void*)&server, 0, sizeof(server));
server.sin_family = PF_INET;
port = atoi(argv[2]);
server.sin_port = htons(port);


if ((hp = gethostbyname(argv[1])) == NULL)
ERROR("client: gethostbyname");
memcpy(&(server.sin_addr), hp->h_addr_list[0], hp->h_length);

if (connect(sd, (struct sockaddr *)&server, sizeof(server)) == ERR)
ERROR("client: connect");

do {
memset(buf, '\0', BUFSIZE);

FD_ZERO(&rfds);

FD_SET(sd, &rfds);
FD_SET(0,&rfds);

timeout.tv_sec = 0;
timeout.tv_usec = 1;

if (select(sd+1, &rfds, NULL, NULL, &timeout) == ERR)
ERROR("select");

if (FD_ISSET(sd, &rfds)) {
if (ReadLine(sd, buf) == ERR) break;
WriteLine(1, buf);
if (strcmp(buf, "bye\n") || !strcmp(buf, "BYE\n")) break;
}

if (FD_ISSET(0, &rfds)) {
if (ReadLine(0, buf) == ERR) break;
WriteLine(sd, buf);
if (strcmp(buf, "bye\n") || !strcmp(buf, "BYE\n")) break;
}

} while (strcmp(buf, "quit\n") && strcmp(buf, "QUIT\n"));

close(sd);

exit(0);
}


void usage(void)
{
fprintf(stderr, "Usage: %s SERVER_NAME PORT\n", command_name);
exit(1);
}
\\\\echo2.common.h\\\\

コード:

#define BUFSIZE 1024
#define PORT 50951

int ReadLine(int sd, char *buf);

int WriteLine(int sd, char *buf);

Re: 初心者です。C言語のネットワークプログラムの問題なのですが、この時点からどう改善すれば正しく動くかわかりません。

Posted: 2011年7月13日(水) 14:24
by softya(ソフト屋)
初心者向けの課題とは思えませんので、何年ぐらいのC言語経験が前提の課題ですか?少なくとも2~3ヶ月の初心者がやるような課題ではありません。
あと次の点を明確にしてください。
・提出期限の日時
・自分で直したところと、課題としての固定部分。
・winsockで分からないところ。
・文法的に理解出来ないところ
それと、インデントとコメントを出来る限りお願いします。

Re: 初心者です。C言語のネットワークプログラムの問題なのですが、この時点からどう改善すれば正しく動くかわかりません。

Posted: 2011年7月13日(水) 17:23
by たろ
おもしろそうだったので腕試しと思って動かしてみましたが、いくつか不明点があります・・。

・OS環境はLinuxでしょうか?OS名やバージョンなど教えてください。
 (こちらはCentOS 5.5で動かしています)

・ReadLine/WriteLine関数の中身(定義)がありません。
 適当に作って動かしていますが、課題の固定部分ならソースの提示おねがいします。

・現ソースコードでチャットができません。
 1行入力したら終了してしまいます。ハンドルネームの実装どころではないです。
 「ハンドルネームの課題以外は、ちゃんと動作している」プログラムはありますか?
 それとも、まず「チャットできるプログラムに改造する」というのも課題でしょうか?

Re: 初心者です。C言語のネットワークプログラムの問題なのですが、この時点からどう改善すれば正しく動くかわかりません。

Posted: 2011年7月13日(水) 23:40
by サスケ
すみません。いろいろと至らない点があったようです。
OS環境はLinuxです。提出期限は7月20日

もうひとつWriteLine,ReadLineの定義プログラムを載せるのを忘れていました。

echo2 common.c

コード:

 \echo 共通関数( echo2 common . c )

#include <stdio  h>
#include <string.h>
#include "echo2 _common.h"
/ ..............................................................................................
データの読み込み
引数: ソケットのディスクリプタ, 文字列保管バッファ
返値: 受信した文字数( バイト数) エラー時..1
.............................................................................................. /
int ReadLine(int sd, char  buf )
{
int i = 0 ;
unsigned char ch ;
/ ソケットデータの読み取り/
while(read(sd , &ch , 1) == 1) {
buf [ i++] = ch ;
if ( i > BUFSIZE-1 ) return -1; / バッファ溢れ防止/
if( ch == '\n ' ) { / LF の場合終了/
buf [ i ] = ' \ 0 ' ;
return i ;
}
}
buf [ i ] = ' \ 0 ' ;
return -1;
}
/ ..............................................................................................
データの書きだし
引数: ソケットのディスクリプタ, 文字列保管バッファ
返値: 送信した文字数( バイト数) エラー時..1
.............................................................................................. /
int WriteLine (int sd , char  buf )
{
int a ;
/ データの送信/
a = write( sd , buf , strlen(buf ) ) ;
return a ;
}
[\code]

これまでに書いたプログラムは以前に提出して、OKをもらったので、全て固定でいいはずなのですが、1行で終わるのはまだ先生からの回答がないため、誤っているかもしれません。というより実装したら確かに1行で終わってしまいました。orz

また至らない点があったらすみません。よろしくお願いします。

Re: 初心者です。C言語のネットワークプログラムの問題なのですが、この時点からどう改善すれば正しく動くかわかりません。

Posted: 2011年7月14日(木) 04:06
by たろ
1行で終わってしまう問題は、mchat_client.cを少し直すだけで解決しました。
strcmp()の判定のしかたが間違っています。

mchat_client.c:75行目、81行目

コード:

if (!strcmp(buf, "bye\n") || !strcmp(buf, "BYE\n")) break;
では、この状態から、最初の課題の改造をしてみます。
①起動時にハンドルネームを登録する(サーバとクライアントどちらも)
②メッセージの前に「ハンドル@マシン名: 」を表示する(ただし自分のメッセージの前には表示しない)
③実行中でも「ch ハンドルネーム」の入力でハンドルネームを変更できるようにする。

なお、改造方針は『元のソースコードはできるだけ変更せずに、少ない改造で済ませる』ことにします。
また完成プログラム全体ではなく、改造部分だけを載せますので、あとは完成させてみてください。

まず①から。
ハンドルネームを格納する文字列バッファを作りましょう。

コード:

char handle_name[256] = "handle_name";
「起動時に登録する」のやり方はいろいろありますが、例えば起動時の引数にハンドルネームを追加します。
mchat_server.c

コード:

if(argc != 3)
    usage();

strcpy( handle_name, argv[2] );

コード:

fprintf(stderr, "Usage: %s PORT HANDLE_NAME\n", command_name);
mchat_client.c

コード:

if (argc != 4) 
    usage();

strcpy( handle_name, argv[3] );

コード:

fprintf(stderr, "Usage: %s SERVER_NAME PORT HANDLE_NAME\n", command_name);
続いて②。
まずマシン名をどうやって取得するか?いろいろ方法がありますが、gethostname()が簡単だと思います。
次のような関数を作り、共通で使えるようにecho2_common.cに定義、echo2_common.hにプロトタイプ宣言を書きます。
echo2_common.c

コード:

/*
 * マシン名(短いホスト名)を取得する
 */
void get_machine_name( char *name, size_t size )
{
    char *dot;
    /* 長いホスト名を取得 */
    gethostname( name, size );
    /* 最初のドット以降を削除 */
    dot = strchr( name, '.' );
    if( dot ) *dot = '\0';
}
この関数を使って、マシン名を保持する変数に取得しましょう。
mchat_server.c/mchat_client.c

コード:

char machine_name[256] = "machine_name";

get_machine_name( machine_name, sizeof(machine_name) );
ハンドル名とマシン名の変数ができたので、メッセージの前に「ハンドル@マシン名: 」を入れます。
mchat_server.c

コード:

else
    //sprintf(send_buf, "from server: %s", recv_buf);
    sprintf(send_buf, "%s@%s: %s", handle_name, machine_name, recv_buf);
mchat_client.c

コード:

if (ReadLine(0, buf) == ERR) break;
WriteLine(sd, handle_name);
WriteLine(sd, "@");
WriteLine(sd, machine_name);
WriteLine(sd, ": ");
WriteLine(sd, buf);
if (!strcmp(buf, "bye\n") || !strcmp(buf, "BYE\n")) break;
最後に③・・・ですが、長くなったので一旦おわります。
もし自分で改造できそうならやってみてください。

わからない点など、コメントもらえれば答えます。

ちなみに、「エラーチェックや異常な入力の考慮は必要十分か」「読みやすく上手く構造化されているか」
といった点にはまったく気を使っていませんので悪しからず・・。

Re: 初心者です。C言語のネットワークプログラムの問題なのですが、この時点からどう改善すれば正しく動くかわかりません。

Posted: 2011年7月18日(月) 23:58
by サスケ
返事が遅れてすみません。
この課題は学内でしかできなくて、平日しか開いていないので、試すこともまだできていません。
にもかかわらず明日中にやらなくては期限に間に合わなくなってしまうため、今はまだどこがわからない、などもままなりません。

③も知りたいです。
とりあえず提出してから明後日以降、自分でわからないところが出たら質問させてください。

Re: 初心者です。C言語のネットワークプログラムの問題なのですが、この時点からどう改善すれば正しく動くかわかりません。

Posted: 2011年7月20日(水) 00:45
by たろ
③の改造例です。(変更部分のみ)

・標準入力(0)を読み取ったあと、
・もし「ch 文字列」だったら、ハンドルネームバッファ(handle_name)に文字列をコピーする。
・それ以外は、今まで通りの処理を行う。

mchat_server.c

コード:

            if (ReadLine(0, recv_buf) == ERR) strcpy(recv_buf, "quit\n");

            if (strncmp(recv_buf,"ch ",3)==0 && recv_buf[3]!='\0' && recv_buf[3]!='\n') {
                /* ハンドルネーム変更 */
                chomp(recv_buf);
                strcpy(handle_name, recv_buf+3);
            } else {
                if (!strcmp(recv_buf, "quit\n") || !strcmp(recv_buf, "QUIT\n") )
                    strcpy(send_buf, recv_buf);
                else
                    //sprintf(send_buf, "from server: %s", recv_buf);
                    sprintf(send_buf, "%s@%s: %s", handle_name, machine_name, recv_buf);

                if (strlen(send_buf) > 0) {
                    for (j=0; j<num_used_socket; j++){
                        if (sd[j] != UNUSED)
                            WriteLine(sd[j], send_buf);
                    }
                }
            }

            if (!strcmp(recv_buf, "quit\n") || !strcmp(recv_buf, "QUIT\n") ) {
mchat_client.c

コード:

            if (ReadLine(0, buf) == ERR) break;
            if (strncmp(buf,"ch ",3)==0 && buf[3]!='\0' && buf[3]!='\n') {
                /* ハンドルネームの変更 */
                chomp(buf);
                strcpy(handle_name, buf+3);
            } else {
                WriteLine(sd, handle_name);
                WriteLine(sd, "@");
                WriteLine(sd, machine_name);
                WriteLine(sd, ": ");
                WriteLine(sd, buf);
            }
            if (!strcmp(buf, "bye\n") || !strcmp(buf, "BYE\n")) break;
echo_common.c

コード:

/*
 * 改行文字を削除する
 */
void chomp(char *p)
{
    for (;*p;p++) {
        if (*p=='\n') {
            *p = '\0';
            return;
        }
    }
}