Contents
目標
上記のように、PC↔Android間の通信を技術的に調べたいと思っていました。ようやくこちらに取り組み始めたいと思います。まだまだまとまっていないのですが、最終的にはWi-Fiで PC↔Android間 での画像とデータの共有を考えています。
C#でのソケット通信を思い出す
久しぶりにソケット通信を行うので、すっかりソケット通信をすっかり忘れていました。5年ぐらい前には下記のリンクで勉強したような気がします。ただし、自分的の理解力がいまいちだったので、途中で投げ出しました(笑) 今見ると、あ~勉強させてもらったなぁという感じぐらいしか思い出せない (笑) なんかもう一つアットマークITあたりでチャットサンプルを参考にしたような気がしたが、もういいや忘れよう。
- TCPクライアント・サーバープログラムを作成する (Dobonさん)
- TCP/IPサーバでの送受信サンプル(C#/VB.NET) (Nonsoftさん)
C#でのソケット通信を調べなおす
当時より、さらにいろいろな情報が出てきているのに加えて、自分の理解力が少し増えたのと、調査のリテラシーも改善されたようなので、調べなおしました。
当時は何が原因で途中で投げだしたかもわかったような気がします。たぶん、 ①スレッドからUIを触る際にInvokeだったりを利用せずにやろうとしていたこと②通信についての理解不足 ③途中でHttpClientとかの方法もあるのを知って発散した ④最終的なアウトプットのイメージ ①~④でそこらへんで混乱したのかも。今なら、スレッド・UIの非同期(async/awai)も少しわかるので、下記のリンクを参考・そのまま利用させてもらいながら、自分の用途に合ったように改良も可能かも。
- C#による非同期ソケット通信 (Tomosoftさん)
- 非同期サーバー ソケットの例 (Microsoft公式)
- [C#] 非同期ソケット通信で簡易echoサーバーを作成する(Web備忘録さん)
- C#.NET Frameworkでソケット通信型トランプゲームを作った話 (@nekokojpnさん)
Thread()で書いてくれていて、わかりやすいのはTomosoftさんの例かと思います。最終的にUIを持つソフトに、ソケット通信を組み込もうとすると、UIとの非同期処理というか、そこらへんが重要になってきて、またそこから調べなおすと、メインテーマを見失う(趣味でやってると)ぐらいの労力になるので、UIとの非同期処理も書かれていると、大変進みやすいかと思います。
Microsoft公式は日本語が読みやすくなってからは、だいぶ理解が進みやすいです。理解が進んでいない状態で、英語で読むとさらにわからんようになったりすので、日本語で読める例が増えているのはありがたい。
async/Taskで書かれているのは、 @nekokojpn さんの例かと思います。
実際にやってみる
結果としては、下記の方法で通信ができていることが確認できました。
ソースコード追加点
Tomosoftさんのコードをもとに以下の通信テストを行います。の前に、少しだけ文字通りのコピペでは動作しないので、変更点を挙げておきます。
- StateObjectクラスをサーバ側のコンソールプロジェクトの方に作成しておく。Microsoft公式のようにStateObjectを作成ください。そのほかここでは、BUFFER_SIZEと大文字にしています。
- TcpClientクラスをクライアント側のクラスに自分で作成する。(クラスの中身をTomosoftさんが書いてくれているので、自分でクラスは作成すること。)
同一PC内での通信
- サーバ側とクライアント側を立ち上げます。
- クライアント側でConnectボタンを押します。→メッセージが表示されます。OK。
- Sendボタンを押します。
- Closeボタンを押すと、エラーが発生しますがこちらは調査中。実際に自分のソフトに組み込む際は内容を精査して対策してから組み込みます。
Wifiを通して、別PCでの通信
別のパソコンにソリューションをコピーして、実際にやってみました。ほとんど同じ動作になるので、PC1側のみの結果を書きます。
- サーバ側(PC1)とクライアント側(PC2)を立ち上げます。
- クライアント側でConnectボタンを押します。→メッセージが表示されます。OK。
- Sendボタンを押します。 今回は少し別の文字列でも送信できているかを確認して、送信できていました。(from ohter PC 456)
ということで、Wi-Fiを通してソケット通信で、別のパソコンにつながっている(データを送信できること)ことが確認できました。
その他、やったこと・気になること。
- [やったこと] 初めはWi-Fiを通して、2つのパソコンがつながっているかも不安だったので、”ping 192.168.11.7″などをして、pingが通っているかも確認しました。
- [やったこと] 無線LANのユニットが2ポートあったので、確認したら、パソコン同士が別のポートにつながっているようだったので、同じ方になるように合わせました。
- [気になること] Closeボタン押したときのエラー。たぶん、私の実装が悪いかと思うので、また暇があるときに確認してみます。
- [気になること] ソースコードで Dns.GetHostByName()が古いといわれている点。改善方法を知りたい。→次回、「ローカル・コンピュータのIPアドレスを取得するには?[C#、VB]」でやってみる。
- [気になること] IPアドレスが変わる可能性があるので、 クライアント側を IPアドレスの変更ができるようにしておきたい。
続編
2019/07/27 画像転送と、その他気になることに関して調査・コーディングしなおしたので、追記します。
ソケット通信での画像転送について
現在画像転送のテスト中、JFIFの文字がみえているので、バッファに落としこんで、サーバ側に転送できているようです。ただし、Socket.Send関数が勝手に分けてくれているので、そこは考慮に入れないといけない模様。一度頭を整理しなおしてから、画像にして保存したいと思います。
画像転送の付加情報
だいぶ考えて、画像を転送するためにヘッダを追加。 ヘッダの内容は、データの型(画像、テキスト、その両方)・ファイルサイズ・ファイル名・ストリームのエンドマーカ(10個程度)などを追加しました。細切れに1000ずつ程度送られてくるバッファを積算していき、画像のバッファのみを切り出して、最終的にファイルにして保存。
Client側のコード
長いので、すべてのソースコードは公開しませんが、考え方の参考になれば幸いです。関数の部分はすべて自分で定義した定数です。
public static byte[] GetBuf_Img_Header(string fileName, int fileSize, byte endOfFileByte) { //送受信文字列エンコード Encoding enc = Encoding.UTF8; int sizeHeader = (int)GetDataSize_Img_Header(); byte[] header = new byte[sizeHeader]; for (int i = 0; i < header.Length; ++i) header[i] = 0; // Type header[Index_Img_01_Type()] = (byte)DataType.Image; // Padding header[Index_Img_02_Padding()] = Img_02_Padding_Data; // ID header[Index_Img_03_ID()] = 1; header[Index_Img_03_ID() + 1] = 0; // Padding header[Index_Img_04_Padding()] = Img_04_Padding_Data; // FileName //文字列をBYTE配列に変換 byte[] fileNameArr = enc.GetBytes(fileName); Array.Copy(fileNameArr, 0, header, Index_Img_05_FileName(), (int)fileNameArr.Length); // Padding header[Index_Img_06_Padding()] = Img_06_Padding_Data; // FileSize //https://www.atmarkit.co.jp/ait/articles/0307/04/news005.html byte[] byteArray = BitConverter.GetBytes(fileSize); header[Index_Img_07_FileSize() + 0] = byteArray[0]; header[Index_Img_07_FileSize() + 1] = byteArray[1]; header[Index_Img_07_FileSize() + 2] = byteArray[2]; header[Index_Img_07_FileSize() + 3] = byteArray[3]; // Padding header[Index_Img_08_Padding()] = Img_08_Padding_Data; header[Index_Img_09_EosMaker()] = GetEosMaker(endOfFileByte); // Padding header[Index_Img_10_Padding()] = Img_10_Padding_Data; // マーカサイズは固定長 header[Index_Img_11_EosMarkerSize()] = Img_11_EosMarkerSize; // Padding header[Index_Img_12_Padding()] = Img_12_Padding_Data; return header; }
使い方の部分としては、以下のような感じです。
byte[] headerStream = DataDef.GetBuf_Img_Header(fullPathFileName, (int)fileStream.Length, fileByteStream[fileStream.Length-1]); byte[] eosBuffer = DataDef.GetBuf_Img_EosMakerBuffer(fileByteStream[fileStream.Length - 1]); long imgDataSize = DataDef.GetDataSize_Img(fileStream.Length); byte[] totalbyteStream = new byte[imgDataSize + headerStream.Length]; Array.Copy(headerStream, 0, totalbyteStream, 0, headerStream.Length); Array.Copy(fileByteStream, 0, totalbyteStream, DataDef.Index_Img_13_FileStream(), fileByteStream.Length); Array.Copy(eosBuffer, 0, totalbyteStream, DataDef.Index_Img_14_EOS(fileByteStream.Length), eosBuffer.Length); lock (syncLock) { //送信 //mySocket.Send(byteStream); mySocket.Send(totalbyteStream); }
Server側のソースコード
// 1つ目の配列を拡大する Array.Resize(ref accuBuffer, accuBuffer.Length + bb.Length); // 2つ目の配列の内容を1つ目の配列へコピーする bb.CopyTo(accuBuffer, accuBuffer.Length - bb.Length); if (totalStreamSize - 10 < accuBuffer.Length) { for (int i = 0; i < bb.Length - dataHeaderEosMakerSize; ++i) { if (DataDef.Is_Img_Eos(bb, dataHeaderEosMaker, i)) { Console.WriteLine("EOS detected"); byte[] fileBytes = new byte[fileSize]; Array.Copy(accuBuffer, DataDef.Index_Img_13_FileStream(), fileBytes, 0, fileBytes.Length); try { using (FileStream fs = new FileStream(System.IO.Directory.GetCurrentDirectory() + "Test.jpg", FileMode.Create)) { fs.Write(fileBytes,0, (int)fileSize); } } catch { /*OnError*/ } headerDetected = false; } } }
画像転送してみたが。。
コードの間違いがあり画像化しようとしたら画像化できておらず。。ここでもだいぶ迷いました。どうしたものかと思い、WinMergeで比較できることを思い出し、WinMergeをダウンロード。画像の拡張子のままだと、画像の比較になってしまうので、拡張子を変更して、比較。黄色の部分が異なる部分です。どうも右側で重複している部分があるので、コードを見直すと2重に積算バッファに追加している部分があったので、修正したら。画像の転送に成功していました。WinMerge良い!!
参考リンク
TCPでファイル(画像)を送受信する ~ファイル転送アプリを作った話~も参考にしながら画像を転送を行いました。Kotlin側でも参考にさせてもらいます。