質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.77%

C言語でクライアント・サーバで通信をするプログラムについて

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 1,550

maikeru

score 56

C言語でソケットを利用するプログラムを書いていて、クライアントプログラムでファイル名を送信して、サーバプログラムが受け取ったファイル名のファイルを開きその中身を送信するプログラムを作成しました。
クライアントプログラムの実行結果は次のようになり、クライアント、サーバプログラムを動作させ、クライアントでファイル名を送信するとサーバ側でファイルを開きその中身をループして送信してくれるのですがサーバがファイルの中身をすべて送信し終わってもクライアントはwhileループでrecv()で待ち続けているためか次のファイル名の入力ができない状態になっています。そのため次の実行例では^Cで強制終了しています。どのように修正すれば正しいプログラムになるでしょうか。

input filename: test.txt
aaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbccccc
ddddekekkkkkkkk
000fffff44444fccccc
^C


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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <fcntl.h>

#define PORT 8888
#define BUFSIZE 128

int main(int argc, char *argv[]){
    struct sockaddr_in addr;
    int fd, n;
    char buf[BUFSIZE];

    if((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
        perror("socket");
        exit(-1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if(connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0){
        perror("connect");
        exit(-1);
    }

    while(1){
        printf("input filename: ");
        fgets(buf, BUFSIZE, stdin);
        send(fd, buf, BUFSIZE, 0);
        while((n = recv(fd, buf, BUFSIZE, 0)) > 0) // ファイルをすべて読み出してもここから抜けられない?
            write(1, buf, n);
    }

    close(fd);

    return 0;
}

サーバ側のプログラム

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <fcntl.h>

#define PORT 8888
#define BUFSIZE 128

int main(int argc, char *argv[]){
    struct sockaddr_in saddr, caddr;
    int fd1, fd2, fp, n, ret, len;
    char buf[BUFSIZE];
    char *c;

    if((fd1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
        perror("socket");
        return -1;
    }

    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(PORT);
    saddr.sin_addr.s_addr =htonl(INADDR_ANY);

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

    if(listen(fd1, 5)){
        perror("listen");
        return -1;
    }

    if((fd2 = accept(fd1, (struct sockaddr*)&caddr, &len)) < 0){
        perror("accept");
        return -1;
    }


    while(1){
        recv(fd2, buf, BUFSIZE, 0);
        c = strchr(buf, '\n'); if(c != NULL) *c = '\0'; //remove '\n'
        printf("filename : [%s]\n", buf);
        if((fp = open(buf, O_RDONLY, 0644)) < 0){
            perror("open");
            exit(-1);
        }
        while((n = read(fp, buf, sizeof(buf))))
            send(fd2, buf, n, 0);
    }

    close(fd1); close(fd2);

    return 0;
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

0

recv は(戻り値としてバイト数を返す通り)一度の呼び出しで何バイト取り込めるか分かりません.
テスト時はたまたま短い文字列(ファイル名)だったので一度に読めたようですが, 最悪何度 recv しても 1バイトずつしか取り込めない可能性もあるということです.
もしクライアントが 'test.txt' と送信してもサーバの recv(fd2, buf, BUFSIZE, 0); で( recv が 3 を返し) buf に 'tes' までしか入っていなかったら, どうなりますか.
そのような状態でファイル名やファイルデータを受け取るには, 2つの方法があります.

1) まずサイズを送信/受信し, 次にそのサイズ分のデータを送信/受信する

ファイル名を送信する場合: 8,'test.txt'
ファイルの中身を送信する場合: (ファイルサイズ),'aaaaa...bbbb....'

2) 「終わりを示すバイト」を定義し, 送信時は送信後にそのバイトを送信し, 受信時はそのバイトが来るまで受信する(「終わりを示すバイト」は捨てる)

(「終わりを示すバイト」='\0' )
ファイル名を送信する場合: 'test.txt','\0'
ファイルの中身を送信する場合: 'aaaaa...bbbb....','\0'

1 の場合, 例えばサイズを表す部分を 4 バイトの int とすると, 8文字を送るために12バイトが必要になります. 2 の方法なら 9 バイトです.
そしてもちろん, そのサイズを表す int も4バイトが一度に得られるとは限りません. byte ならその心配はありませんが, 長さとしては 255 までとなります.
2 の場合, 「終わりを示すバイト」がデータとして使えなくなるため, バイナリファイルには使えません. (エスケープを使えば出来ます.)
クライアントからサーバへのファイル名送信には 2 を使い, サーバからクライアントへのデータの送信には 1 を使うといった混在は可能です.

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

何らかの方法でファイルの送信終了を通知しましょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

(違うところに回答してしまいました。申し訳ありません)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.77%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る