Lesson 21:忘れた頃に復活(?) 受信部分を別スレッドに

 えー、二ヶ月以上も間があいてしまいました・・・。大変申し訳ありません。
 それというのも、アレやコレやが大挙して、あぁ、まったくもぉ〜!!

 と、今更嘆いても仕方ないんで、前々回、前回の続きで、受信部分を別スレッドにしてみます。
 基本的には、前々回のプログラムに手を加えていきます。
 ここで、前々回のプログラムをもう一度みてみましょう。

/**** ファイル名 : MainWindow.h ****/

#ifndef MAINWINDOW
#define MAINWINDOW
//---------------------------------------------------------------------
#include <Be.h>
#include <be/net/socket.h>
//---------------------------------------------------------------------
#define MAINWINDOW_TITLE            "Network"
#define MAINWINDOW_POSITION_LEFT    100
#define MAINWINDOW_POSITION_TOP     100
#define MAINWINDOW_POSITION_WIDTH   400
#define MAINWINDOW_POSITION_HEIGHT  128
#define MAINWINDOW_WINDOWSTYLE      (B_TITLED_WINDOW)
//---------------------------------------------------------------------
#define MSG_CONNECT 'cnnb'
#define MSG_LISTEN  'lstb'
#define MSG_SEND    'sndb'
//---------------------------------------------------------------------
class BAppMainView : public BView
{
public:
    BStringView *strview;
    BTextControl *textctrl;
    BButton *connectbtn;
    BButton *listenbtn;
    BButton *sendbtn;
    BAppMainView(BRect frame);
};
//---------------------------------------------------------------------
class BAppMainWindow : public BWindow
{
private:
    int nsock;
public:
    BAppMainView *mainview;
    //-----------------------------------------------------------------
    BAppMainWindow(BRect frame,const char *title);
    virtual void MessageReceived(BMessage *msg);
    //-----------------------------------------------------------------
    bool QuitRequested();
    //-----------------------------------------------------------------
    void sock_connect(void);
    void sock_listen(void);
    void sock_send(void);
};
//---------------------------------------------------------------------
#endif

/**** ファイル名 : MainWindow.cpp ****/

//---------------------------------------------------------------------
#include "MainWindow.h"
//---------------------------------------------------------------------
BAppMainWindow::BAppMainWindow(BRect frame,const char *title)
    :BWindow(frame,title,MAINWINDOW_WINDOWSTYLE,0)
{
    mainview=new BAppMainView(Bounds());    
    AddChild(mainview);

    nsock=-1;
}
//---------------------------------------------------------------------
void BAppMainWindow::MessageReceived(BMessage *msg)
{
    switch(msg->what)
    {
        case MSG_SEND:
            sock_send();
            break;
        case MSG_CONNECT:
            sock_connect();
            break;
        case MSG_LISTEN:
            sock_listen();
            break;
        default:
            BWindow::MessageReceived(msg);
    }
}
//---------------------------------------------------------------------
BAppMainView::BAppMainView(BRect frame)
    :BView(frame,"bappmainview",B_FOLLOW_ALL,B_WILL_DRAW)
{
    BRect viewrect(Bounds());
    connectbtn=new BButton(BRect(viewrect.right-144,8,
                                 viewrect.right-80,28),"connectbtn",
                                 "Connect",new BMessage(MSG_CONNECT),
                                 B_FOLLOW_RIGHT | B_FOLLOW_TOP);
    AddChild(connectbtn);
    listenbtn=new BButton(BRect(viewrect.right-72,8,
                                viewrect.right-8,28),"listenbtn",
                                "Listen",new BMessage(MSG_LISTEN),
                                B_FOLLOW_RIGHT | B_FOLLOW_TOP);
    AddChild(listenbtn);
    sendbtn=new BButton(BRect(viewrect.right-72,98,
                              viewrect.right-8,118),"sendbtn",
                              "Send",new BMessage(MSG_SEND),
                              B_FOLLOW_RIGHT | B_FOLLOW_TOP);
    AddChild(sendbtn);

    textctrl=new BTextControl(BRect(8,38,viewrect.right-8,58),"textctrl",
                              NULL,"Default",NULL,
                              B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
    AddChild(textctrl);

    strview=new BStringView(BRect(8,68,viewrect.right-8,88),"strview",
                            "NoData",
                            B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
    AddChild(strview);

    textctrl->SetEnabled(false);
    sendbtn->SetEnabled(false);
}
//---------------------------------------------------------------------
void BAppMainWindow::sock_connect(void)
{
    struct sockaddr_in sa;
    int r;
    
    mainview->connectbtn->SetEnabled(false);
    mainview->listenbtn->SetEnabled(false);

    nsock=socket(AF_INET, SOCK_STREAM, 0);
    sa.sin_family=AF_INET;
    sa.sin_port=htons(10000);
    sa.sin_addr.s_addr=inet_addr("127.0.0.1");
    memset(sa.sin_zero,0,sizeof(sa.sin_zero));
    r=connect(nsock,(struct sockaddr *)&sa,sizeof(sa));

    if(r!=-1)
    {
        mainview->strview->SetText("Connect");  
        mainview->textctrl->SetEnabled(true);
        mainview->sendbtn->SetEnabled(true);
    }
}
//---------------------------------------------------------------------
void BAppMainWindow::sock_listen(void)
{
    int ssock;
    struct sockaddr_in sa;
    int sa_size;
    size_t rec;
    char buf[256];
    struct timeval tv;
    struct fd_set fds;
    
    mainview->connectbtn->SetEnabled(false);
    mainview->listenbtn->SetEnabled(false);

    ssock=socket(AF_INET,SOCK_STREAM,0);

    sa.sin_family=AF_INET;
    sa.sin_port=htons(10000);
    sa.sin_addr.s_addr=INADDR_ANY;
    memset(sa.sin_zero,0,sizeof(sa.sin_zero));
    bind(ssock,(struct sockaddr *)&sa,sizeof(sa)); 
    listen(ssock,5);
    sa_size=sizeof(sa);
    nsock=accept(ssock,(struct sockaddr *)&sa, &sa_size); 
    closesocket(ssock);

    do{
        tv.tv_sec=2;
        tv.tv_usec=0;
        FD_ZERO(&fds);
        FD_SET(nsock,&fds);
        select(32,&fds,NULL,NULL,&tv);
    } while(FD_ISSET(nsock,&fds)==0);

    rec=recv(nsock,buf,sizeof(buf),0);
    if(rec<=0)
    {
        closesocket(nsock);
        nsock=-1;
        strcpy(buf,"Disconnect");       
    }

    mainview->strview->SetText(buf);
}
//---------------------------------------------------------------------
void BAppMainWindow::sock_send(void)
{
    int r;
    
    mainview->textctrl->SetEnabled(false);
    mainview->sendbtn->SetEnabled(false);
    
    r=send(nsock,mainview->textctrl->Text(),
         strlen(mainview->textctrl->Text())+1,0);
    if(r==-1)
    {
        closesocket(nsock);
        nsock=-1;
        mainview->strview->SetText("Disconnect");
    }
}
//---------------------------------------------------------------------
bool BAppMainWindow::QuitRequested()
{
    if(nsock!=-1)
        closesocket(nsock);
    be_app->PostMessage(B_QUIT_REQUESTED);
    return true;
};
//---------------------------------------------------------------------

 前々回のプログラムの基本的な流れは、
 というものでした。
 受信部分は、クライアントからの接続待ちを行うsock_listen関数に記述していましたが、この部分を別のスレッドとして動作するようにし、クライアント側も、サーバ側も相手との接続が完了した時点で、このスレッドを実行します。(今回は双方向に通信することができます。)
 では、まずは受信部分のスレッドを作成してみます。関数名はrec_threadとしておきます。
 今回も、受信待ちにはselect関数を使用します。select関数を使用するには当然ソケットが必要になりますので、ソケットを呼び出し元から渡す必要があります。逆に受信したデータは呼び出し元へ渡す必要があります。
 スレッドとのデータのやり取りを行うには、スレッド関数の引数を使用する方法、send_data/receive_data関数を使用する方法、メッセージを使用する方法などがあります。今回は、スレッド関数に必要な情報を渡すのにsend_data/receive_data関数、受信データのやり取りにはメッセージを使用します。

 受信処理に必要な情報は、監視するソケットと受信データを送る対象(BWindowクラス)ですので、それらをまとめた構造体を宣言しておきます。
//---------------------------------------------------------------------
struct RecThreadData
{
    int sock;
    BWindow *owner;
};
//---------------------------------------------------------------------
 データをセットして送る部分は後回しにして、受信処理でそのデータを受け取る部分から作ってみましょう。
 構造体のデータを受け取るために、sock、Owner、rdataを宣言し、receive_data関数でデータを取得後、sock、Ownerに構造体の値をコピーします。
    int sock;
    BWindow *Owner;
    RecThreadData rdata;
    int32 sender;
    int32 code; 

    code=receive_data((thread_id *)&sender,(void *)&rdata,sizeof(rdata));
    Owner=rdata.owner;
    sock=rdata.sock;
 receive_dataの戻り値、引数として使用しているcode、senderですが、今回はデバッグ目的で使用する程度で、処理には絡みませんので、説明は割愛します。

 前々回の受信部分は
do
{
    tv.tv_sec=2;
    tv.tv_usec=0;

    FD_ZERO(&fds);
    FD_SET(nsock,&fds);

    select(32,&fds,NULL,NULL,&tv);
} while(FD_ISSET(nsock,&fds)==0);

rec=recv(nsock,buf,sizeof(buf),0);
if(rec<=0)
{
    closesocket(nsock);
    nsock=-1;
    strcpy(buf,"Disconnect");   
}
となっています。
 これをrec_threadに組み込み、受信したデータは呼び出し側のスレッドにメッセージとして通知するようにします。通知メッセージは、MSG_RECVとします。またrecv関数が失敗した場合は、セッションが切断されたと判断してMSG_CLOSEDを通知するようにします。両メッセージは
#define MSG_CLOSED  'clsd'
#define MSG_RECV    'recv'
としてMainWindow.hに定義しておきます。

 rec_thread関数に、receive_data関数、受信処理、メッセージ送信を組み込むと次のようになります。
//---------------------------------------------------------------------
int32 rec_thread(void *Dummy)
{
    int sock;
    BWindow *Owner;
    struct timeval tv;
    struct fd_set fds;
    BMessage *sendmsg;
    unsigned char buf[1024];
    ssize_t rec;
    RecThreadData rdata;
    int32 sender;
    int32 code; 

    code=receive_data((thread_id *)&sender,(void *)&rdata,sizeof(rdata));
    Owner=rdata.owner;
    sock=rdata.sock;
        
    do{
        do{
            tv.tv_sec=2;
            tv.tv_usec=0;
            FD_ZERO(&fds);
                FD_SET(sock,&fds);
            select(32,&fds,NULL,NULL,&tv);
        } while(FD_ISSET(sock,&fds)==0);

        rec=recv(sock,buf,sizeof(buf),0);
        if(rec<=0)
        {
            sendmsg=new BMessage(MSG_CLOSED);
            sendmsg->AddInt32("socket",sock);
                        
            Owner->PostMessage(sendmsg);
            delete sendmsg;     
        }
        else
        {
            sendmsg=new BMessage(MSG_RECV);
            sendmsg->AddInt32("socket",sock);
            sendmsg->AddData("data",B_ANY_TYPE,(void *)buf,rec);

            Owner->PostMessage(sendmsg);
            delete sendmsg;     
        }
    } while(rec>0);

    return rec;
}
//---------------------------------------------------------------------
 receive_data関数で必要な情報の取得をしたら、do-whileでrecv関数が失敗するまでループを繰り返して、その中で受信処理を繰り返します。受信処理では、select関数でソケットの状態を監視し、受信状態になったらrecv関数でデータを受信します。recv関数が失敗したらMSG_CLOSEDメッセージを、成功したら受信したデータをメッセージに付加したうえでMSG_RECVメッセージを発行します。どちらのメッセージにも、受け取った側が判断しやすいようにソケットを付加しておきます。

 受信部分のスレッドが完成したら、次はrec_thread関数を開始する部分ですが、クライアント側とサーバ側が接続された時点で送受信可能な状態となるので、sock_connect関数、sock_listen関数で行います。
 connect関数やaccept関数が成功した後で
struct RecThreadData rdata;

rdata.sock=nsock;
rdata.owner=(BWindow *)this;
strcpy(threadname,"ClientRecvThread");
recthread=spawn_thread(rec_thread,threadname,
                       B_NORMAL_PRIORITY,NULL);
send_data(recthread,63,(void *)&rdata,sizeof(rdata));
resume_thread(recthread); 
このように、rdataに必要なデータをセットし、spawn_thread関数でrec_threadを作成、send_data関数で必要なデータを渡したらスレッドを開始します。

 sock_connect関数、sock_listen関数は次のようになります。
//---------------------------------------------------------------------
void BAppMainWindow::sock_connect(void)
{
    struct sockaddr_in sa;
    int r;
        
    mainview->connectbtn->SetEnabled(false);
    mainview->listenbtn->SetEnabled(false);

    nsock=socket(AF_INET, SOCK_STREAM, 0);
    sa.sin_family=AF_INET;
    sa.sin_port=htons(10000);
    sa.sin_addr.s_addr=inet_addr("127.0.0.1");
    memset(sa.sin_zero,0,sizeof(sa.sin_zero));
    r=connect(nsock,(struct sockaddr *)&sa,sizeof(sa));

    if(r!=-1)
    {
        struct RecThreadData rdata;
                
        mainview->strview->SetText("Connect");  
        mainview->textctrl->SetEnabled(true);
        mainview->sendbtn->SetEnabled(true);

        rdata.sock=nsock;
        rdata.owner=(BWindow *)this;
        strcpy(threadname,"ClientRecvThread");
        recthread=spawn_thread(rec_thread,threadname,
                               B_NORMAL_PRIORITY,NULL);
        send_data(recthread,63,(void *)&rdata,sizeof(rdata));
        resume_thread(recthread); 
    }
}
//---------------------------------------------------------------------
void BAppMainWindow::sock_listen(void)
{
    int ssock;
    struct sockaddr_in sa;
    int sa_size;
    
    mainview->connectbtn->SetEnabled(false);
    mainview->listenbtn->SetEnabled(false);

    ssock=socket(AF_INET,SOCK_STREAM,0);

    sa.sin_family=AF_INET;
    sa.sin_port=htons(10000);
    sa.sin_addr.s_addr=INADDR_ANY;
    memset(sa.sin_zero,0,sizeof(sa.sin_zero));
    bind(ssock,(struct sockaddr *)&sa,sizeof(sa)); 
    listen(ssock,5);
    sa_size=sizeof(sa);
    nsock=accept(ssock,(struct sockaddr *)&sa, &sa_size); 
    closesocket(ssock);

    if(nsock!=-1)
    {
        struct RecThreadData rdata;
                
        mainview->strview->SetText("Connect");  
        mainview->textctrl->SetEnabled(true);
        mainview->sendbtn->SetEnabled(true);

        rdata.sock=nsock;
        rdata.owner=(BWindow *)this;
        strcpy(threadname,"ServerRecvThread");
        recthread=spawn_thread(rec_thread,threadname,
                               B_NORMAL_PRIORITY,NULL);
        send_data(recthread,63,(void *)&rdata,sizeof(rdata));
        resume_thread(recthread); 
    }
}
//---------------------------------------------------------------------

 スレッドが開始できるようになったら次はMSG_RECVメッセージを受け取った際の処理ですが、これは簡単でMessageReceived関数のswitch文の処理で
case MSG_RECV:
    {
        char *buf;
        ssize_t leng;
                           
        msg->FindData("data",B_ANY_TYPE,(const void **)&buf,&leng);

        mainview->strview->SetText(buf);
    }
    break;
メッセージから受信データを取り出すだけです。

 かなりおおまかですが、これで受信処理を別スレッド化した通信が出来るようになりました。これ以外にもセッションが切断した場合の処理等が必要ですが、それらは非常に簡単ですので、直接ソースを見てください。
 これでいよいよメールサーバへの接続に入っていくことができるようになりました。次回からはメールサーバへの接続を目指していきます。

ソースリスト
圧縮ファイル
R5 Intel環境で確認
Be8thApp20001223.zip
ソースファイル BaseApp.h
main.cpp
MainWindow.cpp
MainWindow.h

次の項目へ

トップページへ戻る