[COLOR="#800080"]بسم الله الرحمن الرحيم
((رب اشرح لي صدري ويسر لي امري واحلل عقدة من لساني يفقهوا قولي))
صدق الله العلي العظيم
[/COLOR]
[COLOR="#000080"]السلام عليكم اعضاء و زوار المنتدى الكرام ورحمة الله وبركاته ارجو ان تكونوا بتمام الصحة والعافية ان شاءالله
سنتعلم في هذا المقال ان شاءالله كيفية بناء مشروع لنقل البيانات المعقدة عبر الشبكة عن طريق استخدام بروتوكول TCP.
[/COLOR]
[COLOR="#000080"]ان البيانات التي تنتقل عبر الشبكة تنتقل وفق بروتوكولات محددة تنظم عملية مرورها من خلال الشبكة ,و من اهم هذه البروتوكولات بروتوكول UDP وبرتوكول TCP وهما من اهم انواع البروتوكولات المستخدمة لنقل البيانات داخل الشبكة ,حيث تعتبر بروتوكول الUDP من النوع الغير موجه (ConnectionLess) أي لا يحتاج الى انشاء جلسة (اتصال) مع الطرف المقابل لتبادل البيانات لانه لا يدعم وصول البيانات كلها الى المستقبل لذا قد تضيع بعض الحزم(Packets) اثناء مرورها داخل الشبكة ولا تصل بشكل كامل الى وجهتا لكن هذا البروتوكول تتسم بالسرعة العالية في نقل البيانات لذا يفضل استخدامها لنقل مقاطع الصوت او الفيديو ويستخدم لعمل الBroadcasting والMulticasting.
أما برتوكول الTCP تعتبر من النوع الموجه (Connection Oriented) أي يحتاج الى انشاء جلسة مع الطرف المقابل لتبادل البيانات ويدعم وصول البيانات بشكل سليم الى المستلم لكن هذا يسبب بطئ في نقل البيانات الى الطرف المستقبل لان هذا البروتوكول يدعم عمليات التحقق (Authentication) من وصول البيانات بشكل كامل وسليم الى وجهتها ,يستخدم عادة لنقل الرسائل.
[/COLOR]
((رب اشرح لي صدري ويسر لي امري واحلل عقدة من لساني يفقهوا قولي))
صدق الله العلي العظيم
[/COLOR]
[COLOR="#000080"]السلام عليكم اعضاء و زوار المنتدى الكرام ورحمة الله وبركاته ارجو ان تكونوا بتمام الصحة والعافية ان شاءالله
سنتعلم في هذا المقال ان شاءالله كيفية بناء مشروع لنقل البيانات المعقدة عبر الشبكة عن طريق استخدام بروتوكول 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 sender, EventArgs e)
{
sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.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.InterNetwork, SocketType.Stream, ProtocolType.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 sender, FormClosingEventArgs 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 namelen, deptlen;
///////////////////////////////////////////
public static int pos = 0;
public static int size = 0;
public static byte[] info;
///////////////////////////////////////////
public static byte[] InfoToBytes(int id, string name, int age, string dept,double avg)
{
namelen = name.Length;
deptlen = dept.Length;
///////////////////////////////////////////////
info=new byte[1024];
//id to byte array
Buffer.BlockCopy( BitConverter.GetBytes(id), 0, info, pos,4);
pos += 4;
//namelength to byte array
Buffer.BlockCopy(BitConverter.GetBytes(namelen), 0, info, pos, 4);
pos += 4;
//name to byte array
Buffer.BlockCopy(UnicodeEncoding.Default.GetBytes(name), 0, info, pos, namelen);
pos += namelen;
//age to byte array
Buffer.BlockCopy(BitConverter.GetBytes(age), 0, info, pos, 4);
pos += 4;
//deptlength to byte array
Buffer.BlockCopy(BitConverter.GetBytes(deptlen), 0, info, pos, 4);
pos += 4;
//dept to byte array
Buffer.BlockCopy(UnicodeEncoding.Default.GetBytes(dept), 0, info, pos, deptlen);
pos += deptlen;
//avg to byte array
Buffer.BlockCopy(BitConverter.GetBytes(avg), 0, info, pos, 8);
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 id, age;
string name, dept;
double avg;
public Form1()
{
InitializeComponent();
}
private void SendInfobtn_Click(object sender, EventArgs e)
{
//set student information
setinfo();
//converting student information to a single byte array
data = StudentInfo.InfoToBytes(id, name, age, dept,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 id, age;
public static string name, dept;
public static double avg;
///////////////////////////////////////////
public static int namelen, deptlen;
///////////////////////////////////////////
public static int pos = 0;
///////////////////////////////////////////
public static void GetStudentInfo(byte[] data)
{
id = BitConverter.ToInt32(data, pos);
pos += 4;
namelen = BitConverter.ToInt32(data, pos);
pos += 4;
name = UnicodeEncoding.Default.GetString(data, pos, namelen);
pos += namelen;
age = BitConverter.ToInt32(data, pos);
pos += 4;
deptlen = BitConverter.ToInt32(data, pos);
pos += 4;
dept = UnicodeEncoding.Default.GetString(data, pos, deptlen);
pos += deptlen;
avg = BitConverter.ToDouble(data, pos);
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.Any, 8002);
//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(data, 0, data.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 sender, FormClosingEventArgs e)
{
listen.Stop();
th.Abort();
GC.Collect();
}
}
}
[COLOR="#0000CD"]اهم الملاحظات:
1- انشاء كائن TcpListener ثم بدء الجلسة مع الطرف المرسل
2- قبول الجلسة مع المرسل باستخدام دالة الAcceptSocket
3- انشاء NetworkStream و BinaryReader
4- قراءة الحجم المرسل
5- قراءة البيانات المرسلة الى مصفوفة بايتات
6- تمرير المصفوفة الى دالة الReceiveStudentInfo.GetStudentInfo لاسترجاع البيانات المرسلة
[/COLOR]