🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Visual C++

Microsoft Visual C++はWindowsのCとC++の統合開発環境(IDE)であり、コンパイラやデバッガを含んでいます。

Socket.IO

Socket.IOはNode.js上で動くライブラリであり、すべてのブラウザとモバイルデバイスでリアルタイムのアプリを作動させる事を目的としています。

Q&A

解決済

4回答

8423閲覧

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

kazuyakazuya

総合スコア193

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Visual C++

Microsoft Visual C++はWindowsのCとC++の統合開発環境(IDE)であり、コンパイラやデバッガを含んでいます。

Socket.IO

Socket.IOはNode.js上で動くライブラリであり、すべてのブラウザとモバイルデバイスでリアルタイムのアプリを作動させる事を目的としています。

0グッド

1クリップ

投稿2019/09/08 01:39

編集2019/09/08 03:41

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

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

内容としては

string

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

#起こっている状況

サーバーサイド

c

1#include <stdio.h> 2#include <winsock2.h> 3 4int main(void) { 5 6 WSADATA wsaData; 7 SOCKET sock0; 8 SOCKET sock; 9 struct sockaddr_in addr; 10 struct sockaddr_in client; 11 //ポート設定 12 int len; 13 int port; 14 printf("ポートを指定してください。\n"); 15 scanf_s("%d", &port, 16); 16 printf("指定されたポート番号は・・・%d\n", port); 17 18 WSAStartup(MAKEWORD(2, 0), &wsaData); //Winsockの初期化 19 20 sock0 = socket(AF_INET, SOCK_STREAM, 0); 21 // ソケット設定の構造体の設定 22 addr.sin_family = AF_INET; 23 addr.sin_port = htons(port); 24 addr.sin_addr.S_un.S_addr = INADDR_ANY; 25 26 bind(sock0, (struct sockaddr*) & addr, sizeof(addr)); 27 28 listen(sock0, 5); 29 30 int n = 1; //接続回数変数初期化 31 while (1) { 32 len = sizeof(client); 33 //sock = accept(sock0, (struct sockaddr*) & client, &len); 34 if ((sock = accept(sock0, (struct sockaddr*) & client, &len)) < 0) { 35 perror("エラーが発生しました。"); 36 exit(1); 37 }else { 38 printf("接続に成功しました。これは%d回目です。\n", n); 39 } 40 41 //コマンドを送る 42 char cmd[128]; 43 printf("コマンドを入力してください。"); 44 scanf_s("%s",&cmd,128); 45 FILE *fp; 46 if((fp=_popen(cmd,"r"))== NULL){ 47 perror("エラーが発生しました。・・・"); 48 } 49 50 char buf[128]; 51 while (fgets(buf,sizeof(buf), fp) != NULL) { 52 send(sock, buf, sizeof(buf), 0); 53 } 54 55 _pclose(fp); 56 closesocket(sock); 57 n++; //カウント1プラス 58 } 59 WSACleanup(); //winsockの終了 60 61 62 return 0; 63}

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

ruby

1require 'socket' 2sock = TCPSocket.open("localhost","55555") 3 4while cmd = sock.gets#1行ずつコマンド実行結果を受け取る。 5puts cmd #putsで表示させる。 6end 7 8sock.close 9

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

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

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

c

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

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

ruby

1hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello

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

c

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

実行結果

ruby

1HelloWorld

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

c

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

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

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

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

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

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

c

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

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

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

ruby

1# coding:WINDOWS-31J

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

ruby

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

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

投稿2019/09/08 04:52

yokotatsu

総合スコア92

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazuyakazuya

2019/09/08 04:59

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

0

C

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

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

C

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

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

投稿2019/09/08 02:25

編集2019/09/13 14:18
thkana

総合スコア7703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazuyakazuya

2019/09/08 02:31

ありがとうございます。 指定にやり方が間違っていますか? 一応、サイズをいろいろ変えましたが文字化けするという点では変わりませんでした。
thkana

2019/09/08 02:37

間違っています。 極端には send(sock, 76, 0); としたら「文字化け」の状況はどうなりますか? なぜそうなるのでしょう。
kazuyakazuya

2019/09/08 02:43 編集

まえより少しましになりました。 実行結果を一応載せます。 考えてみます。
kazuyakazuya

2019/09/08 03:08

1行分のデータサイズが大きすぎた または小さすぎたから だから文字化けした。 サイズを調整(正確なやりかたは調べ中)しても 上らへんが化けるのは イメージ名 PID セッション名 セッション# メモリ使用量 これが全角文字だから。
thkana

2019/09/08 04:27

> 上らへんが化けるのは~これが全角文字だから。 でしょうね。 しかし、質問追記の > ポインタに格納することで、かなりよくなりました。 これはめちゃくちゃです。ポインタはこの場合関係ありません。まぐれ当たり的な。 char buf[5]; while (fgets(buf, sizeof(buf), fp) != NULL) { send(sock, buf, 4, 0); printf(buf); } としても同様になるはずで、tasklist以外にもいろいろやってみると綻びが見えるかも知れません。 で、回答の方に解を書いたつもりですが、それはうまく行かなかったのですか?
kazuyakazuya

2019/09/08 04:32

すみません。編集がついたタイミングで投稿していたので気づけませんでした。 回答でいただいた方法で試してみます。
thkana

2019/09/08 05:22

今回の問題が文字化け(文字にどのコード表を割り当てたか、送り側と受け側の不一致)「だけ」だと未だ思っているのなら、それはとてもまずいことだと思う...
kazuyakazuya

2019/09/08 05:24

バイトサイズの話ですか?
thkana

2019/09/08 05:48

そうですね。 なんでsizeof(buf)がだめだったのか、 なんでsizeof(mijiretu)で大丈夫だったのか、 なんでsizeof(e)が一見動くようにみえたのか、でも何故それでだめなのか、 なんでstrlen(buf)が正しいのか、 ちゃんと理解出来たならいいのですが、見ている限りでは判断がつかなかったので。 文字コードの方は「文字コードの認識の甘さを思い知りました。」とのことですが。
kazuyakazuya

2019/09/08 05:55

文字もそうですが バイトの指定をいい加減にしていたことも ...そうですね 次からバイトサイズにも気をつけます。
kazuyakazuya

2019/09/12 13:28

何で私の質問だとわかったんですか? (質問が独特すぎたか...) strlen+1でNULLが挿入されて 文字化けがなくなりました。 scanf関数と書いていますが 実際にはscanf_sを使いました。
thkana

2019/09/13 02:31

> 何で私の質問だとわかったんですか? 内容もそうですし、以前 https://teratail.com/questions/210061 の追記修正依頼で聞いてますから。最近知恵袋のユーザー名の表示が変わったけど、アバター(アイコン)は変わってなかったし。 ちょっと気力を溜めて回答に追記するかなぁ... 何度もいいますけど、「基本」がわからないところになにか載せるとガラガラと崩れるんです。今回必要なのはC言語じゃなくて「通信」の基本ですけれど。
thkana

2019/09/13 08: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はあるべきじゃないでしょう? 付いている場所は行末じゃなくて行頭ですよ?
kazuyakazuya

2019/09/13 09:33

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

0

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

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

投稿2019/09/08 02:00

y_waiwai

総合スコア88038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazuyakazuya

2019/09/08 02:24

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

2019/09/08 02:30

で、そのファイルの文字コードはなんでしょうか
y_waiwai

2019/09/08 03:33

rubyではなく、Cで読み込んでる方のファイルの文字コードは何でしょう
kazuyakazuya

2019/09/08 03:46

ASCIIコード・・・でしょうか?
y_waiwai

2019/09/08 03:49

えー、、VSCodeとか、サクラエディタとか、文字コードがわかるテキストエディタがあるんで、それで開いて調べてみてください。 そもそも元ファイルの文字コードがわからないでは問題の解決はできませんぜ
kazuyakazuya

2019/09/08 03:53

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

0

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

ruby

1# coding:WINDOWS-31J

投稿2019/09/08 01:59

tatsu99

総合スコア5493

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazuyakazuya

2019/09/08 02:23

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問