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

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

新規登録して質問してみよう
ただいま回答率
85.39%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Boost

Boost (ブースト)は、C++の先駆的な開発者のコミュニティ、 またそのコミュニティによって公開されているオープンソースライブラリのことを指します。

Q&A

解決済

1回答

9243閲覧

BoostライブラリのAsioで非同期通信を構築し,連続でデータの受け取りを行いたい

Unity-chan

総合スコア20

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Boost

Boost (ブースト)は、C++の先駆的な開発者のコミュニティ、 またそのコミュニティによって公開されているオープンソースライブラリのことを指します。

0グッド

0クリップ

投稿2017/11/01 10:48

編集2017/11/07 16:40

###前提・実現したいこと

 非同期で送信されるデータを,連続で受け取るプログラムの構築を目指しています.
具体的には「スレッドでデータ待機する→受け取る→表示する→データ待機する・・・」という処理を実現したいと考えています.
またMain関数からはAsioのライブラリが見えないように構築したいと考えています.
###現状
BoostライブラリのAsioを用いて構築したコードを確認するために,送信したデータを表示して終了するプログラムを構築しました.(参考:https://boostjp.github.io/tips/network/tcp.html)
###発生している問題・エラーメッセージ
実行すると以下のエラーメッセージが表示されます.(IPReceiveData.cppのOnReceive関数)

receive failed: 供給されたファイルハンドルが無効です.

###受信ソース
###IPReceiveData.h

c++

1#ifndef __IPConnect_h__ 2#define __IPConnect_h__ 3 4 5enum TransportProtocol { 6 TP_TCP = 1, 7 TP_UDP = 0, 8}; 9 10class IPReceive { 11public: 12 // TCPかUDPか選択(最初に選択することで,TCPUDP区別なく初期化・通信の関数を使える) 13 IPReceive(TransportProtocol isTcp); 14 ~IPReceive(); 15 16 void iprInitialize(); 17 18 void iprReceiveMSG(); 19 20 char* GetLastData(); 21 22private: 23 class IPReceive_impl* ipr_impl; 24}; 25 26#endif

###IPReceiveData.cpp

C++

1#define BOOST_COROUTINES_NO_DEPRECATION_WARNING 2#define PIMP(x) ipr_impl->x 3 4#include <iostream> 5#include <boost/asio.hpp> 6#include <boost/bind.hpp> 7#include <boost/asio/spawn.hpp> 8#include "IPReceiveData.h" 9 10namespace asio = boost::asio; 11using asio::ip::tcp; 12using asio::ip::udp; 13using std::cout; 14using std::endl; 15using std::string; 16 17 18// Pimplクラス 19class IPReceive_impl { 20private: 21 tcp::acceptor acceptor; 22 tcp::socket socketTCP; 23 //udp::socket socketUDP; 24 //asio::strand strand; 25 asio::streambuf receive_buff_; 26 27 28 29 // 接続が完了したか(async_accept用で、引数は固定) 30 void OnAccept(const boost::system::error_code& error) 31 { 32 if (error) 33 cout << "connect failed : " + error.message() << endl; 34 else 35 cout << "Success connection." << endl; 36 } 37 38 39 // 受信が成功したか(async_read用で,引数は固定) 40 void OnReceive(const boost::system::error_code& error, size_t bytes_transferred) 41 { 42 if (error && error != asio::error::eof) 43 cout << "receive failed: " << error.message() << endl; 44 else { 45 // 受信したデータをchar*に変換 46 const char* data = asio::buffer_cast<const char*>(receive_buff_.data()); 47 lastData = const_cast<char*>(data); 48 49 receive_buff_.consume(receive_buff_.size()); 50 } 51 } 52 53 54public: 55 asio::io_service& ioService; 56 const bool isTCP; 57 string receiveData; 58 char* lastData; 59 60 61 // TCPとUDPを指定して初期化するコンストラクタ 62 IPReceive_impl(asio::io_service& ioService, TransportProtocol isTcp) 63 : ioService (ioService), 64 acceptor (ioService, tcp::endpoint(tcp::v4(), 31400)), 65 socketTCP (ioService), 66 strand (ioService), 67 socketUDP (ioService), 68 isTCP (isTcp) {} 69 70 71 // デストラクタ 72 ~IPReceive_impl() {} 73 74 75 // 指定IP,ポートでTCP接続 76 void ConnectTCP(char *ipAddress, char *portNum) 77 { 78 // 接続後に呼ばれるコールバック関数を指定 79 auto cbOnAccept(boost::bind(&IPReceive_impl::OnAccept, this, asio::placeholders::error)); 80 81 // TCPソケットと上記で宣言したとコールバック関数を登録する 82 acceptor.async_accept(socketTCP, cbOnAccept); 83 } 84 // 指定IP,ポートでUDP接続 85 void ConnectUDP(char *ipAddress, char *portNum) { 86 } 87 88 89 90 // メッセージ送信 91 void ReceiveTCP() 92 { 93 // 受信後に呼ばれるコールバック関数を指定 94 auto cbOnReceive( 95 boost::bind(&IPReceive_impl::OnReceive, this, 96 asio::placeholders::error, 97 asio::placeholders::bytes_transferred)); 98 99 // データを受信する 100 asio::async_read( 101 socketTCP, 102 receive_buff_, 103 asio::transfer_all(), 104 cbOnReceive); 105 } 106}; 107 108 109 110// IP通信の初期化を行う 111IPReceive::IPReceive(TransportProtocol isTcp) { 112 auto ioService = new asio::io_service; 113 ipr_impl = new IPReceive_impl(*ioService, isTcp); 114} 115 116IPReceive::~IPReceive() { 117 cout << "Finish" << endl; 118 delete ipr_impl; 119} 120 121 122// TCP通信の初期化 123void IPReceive::iprInitialize() { 124 if (ipr_impl->isTCP) 125 PIMP(ConnectTCP(ipAddress, portNum)); 126 else 127 PIMP(ConnectUDP(ipAddress, portNum)); 128} 129 130void IPReceive::iprReceiveMSG() { 131 if (PIMP(isTCP)) { 132 PIMP(ReceiveTCP()); 133 PIMP(ioService.run()); 134 } 135} 136 137char* IPReceive::GetLastData(){ 138 return PIMP(lastData); 139}

###Main.cpp

C++

1#include "IPReceiveData.h" 2#define __test__ 3#include <string> 4#include <iostream> 5int main() { 6 // TCPで初期化 7 IPReceive ipSending(TP_TCP); 8 9 10 ipSending.iprInitialize(); 11 12 ipSending.iprReceiveMSG(); 13 14 15 std::cout << ipSending.GetLastData() << std::endl; 16 return 0; 17}

###試したこと
参考URLのようにReceiveTCP()をOnConnect()内で呼び出すと問題なく動作するのですが,今後連続でデータ待機を行う場合に毎回Connectを行う必要が出てくるので可能であればOnConnectからReceiveTCPを呼び出す方法は避けたいと考えています.

###補足情報(言語/FW/ツール等のバージョンなど)
Visual Studio 2017
Boost 1.65.1

###追記
###受信ソース(修正箇所)

C++

1 // 接続が完了したか(async_accept用で、引数は固定) 2 void OnAccept(const boost::system::error_code& error) 3 { 4 if (error) 5 cout << "connect failed : " + error.message() << endl; 6 else 7 cout << "Success connection." << endl; 8 9//-----編集箇所-------------------------- 10 ReceiveTCP(); 11//-----編集箇所-------------------------- 12 } 13 14 15 // 受信が成功したか(async_read用で,引数は固定) 16 void OnReceive(const boost::system::error_code& error, size_t bytes_transferred) 17 { 18 if (error && error != asio::error::eof) 19 cout << "receive failed: " << error.message() << endl; 20 else { 21 // 受信したデータをchar*に変換 22 const char* data = asio::buffer_cast<const char*>(receive_buff_.data()); 23 lastData = const_cast<char*>(data); 24 cout << lastData << endl; 25 26 receive_buff_.consume(receive_buff_.size()); 27 28//-----編集箇所-------------------------- 29 if (lastData[0] != 'e') { 30 cout << "endしないよ:" << lastData << endl; 31 ReceiveTCP(); 32 } 33 else 34 cout << "endするよ" << endl; 35//-----編集箇所-------------------------- 36 } 37 }

###送信ソース

参考URLのソースから,他ソースコードから送信したい文字列を指定できるように変更した.
###IPSendData.h

C++

1#ifndef __IPConnect_h__ 2#define __IPConnect_h__ 3 4 5enum TransportProtocol { 6 TP_TCP = 1, 7 TP_UDP = 0, 8}; 9 10class IPSending { 11public: 12 // TCPかUDPか選択(最初に選択すれば、初期化やメッセージ送信ではTCPUDP区別なく関数を使える) 13 IPSending(TransportProtocol isTcp); 14 ~IPSending(); 15 16 void ipsInitialize(char* ipAddress, char *portNum); 17 18 void ipsSendMSG(char* msg); 19 20private: 21 class IPSend_impl* ips_impl; 22}; 23 24#endif

###IPSendData.cpp

C++

1 2#define BOOST_COROUTINES_NO_DEPRECATION_WARNING // asioはcoroutine2をサポートしていないので警告を消す 3#define PIMP(x) ips_impl->x 4 5#include <iostream> 6#include <memory> 7#include <boost/asio.hpp> 8#include <boost/bind.hpp> 9#include <boost/asio/spawn.hpp> 10#include "IPSendData.h" 11 12namespace asio = boost::asio; 13using asio::ip::tcp; 14using asio::ip::udp; 15using std::cout; 16using std::endl; 17using std::string; 18using std::shared_ptr; 19 20 21 22class IPSend_impl { 23private: 24 tcp::socket socketTCP; // TCP 25 udp::socket socketUDP; // UDP 26 udp::resolver resolver; // UDP 27 asio::strand strand; // spawn 28 udp::endpoint receiver_endpoint; 29 string sendData; // SendTCPでしか使わないが、使用する前にスコープ外へ出て消えてしまうので外 30 31 32 // 接続が完了したか(async_connect用で、引数は固定) 33 void OnConnect(const boost::system::error_code& error) 34 { 35 if (error) 36 throw "connect failed : " + error.message(); 37 else 38 cout << "connect correct!" << endl; 39 } 40 41 void OnSend(const boost::system::error_code& error, size_t bytes_transferred) 42 { 43 if (error) 44 throw "send failed: " + error.message(); 45 else 46 cout << "send correct!" << endl; 47 } 48 49 50public: 51 asio::io_service& ioService; 52 const bool isTCP; 53 54 55 // TCPとUDPを指定して初期化するコンストラクタ 56 IPSend_impl(asio::io_service& ioService, TransportProtocol isTcp) 57 : ioService (ioService), 58 resolver (ioService), 59 strand (ioService), 60 socketTCP (ioService), 61 socketUDP (ioService), 62 isTCP (isTcp){} 63 64 // デストラクタ 65 ~IPSend_impl() { 66 } 67 68 69 // TCPの接続要求を行う 70 void ConnectTCP(char *ipAddress, char *portNum) { 71 // 接続するIPとポート番号を設定する 72 auto address( 73 tcp::endpoint( 74 asio::ip::address::from_string(ipAddress), atoi(portNum))); 75 76 // 接続時に実行するコールバック関数を設定する 77 auto cbOnConnect( 78 boost::bind(&IPSend_impl::OnConnect, this, 79 asio::placeholders::error)); 80 81 // 上記で宣言したアドレスとコールバック関数を登録する 82 socketTCP.async_connect(address, cbOnConnect); 83 } 84 85 // UDPの接続要求を行う 86 void ConnectUDP(char *ipAddress, char *portNum) { 87 auto query(udp::resolver::query(udp::v4(), ipAddress, portNum)); 88 receiver_endpoint = *resolver.resolve(query); 89 90 socketUDP.open(udp::v4()); 91 } 92 93 94 // TCPでメッセージを送信 95 void SendTCP(char *msg) 96 { 97 sendData = msg; 98 99 // 接続後に呼ばれる関数(今回は成否出力) 100 auto cbOnSend( 101 boost::bind(&IPSend_impl::OnSend, this, 102 asio::placeholders::error, 103 asio::placeholders::bytes_transferred)); 104 105 106 // ソケット、上記で宣言した送信データとコールバック関数を登録する 107 asio::async_write( 108 socketTCP, 109 asio::buffer(sendData), 110 cbOnSend); 111 } 112 113 114 // メッセージ送信 115 void SendUDP(char *msg){ 116 } 117}; 118 119 120 121// IP通信の初期化を行う 122IPSending::IPSending(TransportProtocol isTcp) { 123 auto ioService = new asio::io_service; 124 ips_impl = new IPSend_impl(*ioService, isTcp); 125} 126 127IPSending::~IPSending() { 128 delete ips_impl; 129 cout << "Finalize IPSending." << endl; 130} 131 132 133// TCP通信の初期化 134void IPSending::ipsInitialize(char *ipAddress, char *portNum) { 135 if (ips_impl->isTCP) 136 PIMP(ConnectTCP(ipAddress, portNum)); 137 else 138 PIMP(ConnectUDP(ipAddress, portNum)); 139} 140 141void IPSending::ipsSendMSG(char *msg) { 142 if (PIMP(isTCP)) { 143 PIMP(SendTCP(msg)); 144 PIMP(ioService.run()); 145 } 146 else 147 PIMP(SendUDP(msg)); 148}

###Main.cpp

C++

1#include <iostream> 2#include "IPSendData.h" 3#define __test__ 4 5int main() { 6 char test[30]; 7 8 IPSending ipSending(TP_TCP); 9 10 ipSending.ipsInitialize("127.0.0.1", "31400"); 11 12 while (true) { 13 std::cout << "文字を入力してください:"; 14 scanf_s(aaa, 10); 15 16 ipSending.ipsSendMSG(aaa); 17 18 if (aaa[0] == 'e') 19 break; 20 } 21 22 return 0; 23}

実行結果
それぞれ1文字ずつ[a, b, c, d, e]と送信した場合
イメージ説明

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

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

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

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

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

PineMatsu

2017/11/06 08:33

送信部分が見当たらないのですが、ソースは提示できますか?
Unity-chan

2017/11/06 09:08

質問をご覧いただきありがとうございます.先ほど送信ソースを追加したので,確認よろしくお願いします(受信ソースの下).
PineMatsu

2017/11/07 07:43

え?受信ソースの下には何もソースがありませんが。
Unity-chan

2017/11/07 16:42

送信ソースを追記の受信ソース(修正箇所)の項目の下へ移動しました。
guest

回答1

0

ベストアンサー

非同期版を使用するのであれば、コールバック関数の中で次の非同期処理を実行する必要があります。
提示されたコードでは非同期処理を連続で実行しているだけなので、socketが接続される前にreadが行われてエラーとなります。

処理の流れとしては
async_acceptに接続時のコールバック(1)を登録する。
(1)のコールバック内でasync_readにデータ受信時のコールバック(2)を登録する
(2)のコールバック内でデータを処理し、
まだデータが必要であれば
async_readにデータ受信時のコールバック(2)を登録する(受信されたら再度(2)のコールバックが実行される)
必要でなければ
切断等の処理をして終了する。

というような(2)のコールバック関数があたかもループされて実行されるような構造になっていないといけません。

投稿2017/11/01 13:40

hmmm

総合スコア818

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

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

Unity-chan

2017/11/06 05:58

返信が遅くなりもうしわけありません、回答ありがとうございます。 回答して頂いた内容を元に、追記(質問下部)のようにプログラムを編集しました。しかし、データを受信しても文字列が表示されず、終了時に受信した文字列が全て結合されてしまいます。もしよろしければ再度アドバイスを頂けると嬉しいです。
hmmm

2017/11/06 11:25

async_readでtransfer_allじゃなくてtransfer_at_least(1)とかtransfer_exactly(1)とかを使ってください。
Unity-chan

2017/11/10 05:55

transfer_at_least(1)にしたところ,無事一回ごとにデータを表示できました! transfer_allの場合は,バッファが一杯(65535)になるまでデータをため込むのですね…。今回はアドバイスしていただいてありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.39%

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

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

質問する

関連した質問