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

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

ただいまの
回答率

89.98%

C言語 winsock sendデータを送ると文字化けする クライアントはRuby

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 375

kazuyakazuya

score 123

6時間格闘した末に分からなかったので解決方法またはヒントをお願いします。

winsock(C言語)でサーバープログラムを作りました。
クライアントはRubyで作りました。(エンコーディングの話はあとで・・・)
 
内容としては

1 サーバーがクライアントが接続してくるまで待ち受ける
2  クライアントがサーバーへ接続すると、サーバー側でコマンドを打てるようになる。
3  打ったコマンド(サーバー)の実行結果をクライアントへ引き渡す
4   クライアント側で表示

 

起こっている状況

サーバーサイド

#include <stdio.h>
#include <winsock2.h>

int main(void) {

    WSADATA wsaData;
    SOCKET sock0; 
    SOCKET sock; 
    struct sockaddr_in addr;
    struct sockaddr_in client;
    //ポート設定
    int len;
    int port;
    printf("ポートを指定してください。\n");
    scanf_s("%d", &port, 16);
    printf("指定されたポート番号は・・・%d\n", port);

    WSAStartup(MAKEWORD(2, 0), &wsaData); //Winsockの初期化

    sock0 = socket(AF_INET, SOCK_STREAM, 0);
    // ソケット設定の構造体の設定
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.S_un.S_addr = INADDR_ANY;

    bind(sock0, (struct sockaddr*) & addr, sizeof(addr));

    listen(sock0, 5);

    int n = 1; //接続回数変数初期化
    while (1) {
        len = sizeof(client);
        //sock = accept(sock0, (struct sockaddr*) & client, &len);
        if ((sock = accept(sock0, (struct sockaddr*) & client, &len)) < 0) {
            perror("エラーが発生しました。");
            exit(1);
        }else {
            printf("接続に成功しました。これは%d回目です。\n", n);
        }

              //コマンドを送る
              char cmd[128];
              printf("コマンドを入力してください。");
              scanf_s("%s",&cmd,128);
              FILE *fp;
              if((fp=_popen(cmd,"r"))== NULL){
                perror("エラーが発生しました。・・・");
              }

             char buf[128];
             while (fgets(buf,sizeof(buf), fp) != NULL) {
                 send(sock, buf, sizeof(buf), 0);
            }

            _pclose(fp);
            closesocket(sock);
            n++; //カウント1プラス
    }
    WSACleanup(); //winsockの終了


    return 0;
}

クライアントサイド(注意 Rubyです。)

require 'socket'
sock = TCPSocket.open("localhost","55555")

while cmd = sock.gets#1行ずつコマンド実行結果を受け取る。
puts cmd  #putsで表示させる。
end

sock.close


イメージ説明
tasklistを実行させた結果です。

見てのとおり文字化けしています。
クライアントサイドで文字化けしている。
RubyではデフォルトでUTF-8なので
ASCIIにできないかと試行錯誤したのですが
参考記事
chrメソッドを使った方法
一通り試しましたがダメでした・・・。

だが、本当にクライアントサイドに問題があるのか
ということで

while (fgets(buf, sizeof(buf), fp) != NULL) {
            send(sock, "hello",5, 0);←ここ
        }


send関数で渡すデータをコマンド実行結果ではなく
hello文字列を送ったところ

hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello


文字化けせずに表示されます。
さらに

char mojiretu[] = "HelloWorld";
        send(sock,mojiretu,sizeof(mojiretu),0);


実行結果

HelloWorld


文字化けせずに表示できていることから
Rubyクライアントサイドに問題があるのではなく

char buf[128];
 while (fgets(buf,sizeof(buf), fp) != NULL) {
                 send(sock, buf, sizeof(buf), 0);
            }


これに問題があるのかと思うのですが・・・。

fgets関数
イメージ説明
ファイルポインタが指すストリームからbuf配列へ
データを格納しているのだと思うのですが
それでも、なぜ文字化けが起こるのかがわかりません。

本当にfgetsらへんが原因なのか
クライアントサイドのエンコードに問題があるのか・・・

分からないので教えてください。

追記

回答で指摘をいただいた
sizeを76にした結果です。
イメージ説明

char buf[5];
        while (fgets(buf, sizeof(buf), fp) != NULL) {
            char* e = buf;
            send(sock, e,sizeof(e), 0);
            printf(buf);
        }


ポインタに格納することで、かなりよくなりました。
イメージ説明
全角文字のほうはやはり文字コードの問題のようです・・・。

  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 4

checkベストアンサー

+1

再回答になります。
Cのソースを
send(sock, buf, sizeof(buf), 0);を
send(sock, buf, strlen(buf), 0);
にしてください。
rubyのソースを
1行目に

# coding:WINDOWS-31J


を追加し
whileのループを以下のようにしてください。

while cmd = sock.gets#1行ずつコマンド実行結果を受け取る。
  cmd.force_encoding("WINDOWS-31J")
  puts cmd  #putsで表示させる。
end

これで、dirコマンドを実行したとき、正しく表示されることをこちらでは確認しました。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/09/08 13:59

    ありがとうございます。
    UTF-8,ASCIIなどなど・・・
    文字コードの認識の甘さを思い知りました。

    キャンセル

+1

rubyのソースの先頭に、以下の行を追加してください。(必ず1行目です)
こちらで、動作確認はしていませんが、それで解決するかと。
念のため確認しますが、
rubyのソースはシフトJIS(WINDOWS-31J)コードで書いている。
コマンドプロンプトは、シフトJISのコマンドプロンプトを使用している。
ということであってますか。その前提での回答になります。

# coding:WINDOWS-31J

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/09/08 11:23

    ありがとうございます。
    やったのですがだめでした・・・。
    使っているOSはwindowsなので
    WINDOWS-31Jのはず・・・

    キャンセル

+1

そのファイルの文字コードと、Ruby側の想定してる文字コードが違うんでしょう。

そこらへん確認してみては。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/09/08 12:49

    えー、、VSCodeとか、サクラエディタとか、文字コードがわかるテキストエディタがあるんで、それで開いて調べてみてください。

    そもそも元ファイルの文字コードがわからないでは問題の解決はできませんぜ

    キャンセル

  • 2019/09/08 12:53

    ありがとうございます。
    確認してみます。

    キャンセル

  • 2019/09/08 13:30

    UTF-8 BOM付 でした。

    キャンセル

+1

             char buf[128];
             while (fgets(buf,sizeof(buf), fp) != NULL) {
                 send(sock, buf, sizeof(buf), 0);
            }


あなたの送信したいデータサイズは本当にsizeof(buf)バイトですか?


追記。
そういえば最近「Cの文字列ってのは実体は配列で」なんて話をしたんでしたっけ。だとすると、自力での理解はまだ難しいかな...

char buf[128];
に、文字列"Hello"を格納してみましょうか。配列の各要素の内容は
buf[0]:'H', buf[1]:'e', buf[2]:'l', buf[3]:'l', buf[4]:'o', buf[5]:'\0', buf[6]:不定, buf[7]:不定, buf[8]:不定, <略> , buf[127]:不定
となっています。

その状況で、
send(sock, buf, sizeof(buf),0);
はどういうデータを送れと言っているでしょう。

'H''e''l''l''o''\0',不定,不定,不定,...(全部で128バイト分)

'\0',不定,不定,不定,... の部分が、あなたの言う「文字化け」です。

この場合、送らなければいけないのは、そうじゃなくて、
'H''e''l''l''o'
までのはず。

であれば、そのようなデータサイズを求める関数があります。
send(sock, buf, strlen(buf),0);
としましょう。


通信の話

通信ってのは、送る側の誰かさんと、受ける側の誰かさんとの間での情報のやり取りです。双方向通信ってのもありますけど、これは送りと受け、受けと送りの2系統を同時に行っているだけなので特別視するものではありません。

通信で大事なのは送る側と受ける側で、どういうデータをどういう手順で送るかがしっかりと合意されていること。送る側が日本語で喋って、受ける側が英語のつもりで聞いてたら「通信」が成り立ちませんよね。まずはそんな話。コンピュータの場合は、「データ」をやり取りするのでもっとちゃんと決めておかないと簡単に破綻します。

通信にもいろいろな方法がありますが、とりあえずTCP/IPに絞りましょうか。
TCP/IPの通信でなにより忘れてならないのは、「通信の単位は1バイト」それ以上ではない、ということ。どういうことかというと、例えば'H' 'e' 'l' 'l' 'o' と送信したときに、通信の単位は1バイトなので、受け取る側では'H'と'e'と'l'と'l'と'o'はバラバラだということ。もう少し具体的な現象としてみると、
send(sock, "Hello", 5, 0);を実行すると、通信ラインには'H' 'e' 'l' 'l' 'o'が順次送り出されます。これをchar buf[128]; read(sock, buf, 128);で受信するとします。受信側ではbuf[0]に'H',buf[1]に'e', buf[2]に'l', buf[3]に'l', buf[4]に'o'が受信されると期待...してはいけません。送り側が3文字目の'l'を送信中だったり、通信回線の遅延でデータがまだ届いていない場合にはbuf[0]に'H',buf[1]に'e'、そこでread()関数が終わりになるということは普通にあって対応しなければいけない事態です。なお、それが当然なので、対応するためread()は受信した文字数を戻り値に返してきます。
また、
send(sock, "Hello",5,0);
send(sock, " World",6,0);
と送信したときには同様に、read(sock, buf, 128);一回で"Hello"が、二回目に" World"が受信出来るという期待を持ってはいけません。1回目のread()で"He"、二回目で"llo World"が受信出来たり、あるいは一回目で"Hello w"、二回目で"o"しか受信できない、何ていう事態も決して異常ではありません。

では、"Hello"をひとかたまりとして「通信」するためにはどうすればいいでしょう...いくつかの方針があります。

A. 通信全てNバイトの塊、ということに送受両側で取り決めておく
(read()の結果がNバイトに満たなかったら受信したデータは溜めておいて、さらに受信する。受信したデータを足してNバイトになれば結果を出力する。ここで、Nバイト分を超えてデータがあったら「次」のデータなので受信したデータとして溜めておく必要がある)

B. 送る側が「これからNバイト送ります」という情報を先頭に付加し、受信側はそれを受けて、受信したデータからNバイトを一塊として取り出す
(最初に「Nバイト分」というデータを受け取る手続きはAに準じることが多い。そこでNが決まったら以下はAと同様)

C. 送る側が「データ終了」のマークを付加し、受信側はそのマークを受けたらデータを一塊として受信を終了する
(受信したデータは次々溜めていく。受信したデータを監視して、データ終了マークが登場したらそこで結果を出力する。データ終了マーク以降もデータがあれば、それは次のデータなので溜めておく)

このような規則を、read()で受信したデータを処理することで得るような上位の処理を使用します。

今回は、受信側にrubyのgetsメソッドを使っています。これは、改行(MS-DOS/Windows系だとCR(\xd)LF(\xa)の2バイトシーケンス)をデータ終了マークとしたCタイプを期待しています。なので、送る側のプログラムは期待されたデータを送らなければいけません。

さて、例えばchar buf[10];に対し、ファイル(やパイプ)からデータ"Hello\n"をfgets()で取得したら、buf[10]の中身はどうなるでしょう。
buf[0]='H' buf[1]='e' buf[2]='l' buf[3]='l' buf[4]='o' buf[5]='\xd' buf[6]='\xa' buf[7]:'\0' (ここまではfgets()で値を設定。以降はたまたまメモリーにあった値) buf[8]:不定(例えば0xc0) buf[9]:不定(例えば0xc1)
で、これをsend()で送るわけです。send(sock, buf, sizeof(buf),0);で送ったら
'H' 'e' 'l' 'l' 'o' '\xd' '\xa' '\0' '0xc0' '0xc1'
の10バイトが送られます。
これを受信した側では
'H' 'e' 'l' 'l' 'o' '\xd' '\xa' までをfgetsの戻り値として返し、またそれ以降の'\0' '\xc0' '0xc1'は「次のデータ」として溜め込みます。
引き続き、送る側が"World\xd\xa"を送ったとしましょう。受信側でfgets()が返すデータは、先のデータと合わせて
'\0' '0xc0' '0xc1' 'W' 'o' 'r' 'l' 'd' '\xd' '\xa'
となります。「通信」で伝えられるべきデータは'W' 'o' 'r' 'l' 'd''\xd' '\xa' だったはずなので、'\0' '0xc0' '0xc1'があるのは明らかにおかしいことです。で、この'\0' '0xc0' '0xc1'のようなゴミデータを表示したのがあなたのいう「文字化け」です。別に化けているわけでもなんでもないです。あなたが、余計なデータを送り込んでいるだけ。
ゴミデータは送り込んではいけません。では、'H' 'e' 'l' 'l' 'o' '\xd' '\xa' '\0' '0xc0' '0xc1'からどれだけ送ればいいのか...'\0'の前まで。そこまでのデータ量(バイト数)を数えてくれるのは? strlen()関数です。
strlen()+1にしたら、'\0'も送られます。'\0'は画面上表示はされないから送ってもいい、ですか? 元のデータになかったデータですからやっぱりダメです。
(もちろん、C言語のプログラム同士が通信する等の事情で、Cタイプの通信の終了マークを'\0'にしよう、という取り決めをしたのなら話は違います。)

ここで、配列bufの大きさはなにかに影響したでしょうか。していません。データバッファは送るべきデータを十分に格納出来る(データのサイズより大きい)ものでさえあればいいのです。送るデータサイズに配列のサイズをあわせなければいけない、なんてことは全くありません。あなたが、

char mojiretu[] = "HelloWorld";
        send(sock,mojiretu,sizeof(mojiretu),0);


としたときに余計なデータが表示されなかった、その状況の意味を誤解しただけ。この場合はsizeof(mojiretu)がstrlen(mojiretu)+1になりますけど、それが正しいデータサイズでないのは既に説明した通り。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/09/13 11:31

    > 何で私の質問だとわかったんですか?
    内容もそうですし、以前 https://teratail.com/questions/210061 の追記修正依頼で聞いてますから。最近知恵袋のユーザー名の表示が変わったけど、アバター(アイコン)は変わってなかったし。

    ちょっと気力を溜めて回答に追記するかなぁ...
    何度もいいますけど、「基本」がわからないところになにか載せるとガラガラと崩れるんです。今回必要なのはC言語じゃなくて「通信」の基本ですけれど。

    キャンセル

  • 2019/09/13 17:14

    その前に、ちょっと気になって一点確認したんですけど、

    > strlen+1でNULLが挿入されて文字化けがなくなりました。
    これ、考え方が間違っています。rubyの文字列には\0が必須ではありません。前にも書いたと思いますが、Cには「文字列」という型がないので無理やり末尾に'\0'をつけることで文字列としての体裁を作っている、Cだけの約束事でしかありません(Cに対して他の言語が忖度している場合もありますけど)。その意味では、'\0'は送ってはいけないデータです。(しかも、まだ「文字化け」ですか...)
    strlen(buf)でなければおかしいです。

    strlen(buf)+1のとき、ruby側の受信後の表示を
    p cmd
    としてデバッグ表示すると、tasklistに対して(最後の方だけ抜粋で)出力は
    "\x00cmd.exe 15208 Console 2 7,804 K\n"
    "\x00tasklist.exe 6604 Console 2 10,500 K\n"
    "\x00"
    こんな\x00はあるべきじゃないでしょう? 付いている場所は行末じゃなくて行頭ですよ?

    キャンセル

  • 2019/09/13 18:33

    ありがとうございます。
    そうなのですか・・・。
    確認してみます。

    キャンセル

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

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

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