تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
Moving Complex Objects Across The Network
#1
[COLOR="#800080"]بسم الله الرحمن الرحيم
((رب اشرح لي صدري ويسر لي امري واحلل عقدة من لساني يفقهوا قولي))
صدق الله العلي العظيم
[/COLOR]


[COLOR="#000080"]السلام عليكم اعضاء و زوار المنتدى الكرام ورحمة الله وبركاته ارجو ان تكونوا بتمام الصحة والعافية ان شاءاللهSmile

سنتعلم في هذا المقال ان شاءالله كيفية بناء مشروع لنقل البيانات المعقدة عبر الشبكة عن طريق استخدام بروتوكول TCP.
[/COLOR]

[COLOR="#000080"]ان البيانات التي تنتقل عبر الشبكة تنتقل وفق بروتوكولات محددة تنظم عملية مرورها من خلال الشبكة ,و من اهم هذه البروتوكولات بروتوكول UDP وبرتوكول TCP وهما من اهم انواع البروتوكولات المستخدمة لنقل البيانات داخل الشبكة ,حيث تعتبر بروتوكول الUDP من النوع الغير موجه (ConnectionLess) أي لا يحتاج الى انشاء جلسة (اتصال) مع الطرف المقابل لتبادل البيانات لانه لا يدعم وصول البيانات كلها الى المستقبل لذا قد تضيع بعض الحزم(Packets) اثناء مرورها داخل الشبكة ولا تصل بشكل كامل الى وجهتا لكن هذا البروتوكول تتسم بالسرعة العالية في نقل البيانات لذا يفضل استخدامها لنقل مقاطع الصوت او الفيديو ويستخدم لعمل الBroadcasting والMulticasting.

أما برتوكول الTCP تعتبر من النوع الموجه (Connection Oriented) أي يحتاج الى انشاء جلسة مع الطرف المقابل لتبادل البيانات ويدعم وصول البيانات بشكل سليم الى المستلم لكن هذا يسبب بطئ في نقل البيانات الى الطرف المستقبل لان هذا البروتوكول يدعم عمليات التحقق (Authentication) من وصول البيانات بشكل كامل وسليم الى وجهتها ,يستخدم عادة لنقل الرسائل.
[/COLOR]



[COLOR="#0000CD"]وفرت لنا الNET.Framework مكتبات جاهزة لارسال واستقبال البيانات عبر الشبكة وذلك عن طريق مجال الاسماء Sockets حيث تضم مجموعة كبيرة من الاصناف التي تتعامل مع الشبكات ومن اهما TCPClient and UDPClient and Socket.

لكننا سنتعامل في هذا الدرس كيفية ارسال واستقبال البيانات عبر بروتوكول TCP وذلك عن طريق الكائن TcpClient and TcpListener والكائن Socket.[/COLOR]

اولا : ارسال بيانات من خلال الكائن Socket


الطرف المرسل

PHP كود :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;

namespace 
TCPsendermsg
{
    public 
partial class Form1 Form
    
{
        
byte[] data = new byte[1024];
        
Socket sock;
        
IPEndPoint iep;
        
        public 
Form1()
        {
            
InitializeComponent();
        }

        private 
void sendbtn_Click(object senderEventArgs e)
        {
            
sock = new Socket(AddressFamily.InterNetworkSocketType.StreamProtocolType.Tcp);
            
iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8001);
            
data =UnicodeEncoding.Default.GetBytes(textBox1.Text);
            
sock.Connect(iep);
            
sock.Send(data);
            
sock.Close();
        }
    }





[COLOR="#0000CD"]كما تلاحظون ان عملية الارسال سهلة للغاية كل ما هنالك اننا أنشأنا كائن من نوع Socket واخترنا الAdressFmily من نوع InterNetwork حيث يتعامل هذا النوع مع الIPv4 والIPv6 ثم اخترنا نوع الارسال وهو Stream ومن ثم نوع البروتوكول الا وهو الTCP وبعد ذلك فتحنا الاتصال مع الRemotehost عن طريق الIPEndPoint ثم ارسلنا الرسالة بعد تحوليها الى Bytes عن طري الكائن الدالة Send ثم اغلقنا الاتصال.
[/COLOR]


الطرف المستقبل

PHP كود :
using System
using System.Collections.Generic
using System.ComponentModel
using System.Data
using System.Drawing
using System.Linq
using System.Text
using System.Windows.Forms
using System.Net
using System.Net.Sockets
using System.Threading

namespace 
TCPreceivemsg 

    public 
partial class Form1 Form 
    

        
byte[] data = new byte[1024]; 
        
Socket sock
        
EndPoint ep
        
Thread th

        public 
Form1() 
        { 
            
InitializeComponent(); 
            
th = new Thread(new ThreadStart(receive)); 
            
th.Start();   
        } 

        public 
void receive() 
        { 
            
sock = new Socket(AddressFamily.InterNetworkSocketType.StreamProtocolType.Tcp); 
            
ep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8001); 
            
sock.Bind(ep); 
            
sock.Listen(-1); 
            while (
true
            { 
                
Socket s sock.Accept(); 
                
s.Receive(data); 
                
textBox1.Invoke(new MethodInvoker(delegate() 
                    { 
                        
textBox1.Text UnicodeEncoding.Default.GetString(data); 
                    } 
                    )); 
            } 
        } 

        private 
void Form1_FormClosing(object senderFormClosingEventArgs e
        { 
            
sock.Close(); 
            
th.Abort(); 
            
Application.Exit(); 
        } 
    } 




[COLOR="#0000CD"]الملاحظات المهمة في طرف المستقبل:

1- دالة ال()Bind وظيفتها ربط الIP والPort بالSocket
2- دالة ال()Listen وظيفتها بدء التنصت على الPort واسندنا ايها 1- الاستقبال عدد غير محدد من الاجهزة
3- دالة ال()Accept الموافقة على بدء الجلسة مع الطرف المقابل
4- استخدام InfiniteLoop لاستقبال الرسائل بشكل مستمر
5-استخدام الThreading في عملية الاستقبال وذلك لتشغيل دالة الاستقبال بشكل منفصل عن المسار الخاص بالبرنامج, واغلاق الsocket والThread عند اغلاق البرنامج.
6- اهم ملاحظة في طرف المستقبل هي استخدام دالة الInvoke الخاص باداة الTextbox لانشاء Delegate جديدة عن طريق الMethodInvoker ,حيث عند عدم انشاء delegate سيظهر Exception اثناء استقبال الرسالة لان الاداة التي نريد عرض الرسالة داخلها تعمل على مسلك آخر غير المسلك التي نعمل عليها لذا يجب فصله عن طريق دالة الInvoke وذلك بعمل delegate نضع داخلها الTextbox وذلك لعرض البيانات داخلها للحؤول دون ظهور استثناء يعرقل عمل البرنامج.

إلا هنا وبفضل الله انتهينا من تبيان اهم الملاحظات المتعلقة بارسال واستقبال البيانات عبر الشبكة وذلك بانشاء برنامجين توضحان هذه الفكرة ,لكن المشكلة تكمن في كيفية ارسال مجموعة من القيم من خلال الشبكة كوحدة واحدة مثلا ارسال معلومات لموظف ما تضم اسم وعمر ورقم وراتب الموظف كعنصر واحد من خلال الشبكة وكيفية استرجاع هذه البيانات وعرضها مرة اخرى في طرف المستقبل كحالتها الاصلية!!؟؟

اذا ما العمل؟؟

الحل ان شاءالله سهل مع قليل من الصبر والتفكير

ان اهم وأسهل وسيلة لعمل ذلك هي بانشاء صنف تضم هذه القيم ثم دمج هذه القيم في مصفوفة من نوع Byte وبعد ذلك ارسالها عبر الشبكة ,لكن المشكلة تكمن في كيفية استرجاع بعض هذه القيم مرة اخرى عند الطرف المستقبل؟؟
الحل هي بارسال طول كل قيمة نصية فقط مع حجم المصفوفة الى الطرف المستقبل لان حجم النص هنا غير محدد ثم القيام باسترجاع القيم اعتمادا على طول كل عنصر.
[/COLOR]

ثانيا : ارسال البيانات المعقدة من خلال الكائن TcpClient واستقبالها من خلال الكائن TcpLietener


الطرف المرسل :

اولا قم باضافة Class الى البرنامج عن طريق الProject-->AddClass وسمي الكلاس بStudentInfo ثم استبدلها بالكود التالي التي سيأتي شرحها لاحقا :

PHP كود :
using System
using System.Collections.Generic
using System.Linq
using System.Text

namespace 
SendCmplxData 

    public static class 
StudentInfo 
    

        public static 
int  namelendeptlen
        
/////////////////////////////////////////// 
        
public static int pos 0
        public static 
int size 0
        public static 
byte[] info
        
/////////////////////////////////////////// 
        
public static byte[] InfoToBytes(int idstring nameint agestring dept,double avg
        { 
            
namelen name.Length
            
deptlen dept.Length
            
/////////////////////////////////////////////// 
            
info=new byte[1024]; 
            
//id to byte array 
            
Buffer.BlockCopyBitConverter.GetBytes(id), 0infopos,4); 
            
pos += 4
            
//namelength to byte array 
            
Buffer.BlockCopy(BitConverter.GetBytes(namelen), 0infopos4); 
            
pos += 4
            
//name to byte array  
            
Buffer.BlockCopy(UnicodeEncoding.Default.GetBytes(name), 0infoposnamelen); 
            
pos += namelen
            
//age to byte array  
            
Buffer.BlockCopy(BitConverter.GetBytes(age), 0infopos4); 
            
pos += 4
            
//deptlength to byte array 
            
Buffer.BlockCopy(BitConverter.GetBytes(deptlen), 0infopos4); 
            
pos += 4
            
//dept to byte array 
            
Buffer.BlockCopy(UnicodeEncoding.Default.GetBytes(dept), 0infoposdeptlen); 
            
pos += deptlen
            
//avg to byte array 
            
Buffer.BlockCopy(BitConverter.GetBytes(avg), 0infopos8); 
            
pos += 8
            
//count the information size 
            
size pos
            
pos 0
            
//return an array of bytes that contain student information 
            
return info
        } 
    } 



اهم شيء في هذا الClass هي دالة الInfoToBytes تقتصر وظيفة هذه الدالة بتحويل كل عنصر من عناصر الصنف الى مصفوفة من البايتات عن طريق دالة الGetBytes للكائن UnicodeEncoding للنصوص حيث تدعم العربية والGetBytes للكائن Bitconverter للانواع الاخرى ومن ثم دمج هذه العناصر في مصفوفة واحدة من نوع Byte وذلك عن طريق دالة الBlockCopy للكائن Buffer حيث تقوم هذه الدالة باخذ نسخة من مصفوفة ووضعها في موقع معين في مصفوفة اخرى وتأخذ الصيغة التالية:

PHP كود :
Buffer.BlockCopy(byte []array1,int start,byte []array2,int offset,int size); 


[COLOR="#0000CD"]المدخل الاول array1 هي المصفوفة المطلوب نسخها الى المصفوفة الثانية array2
المدخل الثاني هي موقع البداية للمصفوفة الاولى
المدخل الثالث هي المصفوفة التي تستقبل عناصر المصفوفة الاولى
المدخل الرابع تمثل الموقع الذي سوف نبدأ منه بكتابة المصفوفة الاولى داخل المصفوفة الثانية وهذه القيمة سوف تتغير مع كل اضافة الى المصفوفة
والمدخل الاخير هي حجم المصفوفة المطلوب الحاقها بالمصفوفة الثانية

وايضا قمنا بحساب حجم البيانات و طول كل عنصر من نوع string لاننا لا نعرف طول النص المراد ارسالها عبر الشبكة ,أما اطوال المتغيرات من نوع .....int or float or double فهي معروفة حيث تأخذ الint إما 2bytes or 4bytes or 8bytes والFloat تأخذ 4bytes والDouble تأخذ 8bytes لكن كحجم افتراضي تأخذ الint اربع بايتات والDouble 4bytes وهو ما تلاحظونه في زيادة الpos بمقدار4 بعد الint و 8 بعد الDouble حيث تحجز دالة الGetBytes للكائن BitConverter اربعة مواقع من المصفوفة الثانية في كل حالة نسخ من نوع int و8 مواقع في حالة الDouble ما عدا طول الstring فنقوم بحساب طول النص المراد ارسالها وخزنها ايضا في المصفوفة وبعد ذلك زيادة الpos بمقدار طول النص.
[/COLOR]

الكود الخاص بالForm1.cs للمرسل:
PHP كود :
using System
using System.Collections.Generic
using System.ComponentModel
using System.Data
using System.Drawing
using System.Linq
using System.Text
using System.Windows.Forms
using System.Net
using System.Net.Sockets
using System.IO

namespace 
SendCmplxData 

    public 
partial class Form1 Form 
    


        
TcpClient tcp
        
byte[] data
        
IPEndPoint iep
        
NetworkStream ns
        
BinaryWriter bw
        
int idage
        
string namedept
        
double avg
        public 
Form1() 
        { 
            
InitializeComponent(); 
        } 

        private 
void SendInfobtn_Click(object senderEventArgs e
        { 
            
//set student information  
            
setinfo(); 
            
//converting student information to a single byte array 
            
data StudentInfo.InfoToBytes(idnameagedept,avg); 
            
//set an ip and port  
            
iep = new IPEndPoint(IPAddress.Parse("127.0.0.2"), 8002); 
            
//create TcpClient 
            
tcp = new TcpClient(); 
            
//connect to remote server 
            
tcp.Connect(iep); 
            
//get stream from TcpClient for reading or writing data 
            
ns tcp.GetStream(); 
            
//using BinaryWriter to wrirte binary data to NetworkStream 
            
bw = new BinaryWriter(ns); 
            
//werting the size of student info to the output stream 
            
bw.Write(StudentInfo.size); 
            
//writing data to the output stream which is the Networkstream 
            
bw.Write(data); 
            
////////////////////////// 
            
bw.Flush(); 
            
bw.Close(); 
            
tcp.Close(); 
        } 

        private 
void setinfo() 
        { 
            
id int.Parse(idtxt.Text); 
            
name nametxt.Text
            
age int.Parse(agetxt.Text); 
            
dept depttxt.Text
            
avg double.Parse(avgtxt.Text); 
        } 
    } 



[COLOR="#0000CD"]اهم الملاحظات:
1- استدعاء الدالة StudentInfo.InfoToBytes لتحويل البيانات الى مصفوفة واحدة من نوع byte
2- انشاء كائن الاتصال TcpClient ثم عمل Connect للRemote host
3- جلب الStream من الTcp الى الNetworkStream
4- اسناد الNetworkStream الى الBinaryWriter
5- كتابة الحجم والبيانات الى الNetworkStream عن طريق دالة الWrite للBinaryWriter
[/COLOR]


الطرف المستقبل:

[COLOR="#0000CD"]ايضا قم باضافة Class الى البرنامج عن طريق الProject-->AddClass وسمي الكلاس ب ReceiveStudentInfo ثم استبدلها بالكود التالي :
[/COLOR]

PHP كود :
using System
using System.Collections.Generic
using System.Linq
using System.Text


namespace 
ReceiveCmplxData 

    public static class 
ReceiveStudentInfo 
    

        public static 
int idage
        public static 
string namedept
        public static 
double avg
        
/////////////////////////////////////////// 
        
public static int  namelendeptlen
        
/////////////////////////////////////////// 
        
public static int pos 0
        
/////////////////////////////////////////// 

        
public static void GetStudentInfo(byte[] data
        { 
            
id BitConverter.ToInt32(datapos); 
            
pos += 4
            
namelen BitConverter.ToInt32(datapos); 
            
pos += 4
            
name UnicodeEncoding.Default.GetString(dataposnamelen); 
            
pos += namelen
            
age BitConverter.ToInt32(datapos); 
            
pos += 4
            
deptlen BitConverter.ToInt32(datapos); 
            
pos += 4
            
dept UnicodeEncoding.Default.GetString(dataposdeptlen); 
            
pos += deptlen
            
avg BitConverter.ToDouble(datapos); 
            
pos 0
        } 
    } 



[COLOR="#0000CD"]تضم هذا الكلاس دالة تستقبل مصفوفة من البايتات المرسلة من قبل المرسل وتقوم بتجزئتها وارجاعها الى حالتها الاصلية من خلال كائني الBitConverter للint and double وللانواع الاخرى ايضا والUnicodeEncoding للنصوص حيث يرجع سبب استخدام هذا الكائن في برنامجنا للنصوص بانها تدعم العربية.
وتضم كائن الBitConverter دوال تحويل من ByteArray to BaseType مع اعطاءه موقع البداية فقط حيث لا يحتاج الى تحديد الطول لانه يعتمد على نوع المتغير ,وتضم الUnicodeEncoding ايضا دالة تحويل من ByteArray to string وهنا يجب ان نحدد طول النص المراد استرجاعها.[/COLOR]


الكود الخاص بالForm1.cs للطرف المستقبل:
PHP كود :
using System
using System.Collections.Generic
using System.ComponentModel
using System.Data
using System.Drawing
using System.Linq
using System.Text
using System.Windows.Forms
using System.IO
using System.Threading
using System.Net
using System.Net.Sockets

namespace 
ReceiveCmplxData 

    public 
partial class Form1 Form 
    

        
Thread th
        
TcpListener listen
        
NetworkStream ns
        
BinaryReader br
        
Socket sock
        
byte[] data
        
int size 0

        public 
Form1() 
        { 
            
InitializeComponent(); 
            
th = new Thread(new ThreadStart(ReceiveStdInfo)); 
            
th.Start(); 
        } 

        private 
void ReceiveStdInfo() 
        { 
            
listen = new TcpListener(IPAddress.Any8002); 
            
//start listening 
            
listen.Start(); 
            
//infinite loop for reading information continual 
            
while (true
            { 
                
sock listen.AcceptSocket(); 
                
ns = new NetworkStream(sock); 
                
br = new BinaryReader(ns); 
                
//Reading the size of received packet 
                
size br.ReadInt32(); 
                
//////////////////////////////////////////// 
                
data = new byte[size]; 
                
//Reading student information and store it in array of bytes 
                
br.Read(data0data.Length); 
                
//////////////////////////////////////////// 
                //Converting student information from single byte array to its origins 
                
ReceiveStudentInfo.GetStudentInfo(data); 
                
///////////////////////////////////////// 
                //Using invoke method for each text control to avoid threading exception 
                
idtxt.Invoke(new MethodInvoker(delegate() 
                    { 
                        
idtxt.Text ReceiveStudentInfo.id.ToString(); 
                    } 
                   )); 
                
/////////////////////////////////////////// 
                
nametxt.Invoke(new MethodInvoker(delegate() 
                    { 
                        
nametxt.Text ReceiveStudentInfo.name
                    } 
                  )); 
                
///////////////////////////////////////////// 
                
agetxt.Invoke(new MethodInvoker(delegate() 
                    { 
                        
agetxt.Text ReceiveStudentInfo.age.ToString(); 
                    } 
                 )); 
                
/////////////////////////////////////////// 
                
depttxt.Invoke(new MethodInvoker(delegate() 
                    { 
                        
depttxt.Text ReceiveStudentInfo.dept
                    } 
                )); 

                
avgtxt.Invoke(new MethodInvoker(delegate() 
                { 
                    
avgtxt.Text ReceiveStudentInfo.avg.ToString(); 
                } 
                )); 

                
label5.Invoke(new MethodInvoker(delegate() 
                { 
                    
label5.Text "Data have been received!"
                } 
            )); 
                
ns.Flush(); 
                
br.Close(); 
            } 
        } 

        private 
void Form1_FormClosing(object senderFormClosingEventArgs e
        { 
            
listen.Stop(); 
            
th.Abort(); 
            
GC.Collect(); 
        } 
    } 


[COLOR="#0000CD"]اهم الملاحظات:
1- انشاء كائن TcpListener ثم بدء الجلسة مع الطرف المرسل
2- قبول الجلسة مع المرسل باستخدام دالة الAcceptSocket
3- انشاء NetworkStream و BinaryReader
4- قراءة الحجم المرسل
5- قراءة البيانات المرسلة الى مصفوفة بايتات
6- تمرير المصفوفة الى دالة الReceiveStudentInfo.GetStudentInfo لاسترجاع البيانات المرسلة
[/COLOR]
الرد }}}
تم الشكر بواسطة:


المواضيع المحتمل أن تكون متشابهة .
الموضوع : الكاتب الردود : المشاهدات : آخر رد
  Moving Complex Objects Across The Net Using Serlialization Sajad 3 2,982 23-03-13, 04:32 PM
آخر رد: Sajad

التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم