segunda-feira, 22 de agosto de 2011

Sockets de rede com wxWidgets

Introdução
    Em computação sockets são basicamente o elo de ligação entre duas máquinas na rede. O socket pode ser entendido como sendo IP:Porta (Protocolo TCP) onde os dados são transmitidos entre cliente e servidor como uma cadeia de bytes. Existe também a transmissão em forma de datagramas não confiáveis (Protocolo UDP).
   O endereço IP identifica a máquina à qual eu quero me conectar e a porta o serviço executando nesta máquina. Para que um cliente se conecte à determinada máquina em determinada porta é preciso que um servidor esteja 'escutando' nesta porta.
   Em wxWidgets nós temos algumas classes usadas com o propósito de comunicação em rede através de sockets. A classe wxSocketBase, de onde descendem as demais classes relacionadas com sockets, como wxSocketClient e wxSocketServer. Para mais informações acerca destas classes visite: http://docs.wxwidgets.org/stable/wx_wxsocketbase.html#wxsocketbase.

Exemplo
    Em nosso exemplo vamos escrever uma aplicação que será o cliente e outra que será o servidor. Para que o cliente se conecte é preciso que o servidor esteja em execução, 'escutando' na porta definida. O cliente e o servidor podem ser executados na mesma máquina ou em máquinas separadas, conectadas à mesma rede. No primeiro caso fornecemos para o cliente como nome do host servidor a string 'localhost', no segundo caso fornecemos uma string com o número ip da máquina onde está o aplicativo servidor. A aplicação cliente enviará uma string ao servidor que retornará esta mesma string para o cliente, então, o cliente compara a string recebida de volta pelo servidor com a string enviada. Uma mensagem é mostrada dependendo do resultado da comparação (sucesso ou falha).



Vamos ao exemplo:

//cliente.cpp
#include <wx/wx.h>
#include <wx/socket.h>
#include <wx/wfstream.h>

class MeuFrame : public wxFrame {
    public:
        MeuFrame(const wxString &titulo);
        void Conectar(wxCommandEvent &evt);
        void Enviar(wxCommandEvent &evt);
        void Desconectar(wxCommandEvent &evt);
        void EscreveStatus(wxString status, int onde);
    private:
        wxBoxSizer *box;
        wxButton *btn_conectar;
        wxButton *btn_enviar;
        wxButton *btn_desconectar;
        wxTextCtrl *txt_status;     
        wxSocketClient *m_socket;
};

MeuFrame::MeuFrame(const wxString &titulo):
          wxFrame(NULL, wxID_ANY, titulo, wxDefaultPosition, wxSize(360,200))
{
     wxPanel *painel = new wxPanel(this);
     box = new wxBoxSizer(wxVERTICAL);
     wxBoxSizer *box_texto = new wxBoxSizer(wxVERTICAL);
     wxGridSizer *grid_botoes = new wxGridSizer(1,3,5,5);
     txt_status = new wxTextCtrl(painel, -1, _(""),wxDefaultPosition,wxDefaultSize,wxTE_MULTILINE | wxTE_READONLY); 
     box_texto->Add(txt_status,1,wxEXPAND); 
     btn_conectar = new wxButton(painel,1000,_("Conectar"));
     Connect(1000,wxEVT_COMMAND_BUTTON_CLICKED,wxCommandEventHandler(MeuFrame::Conectar));
     btn_enviar = new wxButton(painel,1001,_("Enviar"));
     Connect(1001,wxEVT_COMMAND_BUTTON_CLICKED,wxCommandEventHandler(MeuFrame::Enviar));
     btn_desconectar = new wxButton(painel,1002,_("Desconectar"));
     Connect(1002,wxEVT_COMMAND_BUTTON_CLICKED,wxCommandEventHandler(MeuFrame::Desconectar));
     grid_botoes->Add(btn_conectar,1,wxALL,5);
     grid_botoes->Add(btn_enviar,1,wxALL,5);
     grid_botoes->Add(btn_desconectar,1,wxALL,5);
     box->Add(box_texto,1,wxEXPAND);
     box->Add(grid_botoes,1,wxEXPAND);
     painel->SetSizer(box);
     //criamos o socket
     m_socket = new wxSocketClient();
     //definimos o manipulador de eventos para o socket
     m_socket->SetEventHandler(*this,1003);
     //
     m_socket->SetNotify(wxSOCKET_CONNECTION_FLAG | wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
     m_socket->Notify(true);
     #if wxUSE_STATUSBAR
        //barra de status da aplicação
        CreateStatusBar(2);
     #endif
     EscreveStatus(_("Desconectado"),1);
   
     Centre();
}

void MeuFrame::Conectar(wxCommandEvent &evt)
{
    wxIPV4address end;
    wxString host = wxGetTextFromUser(_("Digite o endereço do servidor"),
                                      _("Conectar"),
                                      _("localhost")
                                      );
    //atribuimos o nome do host digitado pelo usuario
    //no nosso caso 'localhost' (o host no qual o servidor está executando),
    //mas poderia ser um numero de ip de uma máquina na rede
    end.Hostname(host);
    //o numero da porta do serviço ao qual queremos nos conectar
    //utilizamos um numero alto para evitar conflitos com portas que já são utilizadas por outros serviços
    end.Service(3000);
    //fazemos uma tentativa de conexão
    //quando o segundo parametro é true a aplicação é paralisada
    //enquantro a conexão não se estabelece
    //quando é false a conexão é tentada e retorna-se logo em seguida
    //mesmo sem sucesso
    //em caso de sucesso o método retorna true, caso contrário, retorna false
    m_socket->Connect(end,false);
    //esperamos 10 segundos tentando estabelecer a conexão
    m_socket->WaitOnConnect(10);
    if(m_socket->IsConnected())
    {
        EscreveStatus(_("Conexão realizada com sucesso!"),0);
        EscreveStatus(_("Conectado"),1);       
    }
    else
    {
        EscreveStatus(_("Não foi possível realizar a conexão!"),0);  
        EscreveStatus(_("Desconectado"),1);     
        wxMessageBox(_("Não é possível estabelecer uma conexão com o host especificado!"),_("Alerta"));
    }

}

void MeuFrame::Enviar(wxCommandEvent &evt)
{
    unsigned char tam;
    wxString str_envio;
    wxChar *str_resposta;
    m_socket->SetFlags(wxSOCKET_WAITALL);
    str_envio = wxGetTextFromUser(_("Digite um texto para enviar ao servidor"),
                                  _("Texto"),
                                  _("")
                                 );  
                                
    str_resposta = new wxChar[wxStrlen(str_envio)+1];
    //tamanho da string enviada em bytes
    tam = (unsigned char) ((wxStrlen(str_envio)+1) * sizeof(wxChar));
    txt_status->AppendText(_("\nEnviando um buffer de texto para o servidor..."));
  
    //o primeiro byte é o tamanho da string o restante é a string em si
    m_socket->Write(&tam,1);
    m_socket->Write(str_envio,tam);
    if(m_socket->Error())
    {
        txt_status->AppendText(_("\nFalha!"));
        return;
    }
    else
    {
        txt_status->AppendText(_("\nSucesso!"));
    }
 
    txt_status->AppendText(_("\nRecebendo o texto de volta do servidor..."));
  
    //armazenamos a string de resposta do servidor em str_resposta
    m_socket->Read(str_resposta,tam);
 
    txt_status->AppendText(_("\nComparando as duas strings..."));
  
    //comparamos a string enviada com a string de resposta do servidor
    if(memcmp(str_resposta,str_envio,tam) == 0)
    {
       wxString s;
       s.Printf(_("\nSucesso! String Recebida: %s"), str_resposta);
       txt_status->AppendText(s);
    }
    else
    {
       txt_status->AppendText(_("\nFalha na recepção!"));
    }
    delete[] str_resposta;
}

void MeuFrame::Desconectar(wxCommandEvent &evt)
{
    //desconectamos do servidor
    m_socket->Close();
    EscreveStatus(_(""),0);
    EscreveStatus(_("Desconectado"),1);
}

void MeuFrame::EscreveStatus(wxString status, int onde)
{
    #if wxUSE_STATUSBAR
        SetStatusText(status,onde);
    #endif
}

class MinhaApp : public wxApp
{
    public:
        virtual bool OnInit();
};

IMPLEMENT_APP(MinhaApp)

bool MinhaApp::OnInit()
{
    MeuFrame *frame = new MeuFrame(_("Sockets com wxWidgets: Cliente"));
    frame->Show(true);
    return true;
}

//servidor.cpp
#include <wx/wx.h>
#include <wx/socket.h>

enum 
{
    ID_SERVER = 1000,
    ID_SOCKET   
};

class MeuFrame : public wxFrame
{
    public:
        MeuFrame(const wxString titulo);
        void OnSocketEvent(wxSocketEvent &evt);
        void OnServerEvent(wxSocketEvent &evt);
        void Recebe(wxSocketBase *socket);
        void EscreveStatus();
    private:
        wxTextCtrl *texto;
        wxSocketServer *m_server;
        int num_clientes;
};

MeuFrame::MeuFrame(const wxString titulo):
          wxFrame(NULL, wxID_ANY, titulo, wxDefaultPosition, wxSize(360,200))
{
  
    texto = new wxTextCtrl(this,-1,_(""), wxDefaultPosition,wxDefaultSize,
                           wxTE_MULTILINE | wxTE_READONLY);
    wxIPV4address end;
    end.Service(3000);

    m_server = new wxSocketServer(end);

    //verificamos se o servidor está 'escutando'
    if(m_server->Ok())
    {
        texto->AppendText(_("Servidor escutando...\n"));
    }
    else
    {
        texto->AppendText(_("Não é possével escutar na porta especificada\n"));
        return;
    }
    
    //conectamos os eventos de socket aos seus respectivos manipuladores
    Connect(ID_SERVER,wxEVT_SOCKET,wxSocketEventHandler(MeuFrame::OnServerEvent));
    Connect(ID_SOCKET,wxEVT_SOCKET,wxSocketEventHandler(MeuFrame::OnSocketEvent));

    //configura manipulador de eventos do servidor
    m_server->SetEventHandler(*this,ID_SERVER);
    //especifica quais eventos de socket serão enviados ao manipulador de eventos
    m_server->SetNotify(wxSOCKET_CONNECTION_FLAG);
    //Notify(true) habilita os eventos de socket especificados
    m_server->Notify(true);
    num_clientes = 0;
    #if wxUSE_STATUSBAR
       //barra de status da aplicação
       CreateStatusBar(1);
    #endif
    EscreveStatus();
}

void MeuFrame::Recebe(wxSocketBase *socket)
{
    unsigned char tam;
    char *str;
    texto->AppendText(_("Recebendo dados...\n"));
    socket->SetFlags(wxSOCKET_WAITALL);
    //lemos o tamanho da string
    socket->Read(&tam,1);  
    str = new char[tam];
    //lemos a string recebida e armazenamos em str
    socket->Read(str,tam);   
    texto->AppendText(_("Enviando a string de volta...\n"));
    //enviamos a string recebida de volta ao cliente
    socket->Write(str,tam);
    delete[] str;
    texto->AppendText(_("String enviada ao cliente...\n"));
}

void MeuFrame::OnServerEvent(wxSocketEvent &evt)
{
    wxSocketBase *sock;
    //aceita uma nova conexão e cria um novo objeto wxSocketBase que representa o lado servidor da conexão
    sock = m_server->Accept(false);
    if(sock)
    {
       texto->AppendText(_("Novo cliente aceito\n"));
    }
    else
    {
       texto->AppendText(_("Não foi possível aceitar uma nova conexão\n"));
       return;
    }

    sock->SetEventHandler(*this, ID_SOCKET);
    sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
    sock->Notify(true);
    num_clientes++; 
    EscreveStatus();  
}

void MeuFrame::OnSocketEvent(wxSocketEvent &evt)
{
    wxSocketBase *sock = evt.GetSocket();

    switch(evt.GetSocketEvent())
    {
        case wxSOCKET_INPUT:
        {
           //desabilitamos novos eventos de entrada temporariamente
           sock->SetNotify(wxSOCKET_LOST_FLAG);
           //recebemos e reenviamos a string para o cliente
           Recebe(sock);
           //habilitamos os eventos de entrada novamente
           sock->SetNotify(wxSOCKET_LOST_FLAG | wxSOCKET_INPUT_FLAG);
           break;
        }
        case wxSOCKET_LOST:
        {
            num_clientes--;
            EscreveStatus();
            texto->AppendText(_("Deletando socket.\n\n"));
            //destruimos o socket
            sock->Destroy();
            break;
        }
        default: ;
    }

}

void MeuFrame::EscreveStatus()
{
     wxString s;
     s.Printf(_("Numero de clientes conectados: %d"), num_clientes);
     #if wxUSE_STATUSBAR
        SetStatusText(s,0);
     #endif
}

class MinhaApp : public wxApp
{
    public:
        virtual bool OnInit();
};

IMPLEMENT_APP(MinhaApp)

bool MinhaApp::OnInit()
{
    MeuFrame *frame = new MeuFrame(_("Sockets com wxWidgets: Servidor"));
    frame->Show(true);
    return true;
}