Sunday, November 27, 2011

Menghantar file menggunakan Soket UDP

Pengenalan
User Datagram Protocol atau UDP merupakan protokol yang ringkas. Protokol ini tidak boleh dipercayai - Apabila data dihantar, ia tidak dapat dipastikan sama ada data yang dihantar itu tadi sampai ataupun tidak. Pada protokol ini, tiada konsep pengakuan(acknowledgement), penghantaran semula(retransmission) ataupun masa tamat(timeout). Apabila data dihantar, kemungkinan data yang sampai tidak tersusun. Data dalam susunan 1,2,3 dihantar kemungkinan diterima dalam susunan 2,3,1. Oleh itu, penghantaran fail menggunakan protokol UDP adalah sedikit susah.



Biasanya dalam permainan komputer FPS, protokol UDP digunakan untuk menghantar lokasi dan aksi si pemain. Data-data ini akan dihantar berterusan kepada pemain lain. Namun, apabila data yang dihantar tidak sampai, ia akan dibiarkan tanpa dihantar semula agar permainan itu tidak kelihatan lewat atau lambat. Lain pula apabila menghantar fail, protokol baru perlu dibina diatas UDP bagi memastikan data yang dihantar lengkap dan mencukupi.

Tentang Projek Ini
Ini adalah satu projek ringkas untuk menunjukkan penghantaran fail melalui UDP. Kemungkinan kod-kod yang dipaparkan disini mempunyai ralat dan mungkin tidak dapat untuk menghantar fail dengan sempurna.

Download Fail Projek + Demo

UDP
Bagi memulakan projek ini, satu kelas ringkas bagi soket UDP perlu dihasilkan. Kelas ini dinamakan CSockUDP dan mempunyai method-method berikut:
class CSockUDP
{
public:
 static int InitWinsock();
 static void DestroyWinsock();

 void SetCallback(cbDataRecieved fnRecieved);
 int CreateSocket(int nPort = 31313);

 void StartListen();
 void StopListen();

 bool SetServerAddress(char* szServer, int nPort);
 int SendData(LPVOID lpData, int nSize);
 int ResendData();
}

Metod InitWinsock() dan DestroyWinsock() adalah panggilan ke WinsockAPI untuk memulakan penggunaan Winsock DLL dan menghentikannya. Ia perlu dipanggil awal semasa program baru berjalan dan juga sebelum program berakhir.

Panggilan kepada CreateSocket() akan membuat satu soket UDP dengan port yang ditetapkan. Selepas itu, metod SetCallback() perlu dipanggil untuk penerimaan data. StartListen() akan membina satu bebenang baru bagi melaksanakan satu fungsi gelung untuk memeriksa jika ada data masuk. Metod-metod yang lain jelas maksudnya dan rasanya tidak memerlukan penerangan.

----

Untuk menghantar fail melalui UDP, pertama sekali protokol untuk penghantaran fail perlu dibina. Setiap data yang dihantar mestilah mempunyai pengenalan supaya penerima faham apakah yang baru diterimanya itu dan apa yang perlu dibuat pada data yang baru diterimanya itu. Untuk itu kita akan membuat satu struktur bagi data header dengan disusuli oleh data yang hendak dihantar.

struct TUDPHeader
{
 char szID[3]; // 'IKH' - unique id
 DWORD dwCRC32; // Checksum of Data
 char type;  // eDataType
 int nLength; // Data length

 // Data (TUDPFileHeader/TUDPFileReply/TUDPFileTransfer)
};
Didalam header ini ada satu pengenalan, szID yang akan mempunyai data "IKH" untuk pengenalan kepada penerima. Jika paket yang diterimanya lain daripada "IKH" tadi, paket data tersebut akan terus diabaikan tanpa dibaca kerana kemungkinan ianya datang daripada software yang lain.

dwCRC32 akan diisi dengan checksum bagi keseluruhan data (tidak termasuk TUDPHeader). Penghantar akan mengira checksum bagi data-data yang akan dihantar dan dimasukkan kedalam dwCRC32. Penerima, setelah menerima paket akan mengira semula checksum bagi data dan membandingkan dengan dwCRC32  yang dihantar. Jika terdapat perbezaan, kemungkinan data yang dihantar korup atau rosak ataupun telah diubah. Penerima akan meminta data tersebut dihantar semula.

type fungsinya menjelaskan kepada penerima jenis data apakah yang dihantar.

enum eDataType
{
 eDataTypeConnect,   // ping
 eDataTypeConnected,   // Reply if server got connect message
 eDataTypeResend,   // Corrupted data, request for resend
 eDataTypeQuit,    // Exit the program, client and server

 eDataTypeFileHeader,  // TUDPFileHeader
 eDataTypeFileTransfer,  // TUDPFileTransfer

 eDataTypeFileReplyAccept, // Accept the file send request
 eDataTypeFileReplyReject, // Denied the file send request
 eDataTypeFileReplyOK,  // Proceed to next stream
 eDataTypeFileReplyFinish // Finished transfer
};

Kemudian, akhir sekali pada Header adalah nLength, yang akan memberitahu penerima berapa besar saiz data yang dihantar agar penerima tahu dimanakah data tersebut tamat.

----

Sewaktu fail hendak dihantar, satu paket data akan dibina menggunakan TUDPHeader diikuti dengan TUDPFileHeader. Ia akan menghantar nama fail diikuti dengan panjang fail tersebut dalam unit bytes.

struct TUDPFileHeader
{
 char szFileName[128];
 int nFileSize;
};

Setelah itu, penerima akan membalas sama ada untuk menerimanya atau tidak seperti yang boleh dilihat di eDataTypeeDataTypeFileReplyAccept/eDataTypeFileReplyReject. Jika diterima, penerima akan membina satu fail dan penghantar akan terus menghantar fail tersebut menggunakan TUDPFileTransfer yang mengandungi offset, saiz data, dan data fail itu sendiri.

struct TUDPFileTransfer
{
 int nFileOffset;
 int nLength;
 char szData[2048];
};

Penerima pula akan menulis fail yang dibinanya tadi dengan data yang dibekalkan sehingga lengkap.

Contoh menghantar TUDPHeader + TUDPFileHeader
This article also available in English: Click Here

No comments:

Post a Comment