Search This Blog

Monday, January 22, 2018

UDP/TCP Socket Programming with wxWidgets

  1. နိဒါန်း
  2. UDP
  3. TCP
  4. အကိုးအကားများ

နိဒါန်း

wxWidgets နဲ့ UDP ၊ TCP တို့ကို သုံးပြီး network ပေါ်မှာ ဒေတာ အပြန် အလှန် ပေးပို့ ဆက်သွယ် တဲ့ အကြောင်း ဆွေးနွေး ချင် ပါတယ်။ အဲဒီ အတွက် wxWidgets ကို တပ်ဆင် ထားဖို့ လိုပါ တယ်။ သူ့ကို platform အမျိုးမျိုး အတွက် တပ်ဆင်တဲ့ အကြောင်း တွေကို အောက်က လင့်ခ် မှာ ဖတ်နိုင် ပါတယ်။
အင်တာနက် စတဲ့ network တွေ ပေါ်မှာ TCP ဒါမှမဟုတ် UDP တွေသုံးပြီး စက်တစ်ခု နဲ့ တစ်ခု ဒေတာ တွေ အပြန်အလှန် ပို့ဖို့ အတွက် socket တွေကို အသုံးပြု နိုင် ပါတယ်။ ခေတ်ပေါ် operating system တွေ အားလုံးက socket layer ကို အထောက် အပံ့ ပေးကြ ပေမယ့် platform ပေါ်မူ တည်ပြီး socket ကို အသုံး ပြုရတဲ့ ပုံစံ တွေက အမျိုးမျိုး ကွဲပြား နိုင်ပါတယ်။ wxWidgets မှာ အောက်ခံ platform အတွက် ပူစရာ မလိုပဲ အလွယ် တကူ အသုံးပြုနိုင်တဲ့ socket class ပါ ပါတယ်။ အဲဒီ class ကို မတူညီတဲ့ ပုံစံ နည်းလမ်း အမျိုးမျိုး နဲ့ အသုံးပြုနိုင်ပြီး၊ အသုံးပြုပုံ နမူနာ တချို့ကို အောက်မှာ ဆက်ပြီး ဖော်ပြထား ပါတယ်။


UDP

IP (Internet Protocol) network တွေ ပေါ်မှာ စက်တစ်ခု ကနေ တစ်ခု ကို UDP (User Datagram Protocol) သုံးပြီး (Datagram လို့လည်း ခေါ် ကြတဲ့) message တွေကို ပို့လို့ ရပါတယ်။ UDP က ရိုးရှင်း တဲ့ connectionless communication ပုံစံ ကို သုံးတဲ့ အတွက် ဒေတာ မပို့ခင် ချိတ်ဆက် တာတွေ၊ handshake လုပ်တာတွေ၊ အမှား ပြင်တာ တွေ မပါပဲ ပေါ့ပါး မှု ရှိပြီး ပို့လိုက်တဲ့ ဒေတာ မှန်မမှန် ကိုပဲ checksum သုံးပြီး စစ်ပါတယ်။ ပို့လိုက်တဲ့ ဒေတာ က မှားသွား၊ ထပ်သွား လည်း ပြန်ပို့ စရာ မလို၊ ပြင်စရာ မလိုပဲ မြန်ဆန် သွက်လက် ဖို့ပဲ အရေးကြီး တဲ့ နေရာ (ဥပမာ Voice over IP လိုမျိုး) တွေက UDP နဲ့ သင့်တော် ပါတယ်။

wxWidgets ကို ထည့်သွင်း တပ်ဆင်ပြီး တဲ့ အခါ samples ဆိုတဲ့ အခန်းထဲက sockets ထဲမှာ network ချိတ်ဆက် အသုံးပြု တဲ့ နမူနာ [GZ09] ထဲမှာ UDP သုံးတာ ပြထား ပါတယ်။ အဲဒီ နမူနာ က တခြား TCP တွေကို ရော ပြည့်စုံ အောင် ပေါင်းပြ ထားတဲ့ အတွက် ရှုပ်ထွေး ခက်ခဲ မှု အနည်းငယ် ရှိတာ ရယ်၊ event ကို မသုံးပဲ data ပြန်မရ မခြင်း ရပ် နေတာ တွေ ရှိတာ ကြောင့်၊ သူ့ကို အခြေခံ ပြင်ဆင် ထားတဲ့၊ ပိုပြီး ရိုးရှင်း လွယ်ကူတဲ့ UDP သီးသန့် ce_wx_udp.cpp ဆိုတဲ့ နမူနာ တစ်ခု ကို အောက်က link မှာ ဖော်ပြ ထားပါတယ်။

https://github.com/yan9a/wxwidgets/blob/master/wxSockets/ce_wx_udp.cpp

ပရိုဂရမ် အစမှာ wxWidgets နဲ့ socket အတွက် header ဖိုင်တွေ နဲ့ IP address အမျိုးအစား တွေကို အောက်ပါ အတိုင်း သတ်မှတ် နိုင်ပါတယ်။
#include "wx/wx.h"
#include "wx/socket.h"
#if wxUSE_IPV6
    typedef wxIPV6address IPaddress;
#else
    typedef wxIPV4address IPaddress;
#endif
ပြီးတဲ့ အခါ လိုချင်တဲ့ IP address ၊ port number တွေနဲ့ UDP socket တစ်ခု ကို ဖန်တီးပြီး၊ အသုံးပြုချင်တဲ့ event တွေကို သတ်မှတ် နိုင် ပါတယ်။
// Create the address - defaults to localhost:0 initially
IPaddress addr;
addr.AnyAddress();
addr.Service(3000);

// Create the socket
sock = new wxDatagramSocket(addr);

// Setup the event handler
sock->SetEventHandler( *this, SOCKET_ID);
sock->SetNotify(wxSOCKET_INPUT_FLAG);
sock->Notify(true);


ဒေတာ တွေလက်ခံ ရရှိတဲ့ အခါ OnSocketEvent ရဲ့ wxSOCKET_INPUT အမျိုးအစား event မှာ RecvFrom method ကို သုံးပြီး ဖတ်နိုင် ပါတယ်။
n = sock->RecvFrom(addr, buf, sizeof(buf)).LastCount();


ဒေတာ ပို့ဖို့ အတွက် ပို့မယ့် remote host ရဲ့ address နဲ့ port number ကို သတ်မှတ်ပြီး၊ SendTo method နဲ့ ပို့နိုင် ပါတယ်။
IPaddress raddr;
raddr.Hostname("localhost");
raddr.Service(3001);
sock->SendTo(raddr, buf,n)


ဒီ ပရိုဂရမ် ကို စမ်းကြည့်ဖို့ အတွက် https://www.hw-group.com/products /hercules/index_en.html မှာ free ရနိုင်တဲ့ Hercules Utility ကို သုံးနိုင် ပါတယ်။ ပရိုဂရမ်ကို အောက်က အတိုင်း build လုပ်ပြီး run လိုက်တဲ့ အခါ သူတို့ အချင်းချင်း ဒေတာ တွေ အပြန်အလှန် ပို့နိုင် တာကို အောက်က ပုံတွေမှာ ပြထား သလို တွေ့နိုင် ပါတယ်။
g++ ce_wx_udp.cpp `wx-config --cxxflags --libs` -o ce_wx_udp
gksudo ./ce_wx_udp



Figure. Hercules utility ကို အသုံးပြုခြင်း။



Figure. UDP ဖြင့် ဒေတာများ ပို့ခြင်း နှင့် လက်ခံခြင်း။


TCP

TCP (Transmission Control Protocol) က အဓိက ကျတဲ့ network protocol တွေထဲမှာ တခု အပါအဝင် ဖြစ်ပါတယ်။ သူက ဒေတာ တွေပို့ တဲ့ အခါ စိတ်ချ ယုံကြည် ရပြီး၊ အစီအစဉ် တကျ ရောက်အောင် ၊ အမှား မပါ အောင် ပို့နိုင် ပါတယ်။ TCP နဲ့ ဒေတာ တွေ ပို့နိုင် ဖို့ အရင်ဆုံး connection ကို ချိတ်ဆက်ဖို့ လိုပါတယ်။ အဲဒီ အတွက် သတ်မှတ်ထား တဲ့ port number ကို နားထောင် နေမယ့် server နဲ့ အဲဒီ ကို လှမ်း ဆက်သွယ် ပြီး ချိတ်ဆက်မှု ကို စတင် မယ့် client ဆိုပြီး နှစ်မျိုး ရှိပါ တယ်။

TCP Server

TCP server က သတ်မှတ် ထားတဲ့ port number တစ်ခု မှာ passively နားထောင် နေပြီး၊ သူ့ကို လာဆက် သွယ်တဲ့ client နဲ့ ဒေတာ တွေ အပြန်အလှန် ပို့နိုင် ပါတယ်။ Client တွေ ဆီက ဒေတာ တွေကို လက်ခံ ဖော်ပြပြီး၊ ပြန်ပို့ပေး၊ ပြီးတော့ လက်ရှိ ဆက်သွယ် နေတဲ့ client အရေအတွက် တွေကို ပါဖော်ပြ ပေးတဲ့ ce_wx_tcp_server.cpp ဆိုတဲ့ TCP server နမူနာ [GZ09] တစ်ခု ကို အောက်က link မှာ ဖော်ပြထား ပါတယ်။

https://github.com/yan9a/wxwidgets/blob/master/wxSockets/ce_wx_tcp_server.cpp

အစ frame constructor မှာ socket server တစ်ခု ကို အောက်က အတိုင်း သတ်မှတ် လိုတဲ့ port number နဲ့ ဖန်တီးပြီး၊ ဆက်သွယ်မှု တစ်ခု ရောက်လာရင် လုပ်ဆောင်ဖို့ event handler ကို သတ်မှတ် နိုင်ပါတယ်။
// Create the address - defaults to localhost:0 initially
IPaddress addr;
addr.AnyAddress();
addr.Service(3000);

// Create the socket
sock = new wxSocketServer(addr);

// Setup the event handler and subscribe to connection events
sock->SetEventHandler( *this, SERVER_ID);
sock->SetNotify(wxSOCKET_CONNECTION_FLAG);
sock->Notify(true);


OnServerEvent မှာ ရောက်လာတဲ့ ဆက်သွယ်မှု ကို လက်ခံပြီး ရင် ရလာတဲ့ socket အတွက် ဒေတာတွေ ဝင်လာတဲ့ အခါ၊ ဒါမှမဟုတ် ဆက်သွယ်မှု ပြတ်တောက် သွားတဲ့ အခါ လုပ်ဆောင် ဖို့ event handler တွေကို သတ်မှတ် ပါမယ်။ နောက်ပြီး စုစုပေါင်း client အရေအတွက် ကို ဖော်ပြနိုင် ပါတယ်။
wxSocketBase *sockBase;
sockBase = sock->Accept(false);

sockBase->SetEventHandler( *this, SOCKET_ID);
sockBase->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
sockBase->Notify(true);

numClients++;
SetStatusText(wxString::Format(wxT("%d  clients connected"),numClients), 1);


ဒေတာ တွေ ဝင်လာရင် OnSocketEvent နဲ့ လက်ခံ ရယူ တဲ့ ပုံစံ က လက်ရှိ socket ရဲ့ setting တွေပေါ် မူတည်ပြီး အမျိုးမျိုး ဖြစ်နိုင် ပါတယ်။ Setting တွေ သတ်မှတ် တာ မမှန်ရင် ပရိုဂရမ် ရပ်သွား နိုင်တာကြောင့် မှန်မှန် ကန်ကန် သတ်မှတ်ဖို့ အရေးကြီး ပြီး၊ အဲဒီ အကြောင်း အသေးစိတ်ကို Julian Smart ရဲ့ Cross-Platform GUI Programming with wxWidgets [SH06] ဆိုတဲ့ စာအုပ် အခန်း ၁၈ မှာ ဖော်ပြ ထားတာကို ဖတ်ကြည့် သင့်ပါတယ်။ ဒီ နမူနာ မှာတော့ ပထမ ဆုံး byte မှာ message ရဲ့ အရွယ် အစား ကို ပို့ ပေးဖို့ လိုပြီး၊ အဲဒီ အရေ အတွက် မရ မချင်း စောင့်ပြီး ဖတ်တဲ့ wxSOCKET_WAITALL ကို အသုံးပြု ထား ပါတယ်။
sockBase->SetFlags(wxSOCKET_WAITALL); 
 
// Read the size @ first byte
unsigned char len;
sockBase->Read(&len, 1);

char buf[256];
// Read the message
wxUint32 lenRd = sockBase->Read(buf, len).LastCount();


ဒေတာ တွေ လက်ခံ ရရှိ ပြီးတဲ့ အခါ OK ဆိုတဲ့ message ကို သူ့ရဲ့ အရွယ် အစား 2 ကို ရှေ့ဆုံး byte မှာ ထည့်ပြီး client ဆီကို အကြောင်း ပြန်ပါ မယ်။
len = 2;
buf[0] = 'O';
buf[1] = 'K';
sockBase->Write(&len,1);
sockBase->Write(buf, len);


Connection ကို ဖြတ်တောက် လိုက်တဲ့ အခါ မှာ wxSOCKET_LOST ဆိုတဲ့ OnSocketEvent ဖြစ်တဲ့ အခါမှာ ပြတ်တောက် သွားတဲ့ socket ကို ဖျက်လိုက် ပါမယ်။
sockBase->Destroy();


ပရိုဂရမ် ကို အောက်က command တွေနဲ့ build လုပ်ပြီး၊ run လိုက်တဲ့ အခါ ပုံ မှာ ပြထား သလို client ရဲ့ ဆက်သွယ်မှု ကို နားထောင် နေတာ တွေ့နိုင် ပါတယ်။
g++ ce_wx_tcp_server.cpp `wx-config --cxxflags --libs` -o ce_wx_tcp_server
gksudo ./ce_wx_tcp_server



Figure. TCP server


TCP Client

TCP client က လိပ်စာ တစ်ခု က port number တစ်ခု မှာ နားထောင် နေတဲ့ server ကို သွားရောက် ဆက်သွယ်ပြီး၊ ဒေတာ တွေ အပြန်အလှန် ပို့နိုင် ပါတယ်။ Connection တစ်ခု ကို ပြုလုပ်ပြီး ဒေတာ တွေကို ပို့ပေး၊ server က ပြန်ပို့ တာကို လက်ခံ ဖော်ပြ ပေးတဲ့ ce_wx_tcp_client.cpp ဆိုတဲ့ TCP client နမူနာ [Gar99] တစ်ခု ကို အောက်က link မှာ ဖော်ပြထား ပါတယ်။

https://github.com/yan9a/wxwidgets/blob/master/wxSockets/ce_wx_tcp_client.cpp

ပရိုဂရမ် အစမှာ wxSocketClient တစ်ခု ကို ဖန်တီးပြီး၊ သူ့အတွက် event handler တွေကို သတ်မှတ် ပါတယ်။
 // Create the socket
 sock = new wxSocketClient();
 
 // Setup the event handler and subscribe to most events
 sock->SetEventHandler( *this, SOCKET_ID);
 sock->SetNotify(wxSOCKET_CONNECTION_FLAG |
 wxSOCKET_INPUT_FLAG |
 wxSOCKET_LOST_FLAG);
 sock->Notify(true);


ပရိုဂရမ် ကို အောက်က command တွေ သုံးပြီး build နဲ့ run လုပ်ပြီးတဲ့ အခါ အောက်က ပုံ မှာ ပြထား သလို File→Open session ကို နှိပ်ပြီး အရင် အပိုင်း က run ထားတဲ့ TCP server ကို ဆက်သွယ်မှု စတင် ပြုလုပ် နိုင် ပါတယ်။ ဆက်သွယ်မှု ကို ပြန်ပိတ် ချင်ရင် တော့ Close session ကို နှိပ်နိုင် ပါတယ်။
g++ ce_wx_tcp_client.cpp `wx-config --cxxflags --libs` -o ce_wx_tcp_client
gksudo ./ce_wx_tcp_client

Figure. TCP client မှ open session ပြုလုပ်ပုံ။


ဆက်သွယ်မှု ပြုလုပ်ဖို့ အတွက် လိပ်စာ နဲ့ port number တွေကို သတ်မှတ်ပြီး Connect ဆိုတဲ့ method ကို သုံးပြီး ဆက်သွယ် နိုင် ပါတယ်။
IPaddress addr;
addr.Hostname("localhost");
addr.Service(3000);
sock->Connect(addr, false);


ဆက်သွယ် မှု ပြုလုပ်ပြီး တဲ့ အခါ ဒေတာ တွေကို Send ခလုတ် နှိပ်ပြီး ပို့ နိုင် ပါတယ်။ အောက်မှာ ဖော်ပြ ထားတဲ့ အတိုင်း ပို့မယ့် ဒေတာ တွေရဲ့ ပထမ byte မှာ အရေ အတွက် ကို အရင်ထားပြီး၊ buffer ထဲက ဒေတာ တွေကို နောက်က နေ Write ကိုသုံးပြီး ပို့နိုင် ပါတယ်။
void MyFrame::OnSend(wxCommandEvent& WXUNUSED(event))
{
 wxString str = txtSend->GetValue();
 wxCharBuffer buffer = str.ToUTF8();
 size_t txn = str.length();
 unsigned char len;
 len = txn;
 sock->Write(&len, 1);//send the length of the message first
 if (sock->Write(buffer.data(), txn).LastCount() != txn)
 {
  txtRx->AppendText(wxT("Write error.\n"));
  return;
 }
 else {
  txtRx->AppendText("Tx: " + str + "\n");
 }
}


OnSocketEvent မှာ wxSOCKET_INPUT ဆိုပြီး ဒေတာ တွေ လက်ခံ ရရှိတဲ့ အခါ အရင် အပိုင်းက TCP server မှာလိုပဲ Read method ကိုသုံးပြီး ဖတ်နိုင် ပါတယ်။ ဆက်သွယ်မှု ကို အဆုံးသတ်ဖို့ အတွက် File→Close session ကို နှိပ် နိုင်ပါတယ်။ ဒေတာ တွေကို ပို့ပြီး တဲ့ အခါ server က ပြန်ပို့ တာကို ဖော်ပြ ထားတာကို အောက်က Client အတွက် ပုံ နဲ့ server အတွက် ပုံ တွေမှာ အသီးသီး တွေ့နိုင် ပါတယ်။
wxUint32 lenRd = sockBase->Read(buf, len).LastCount();



Figure. TCP client ဖြင့် ဒေတာ ပို့ခြင်း နှင့် လက်ခံခြင်း။



Figure. TCP server တွင် ဒေတာ လက်ခံရရှိပုံ။




Related Posts



အကိုးအကားများ

[Gar99] Guillermo Rodriguez Garcia, Client for wxSocket demo, 1999.
url: https://github.com/wxWidgets/wxWidgets/blob/master/samples/sockets/client.cpp.

[GZ09] Guillermo Rodriguez Garcia and Vadim Zeitlin, Server for wxSocket demo, 2009.
url: https://github.com/wxWidgets/wxWidgets/blob/master/samples/sockets/server.cpp.

[SH06] Julian Smart and Kevin Hock, Cross-Platform GUI Programming with wxWidgets, 2006.
Pearson Education, Inc. ISBN: 0-13-147381-6.

No comments:

Post a Comment