2012年12月9日日曜日

ソケット通信によるpythonとC++の連携

pythonでインターフェース作ったり、Arduinoのコントロールしながら、
C++での画像処理結果(前に何があるか、とか)をpythonに送りたくなったのでソケット通信でやって見ました。

pythonだとopencvで特徴量記述とかが出来ないし(そもそも遅いし)、C++でインターフェースとかシリアル通信なんかやってられないので、双方の弱点をカバーする方法としてプロセス間通信が最も横着な方法であると判断。

参考文献:
1、Pythonメモ 様の記事 ttp://yoshi-python.blogspot.jp/2009/10/blog-post_19.html
2、Geekなぺーじ 様の記事 ttp://www.geekpage.jp/programming/linux-network/tcp-1.php
3、信州大学HP 様 ttp://intuniv.cs.shinshu-u.ac.jp/Lecture/NetProg/chapter3/struct.html
4、pythonのリファレンス


■ 今回はpythonをサーバとしてC++側と双方向通信させました。 ちなみにサーバとクライアントの違いについてはよくわかってません。接続を待つ方がサーバぐらいの認識でいいのだろうか。

以下、参考文献のページのコードをほぼ引用した自分用のメモ

python側:

import socket 

def server(host, port):
    s = socket.socket()      ←ソケットのインスタンスを作る
    s.bind((host, port))   ←バインド(IPとポートを設定する)
    s.listen(1)       ←接続待ちに必要。引数は接続できるノードの数。
    print "waiting"
    conn, addr = s.accept()  #  ←クライアントの接続を待つ。connはつながってきたソケット
               addrはノードのアドレス(使い所は不明)
    while True:   ←ずっとループ

        data = conn.recv(1024)  # クライアントからのデータを受信する(ここでは1024byte)
              ↑ここでのdataの形式がよくわからないが多分char(C++側ではconst charで送ってる)  
        print "server: receive '%s'" % data

        datalist=str(data)  ←とりあえずデータをstring化

        datasp=datalist.split(",") ←データをカンマで区切る。数字をカンマ区切りで送ることを考えている。

        print datasp
        object.set_value(float(datasp[0])) 
  ↑任意の変数に受信データの値を入力。これでArduinoでもなんでも動かせる(pythonと繋がってれば)
       
        conn.send("sendfrompython")  # クライアントにデータを送信する。ここでpythonからの命令(右に曲がるから右に注目しろ、とか)をC++に送ることが出来る。

    conn.close()  ←クライアントのソケットを閉じる。
    s.close()    ←こっちのソケットを閉じる。 
閉じてもすぐに切断されるわけではないらしい。連続で同じポート使ったりできないことがあるので時間開けるかポート番号変えるのが吉。いまのところpython-C++ではこの事象は起こってないがpythonのプログラム間(同ソース別スレッド)でこの事象を確認。

class Server(threading.Thread):######### C++との通信スレッド
    def run(self):
        
            server(HOST, PORT) ←上記関数。HOST(127.0.0.1とか)とPORT(12345とか)はグローバル変数か何かにしとく


C++側:参考資料2からほぼ引用、難しそうな構造体については参考文献3を参照
 
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
↑必要なヘッダの皆さん。特に意識せずに使えたので最初から入ってると思われる。



 void* thread(void* pParam)  //pythonへの情報送信用スレッド
{
////ソケット通信による通信
int sockmode=1;   ////1ならソケット通信ON ←通信をONOFFできて便利

if(sockmode==1){
struct sockaddr_in server;  ←アドレスなんかを入れておく構造体インスタンスを定義

 int sock;    ←ソケットをとりあえず定義
 char buf[64];  ←バッファを定義[]内はバイト数。多すぎず少なすぎずで。(少ないと即死)


 int n;  ←データ読み込み用

 /* ソケットの作成 */
 sock = socket(AF_INET, SOCK_STREAM, 0); 
↑AF_INETはIPv4であること、SOCK_STREAMはTCPであることを表している。多分どの言語でも共通の定数。ここらを変えるとIPv6にしたりUDPにもできる。サーバと共通じゃないとだめ。

 /* 接続先指定用構造体の準備 */↓python側の設定を書く。つまり宛先の設定
 server.sin_family = AF_INET;
 server.sin_port = htons(12347); ←ポート番号。htons()はバイトオーダを変換する関数
                 (詳細不明。接続のための何らかの規格化だろう) 
 server.sin_addr.s_addr = inet_addr("127.0.0.1");←このIPは自分自身(PC)を示す。変えると別の機器(PC他)にももちろんつながる。はず。関数は文字列をちゃんとアドレスの形式にする関数

 /* サーバに接続 */↓詳細は「C++ connect socket」でぐぐってね
 connect(sock, (struct sockaddr *)&server, sizeof(server));

while(1){ /* サーバからデータを受信。 送信も */
    memset(buf, 0, sizeof(buf)); ←バッファのリセット
    stringstream sts; ←とっても便利なstringstream(以下ss)オブジェクトを定義

    string str="0,0,end"; ←送信文字列の初期化。文字は適当に
 最後の「,end」はとても大事。python側でsplitするときとか特に





↓   別のところで「進んでOK」とか「止まれ」とかのフラグ出すようにしてる。それに応じて送信値変更。明らかに改善の余地あり。
 if(forwardflag ==1){str="100,100,end";}

    if(stopflag==1){str="0,0,end";}


    cout << "send:" <<str <<endl;

    char sdata[64]; ←送信用バッファの定義
    sts <<  str;  ←事前に作った文字列をssへ
    sts >> sdata; ←ssから送信用charへ自動変換&代入。このツールまじ便利


    const char* sdataconst= sdata; ←charをconst charへ送信関数の引数がconst〜なので(このへんの変換でちょっと詰まった。)

    write(sock, sdataconst, strlen(sdataconst));←データをあっちに送信。第3引数は送信データの文字数にする。strlenは全角文字入ると正確じゃなくなるので注意

    cout<<"senddata"<<sdataconst <<endl;

    int n = read(sock, buf, sizeof(buf)); ←pythonからbufへ読み込み。nは読み込めたかどうかとかを確認するための数字(読み込め無かったら-1とか)。読み込みデータじゃないので注意
    cout <<"resdata" <<buf << endl;

    sleep(1);←1秒に1回通信。もっと少なくてもいいけどスリープ入れないと多分ろくなことにならない

    sts.clear(); ←ssの中身をクリア
}
 /* socketの終了 */
 close(sock); ←ソケット閉じる
}

 return 0;


以上。
これでとりあえず通信ができることを確認。強制終了前提のコードなのであくまで参考で。

↓左がC++で右がpython(eclipseのコンソール)。なんとか通信している。








1 件のコメント:

  1. 初心者です。
    c++で、ソケット通信を用いて、pythonに数字を送りたいです。どんなプログラムを書けばよいでしょうか?教えていただけると幸いです。

    返信削除