ثانيا: Remotting Programming :
يعتبر موضوع الـ Remotting من المواضيع الهامة جدا في برمجة الشبكات , إذ يقدم لنا إمكانيات رائعة في مشاركة و استخدام الـ Remote Methods والموجودة على الـ Remote Server وتشبه عملية التعامل مع الـ Remotting عملية الـ Web Services إلى حد كبير حيث يتطلب استخدامها عمل Proxy Object في طرف الـClient, لكن مع إمكانيات اكبر حيث تقوم بإنشاء الـServer بنفسك ولست مضطرا لتثبيت
الـ IIS أو غيره من التطبيقات الوسيطة للاستفادة من خدمات الـ Remote Server , وتستطيع من خلاله تنفيذ أي عملية معالجة على الـ Server مما يعزز من مبدأ الـ Distributed Systems والذي شرحناه في الجزء الأول من هذا الفصل حيث تبقى الـ methods و عملية المعالجة الخاصة بها على الـ Server في حين يتم إرسال الـ Result إلى الـ Clients كـ XML Data أو Binary Data حسب قناة الاتصال المستخدمة سواء HttpChannel أو TcpChannel , وتمرر إلى الـ Client عبر الـ Proxy Object والذي ربط الـ Remote Class الموجود على الـ Server مع الـ Client لاحظ الشكل التالي:

1- Using Remotting in Dot Net :
يمكن استخدام الـ Remotting بطريقتين الأولى عبر بروتوكول الـ HTTP حيث تتم عملية التراسل كـ XML Data بين الـ Server والـ Clients وتتم باستخدام بروتوكول
الـ SOAP أما الطريقة الثانية فتتم باستخدام الـ TCP Protocol حيث تكون عملية التراسل كـ Binary Data بين الـ Server وبقية الـ Clients , ويفضل دائما استخدام
الـ TCP Channel في الحالتين التاليتين:
الأولى: إذا كانت قناة الاتصال سريعة نسبيا
الثانية: توافقية البيئات حيث تستخدم نظام التشغيل Microsoft Windows في جميع الـ clients , ويعتبر هذه الأمر من أهم عيوب استخدام الـ TCP Channel وخاصة في حالة الـWeb حيث لا تضمن البيئة التي سيعمل عليها الـ Client ويفضل في هذه الحالة استخدام الـ HTTP Channel , لكن تبقى أدائية الـ TCP Channel أفضل من الـ HTTP ...
وما يميز الـ HTTP Channel هو استخدامه بروتوكول الـ SOAP والذي يتعامل مع الـXML في عملية التراسل مما يضمن توافقيته مع جميع البيئات.
يلزم استخدام الـ Remotting في الدوت نيت ثلاثة أمور :
أولا, تحديد طبيعة القناة المستخدمة سواء عبر الـ HTTP كـXML أو عبر الـ TCP كـBinary Formatter ودعمت الدوت نيت كلا منهما في الـClass HttpChannel والموجود ضمن System.Runtime.Remoting.Channels.Http Namespace والـTcpChannel Class والموجود ضمن الـ System.Runtime.Remoting.Channels.Tcp Namespace (يجب إضافته إلى الـ References في المشروع), حيث يجب تعريفها في كلا الطرفين الـ Client والـ Server وكما يلي:
C#: HttpChannel http_Channel = new HttpChannel(Port_Number); VB.NET: Dim http_Channel As HttpChannel = New HttpChannel(Port_Number)
وفي حالة استخدام الـ TcpChannel كما يلي:
C#: TcpChannel Tcp_Channel = new TcpChannel(Port_Number); VB.NET: Dim Tcp_Channel As TcpChannel = New TcpChannel(Port_Number)
بعد ذلك يجب تسجيل القناة المستخدمة باستخدام الميثود RegisterChannel والموجودة ضمن الـ ChannelServices Class ضمن الـSystem.Runtime.Remoting.Channels Namespace , ويتم ذلك في كلا الطرفين أيضا الـ Client والـ Server وكما يلي:
C#: ChannelServices.RegisterChannel(chan); VB.NET: ChannelServices.RegisterChannel(chan)
بعد ذلك يجب تعريف الـ Class الذي سيتم مشاركته على الـ Server ويتم ذلك باستخدام الميثود RegisterWellKnownServiceType والموجودة ضمن الـ RemotingConfiguration Class والموجود ضمن الـ System.Runtime.Remoting Namespace ويتم ذلك كما يلي في طرف الـ Server:
C#:
RemotingConfiguration.RegisterWellKnownServiceType (Type.GetType ("Class_File_Name, Distributed_Class_Name"), "unique_Service_Name", WellKnownObjectMode.SingleCall);
VB.NET:
RemotingConfiguration.RegisterWellKnownServiceType (Type.GetType ("Class_File_Name, Distributed_Class_Name"), "unique_Service_Name", WellKnownObjectMode.SingleCall)وتأخذ الـ RegisterWellKnownServiceType Method ثلاثة باروميترات الأول الـType الخاص باسم الـ Remote Class مع اسم الـ Dll File ومساره والذي نريد توزيعه ويأخذ الباروميتر الثاني اسم الـ URI الخاص بالـ Object الذي نريد توزيعه ويجب أن يكون
الـ URI Object اسم فريد unique على مستوى الـ Application Port , ويأخذ الباروميتر الثالث نوع الـ Remote Instance Object فإذا تم اختيار SingleCall فهذا يعني إنشاء New Instance جديد لكل Client يقوم بإنشاء Instance من الـ Remote Class ,
وإذا تم اختيار Singleton فهذا يعني استخدام نفس الـ Remote Instance Object لكل الـ Clients , وينصح استخدام SingleCall في حالة كان النظام الموزع سيستخدم Session لكل User وينصح باستخدام Singleton في حالة كان الـ Server سيبث نفس المعلومات إلى كل الـ Clients على الشبكة...
أما في الـ Client فيجب تعريف نوع الـ Channel سواء TCP أو HTTP Channel حسب نوع القناة التي تم استخدمها في الـ Server ولا يتم وضع رقم الـ Port في الـ Client Channel ونكتفي بوضعه في الـ URI Object ويتم تعريف القناة في الـ Client كما يلي:
C#: HttpChannel http_Channel = new HttpChannel VB.NET: Dim http_Channel As HttpChannel = New HttpChannel()
وفي حالة استخدام الـ TcpChannel كما يلي:
C#: TcpChannel Tcp_Channel = new TcpChannel(); VB.NET: Dim Tcp_Channel As TcpChannel = New TcpChannel()
ويتم تسجيل القناةRegisterChannel في الـ Client كما هو الحال في الـ Server وكما يلي:
C#: ChannelServices.RegisterChannel(chan); VB.NET: ChannelServices.RegisterChannel(chan)
بعد ذلك نقوم بتعريف New Instance Object من الـ Remote Client حيث يجب أن يحتوي الـ Client Project على الـ Dll File الخاص بالـ Distributed Class حيث نقوم في البداية بتعريف الـ URI والذي سيحتوي على الـ Protocol المستخدم ويتبع بعنوان الجهاز أو الـ DNS الخاص بالـ Server ومن ثم رقم الـ Port المستخدم و اسم الخدمة التي تم تعريفها في الـ Server ويتم ذلك كما يلي:
في حالة استخدام TCP Channel :
C#: string URI = "Tcp://"+Remote_IP.Text+":Port/ unique_Service_Name "; VB.NET: Dim URI As String = "Tcp://" & Remote_IP.Text & ": Port/ unique_Service_Name "
في حالة استخدام HTTP Channel :
C#: string URI = "Http://"+Remote_IP.Text+": Port / unique_Service_Name"; VB.NET: Dim URI As String = " Http://" & Remote_IP.Text & ": Port / unique_Service_Name "
وبعد ذلك نقوم بإنشاء New Instance Object من الـ Remote Class ويتم ذلك باستخدام الـ GetObject Method والموجود ضمن الـ Activator Class ونمرر لها
الـ Distributed Class Type و الـ URI الذي عرفناه , وكما يلي:
C#: Distributed_Class_Name obj = (Distributed_Class_Name) Activator.GetObject (typeof (Distributed_Class_Name), URI); VB.NET: Dim obj As Distributed_Class_Name = CType(Activator.GetObject(GetType(Distributed_Class_Name), URI), Distributed_Class_Name)
ملاحظة هامة جدا:
يجب أن يتم توريث الـ MarshalByRefObject Class إلى الـ Distributed Class الذي نريد توزيعه والموجود ضمن System Namespace وعند توريث الـ MarshalByRefObject إلى الـ Distributed Class سيتم تنفيذ كافة العمليات أو الـ Process على الـ Server وسيحصل الـ Client على الـ Result النهائي في حين إذا تم استخدام الـMarshalByValueObject عندها سيتم استخدام الـ Remote Class و لكن ستتم عملية المعالجة على الـ Client Side ...
وهكذا بينا كيفية استخدام الـ Remotting في الدوت نيت , سنقوم الآن بتطبيق هذه المفاهيم في مشروع حيث سنقوم بإنشاء Distributed Class يقوم بعملية التقاط لصورة سطح المكتب الخاص بالـ Server وبثها إلى الـ Client مستخدما الـ TcpChannel بين الـ Server والـ Client وسنفترض في هذا المثال إنشاء Remote Classroom حيث يقوم المعلم بإلقاء محاضرة إلى طلابه ويتم بث صورة الكاميرا وسطح المكتب الخاص بالمعلم عبر الإنترنت إلى الطلاب في الـ Classroom والموجودين في دول متعددة وكما في الشكل التالي:
سنستخدم في هذا المثال الـ TCP Channel والـ HttpChannel وسنقارن الأداء في كل منهما ,برنامج المحاضر (Lecturer Project) وسيكون الشكل العام له كما هو مبين في الشكل التالي حيث:
برنامج الـ Client ويمكن أن يكون من خلال الإنترنت في حالة كان لديك Real IP أو
في الشبكة المحلية وكما هو مبين في الشكل التالي:
ولإنشاء الـ Remote Class والذي سيقوم بجلب صورة سطح المكتب , سنستخدم دوال الـ API ومنها الـ GetDesktopWindow Method والموجودة ضمن الـuser32.dl والـ BitBlt Method لرسم الصورة , ثم سنقوم بتحويل الصورة إلى Byte Array وحتى نستطيع إرسالها إلى الـ Clients , وبتأكيد سنستخدم طريقة MarshalByRefObject وحتى تتم عملية التقاط ومعالجة الصورة على الـ Server Side , ويتم ذلك كما يلي :
C#:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.IO;
public class ScreenCapture : System.MarshalByRefObject
{
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("gdi32.dll")]
private static extern bool BitBlt(
IntPtr hdcDest, // handle to destination DC
int nXDest, // x-coord of destination upper-left corner
int nYDest, // y-coord of destination upper-left corner
int nWidth, // width of destination rectangle
int nHeight, // height of destination rectangle
IntPtr hdcSrc, // handle to source DC
int nXSrc, // x-coordinate of source upper-left corner
int nYSrc, // y-coordinate of source upper-left corner
System.Int32 dwRop // raster operation code
);
private const Int32 SRCCOPY = 0xCC0020;
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
private const int SM_CXSCREEN = 0;
private const int SM_CYSCREEN = 1;
public Size GetDesktopBitmapSize()
{
return new Size(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
}
public byte[] GetDesktopBitmapBytes()
{
Size DesktopBitmapSize = GetDesktopBitmapSize();
Graphics Graphic = Graphics.FromHwnd(GetDesktopWindow());
Bitmap MemImage = new Bitmap(DesktopBitmapSize.Width, DesktopBitmapSize.Height, Graphic);
Graphics MemGraphic = Graphics.FromImage(MemImage);
IntPtr dc1 = Graphic.GetHdc();
IntPtr dc2 = MemGraphic.GetHdc();
BitBlt(dc2, 0, 0, DesktopBitmapSize.Width, DesktopBitmapSize.Height, dc1, 0, 0, SRCCOPY);
Graphic.ReleaseHdc(dc1);
MemGraphic.ReleaseHdc(dc2);
Graphic.Dispose();
MemGraphic.Dispose();
Graphics g = System.Drawing.Graphics.FromImage(MemImage);
System.Windows.Forms.Cursor cur = System.Windows.Forms.Cursors.Arrow;
cur.Draw(g,new Rectangle(System.Windows.Forms.Cursor.Position.X-10,System.Windows.Forms.Cursor.Position.Y-10,cur.Size.Width,cur.Size.Height));
MemoryStream ms = new MemoryStream();
MemImage.Save(ms,System.Drawing.Imaging.ImageFormat.Jpeg);
return ms.GetBuffer();
}
}
VB.NET
Imports System
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Imports System.IO
Public Class ScreenCapture : Inherits System.MarshalByRefObject
<DllImport("user32.dll")> _
Private Shared Function GetDesktopWindow() As IntPtr
End Function
<DllImport("gdi32.dll")> _
Private Shared Function BitBlt(ByVal hdcDest As IntPtr, ByVal nXDest As Integer, ByVal nYDest As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hdcSrc As IntPtr, ByVal nXSrc As Integer, ByVal nYSrc As Integer, ByVal dwRop As System.Int32) As Boolean
End Function
Private Const SRCCOPY As Int32 = &HCC0020
<DllImport("user32.dll")> _
Private Shared Function GetSystemMetrics(ByVal nIndex As Integer) As Integer
End Function
Private Const SM_CXSCREEN As Integer = 0
Private Const SM_CYSCREEN As Integer = 1
Public Function GetDesktopBitmapSize() As Size
Return New Size(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN))
End Function
Public Function GetDesktopBitmapBytes() As Byte()
Dim DesktopBitmapSize As Size = GetDesktopBitmapSize()
Dim Graphic As Graphics = Graphics.FromHwnd(GetDesktopWindow())
Dim MemImage As Bitmap = New Bitmap(DesktopBitmapSize.Width, DesktopBitmapSize.Height, Graphic)
Dim MemGraphic As Graphics = Graphics.FromImage(MemImage)
Dim dc1 As IntPtr = Graphic.GetHdc()
Dim dc2 As IntPtr = MemGraphic.GetHdc()
BitBlt(dc2, 0, 0, DesktopBitmapSize.Width, DesktopBitmapSize.Height, dc1, 0, 0, SRCCOPY)
Graphic.ReleaseHdc(dc1)
MemGraphic.ReleaseHdc(dc2)
Graphic.Dispose()
MemGraphic.Dispose()
Dim g As Graphics = System.Drawing.Graphics.FromImage(MemImage)
Dim cur As System.Windows.Forms.Cursor = System.Windows.Forms.Cursors.Arrow
cur.Draw(g, New Rectangle(System.Windows.Forms.Cursor.Position.X - 10, System.Windows.Forms.Cursor.Position.Y - 10, cur.Size.Width, cur.Size.Height))
Dim ms As MemoryStream = New MemoryStream
MemImage.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg)
Return ms.GetBuffer()
End Function
End Classوبتأكيد يجب تحويل الـ Class السابق إلى Dll File ثم وضعه بجانب الملف التنفيذي للمشروع , وأيضا إضافته ضمن الـ References في مشروع الـ Client وحتى نستطيع عمل الـ Casting على الـ Object القادم من الـ Sever ...
أولا : إنشاء الـ Remotting Server :
سيحتوي برنامج الـ Server على الأمور التالية:
سنقوم الآن بإنشاء وتسجيل TCP Remote Channel ثم إنشاء الـ Server Object ويتم ذلك كما يلي:
C#:
TcpChannel chan = new TcpChannel(6600);
ChannelServices.RegisterChannel(chan);
RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType("ScreenCapture, ScreenCapture"), "MyCaptureScreenServer",WellKnownObjectMode.Singleton);
VB.NET
Dim chan As TcpChannel = New TcpChannel(6600)
ChannelServices.RegisterChannel(chan)
RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType("ScreenCapture, ScreenCapture"), "MyCaptureScreenServer",WellKnownObjectMode.Singleton)قمنا بتسمية الـ Server Object بـ MyCaptureScreenServer و طبيعة الـ Instance سيكون Singleton وهذا يعني استخدام نفس الـ Instance Object لكل الـ Clients و في حالة إذا أردنا إنشاء New Instance Object لكل Client Request فيجب استخدام الـ SingleCall وفي حالتنا هذه ينصح باستخدام الأول وحتى لا يزيد الـ Load على الـServer في حالة ربطه على مجموعة كبيرة من الـ Clients إذ أن المعلومات التي يتم بثها إلى الـ Clients هي نفسها ...
ولربط الـ WebCamCapture Class في الـ Picturebox نضع الكود التالي في حدث ImageCaptured الخاص بالـ WebCamCapture Class وكما يلي:
C#:
private void webCamCapture1_ImageCaptured(object source, WebCam_Capture.WebcamEventArgs e)
{
pictureBox1.Image = e.WebCamImage;
}
VB.NET:
Private Sub webCamCapture1_ImageCaptured(ByVal source As Object, ByVal e As WebCam_Capture.WebcamEventArgs)
pictureBox1.Image = e.WebCamImage
End Subوالآن نستطيع تشغيل الكاميرا عند الضغط على الـ Start Button الموجود في الـ main Menu ونضع في الحدث الخاص بها كود التشغيل ونمرر له الـ Period Time لكل عملية التقاط وهو مثلا 1 Milliseconds ويتم ذلك كما يلي:
C#: this.webCamCapture1.TimeToCapture_milliseconds = 1; this.webCamCapture1.Start(0); VB.NET: Me.webCamCapture1.TimeToCapture_milliseconds = 1 Me.webCamCapture1.Start(0)
ثانيا : إنشاء الـ Remotting Client :
ويحتوي أيضا على الأمور التالية:
وسنقوم بتعريف المتغيرات والـ Objects التالية في الـ Global Declaration للبرنامج:
C#: ScreenCapture obj; TcpChannel chan; string URI; VB.NET: Dim obj As ScreenCapture Dim chan As TcpChannel Dim URI As String
سنستخدم الـScreenCapture Object لعمل الـ Casting على الـ Incoming Remote Instance Object , وسنستخدم الـ TcpChannel Object لإنشاء الـ Remotting Channel وسنضع في الـ URI String Variable الـ URI الخاص بالـ Server Object والذي تم تعريفه في الـ Server .
ثم سنقوم بوضع التعريفات الأساسية للـ TCP Channel والـ Remote Object في الـConstructer الخاص بالمشروع وكما يلي:
C#:
public Form1()
{
URI = "Tcp://"+textBox1.Text+":6600/MyCaptureScreenServer";
chan = new TcpChannel();
ChannelServices.RegisterChannel(chan);
obj = (ScreenCapture)Activator.GetObject(typeof(ScreenCapture), URI);
}
VB.NET:
URI = "Tcp://" & textBox1.Text & ":6600/MyCaptureScreenServer"
chan = New TcpChannel()
ChannelServices.RegisterChannel(chan)
obj = CType(Activator.GetObject(GetType(ScreenCapture), URI), ScreenCapture)حيث سيتم تمرير الـ Remote IP أو الـ DNS الخاص بالـ Server من الـ TextBox ويتبعه رقم الـ Port والـ Remote Object الذي تم تعريفه في الـ Client...
ولجلب الصورة سنضع الكود التالي في Timer حيث يجلب الصورة من الـ Server كل فترة محددة:
C#:
private void timer1_Tick(object sender, System.EventArgs e)
{
try
{
URI = "Tcp://"+textBox1.Text+":6600/MyCaptureScreenServer";
byte[] buffer = obj.GetDesktopBitmapBytes();
MemoryStream ms = new MemoryStream(buffer);
pictureBox1.Image = Image.FromStream(ms);
}
catch(Exception ex){timer1.Enabled=false; MessageBox.Show(ex.Message);}
}
VB.NET:
Private Sub timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs)
Try
URI = "Tcp://" & textBox1.Text & ":6600/MyCaptureScreenServer"
Dim buffer As Byte() = obj.GetDesktopBitmapBytes()
Dim ms As MemoryStream = New MemoryStream(buffer)
pictureBox1.Image = Image.FromStream(ms)
Catch ex As Exception
timer1.Enabled = False
MessageBox.Show(ex.Message)
End Try
End Subولتحويل قناة الاتصال إلى Http Channel فقط قم بتغيير الـ TcpChannel إلى HttpChannel وعندها ستتمكن من مشاهدة الـ Remote Methods عبر الـ Internet Browser ولمشاهدتها نضيف الـ WSDL – Services Definition Language Query التالي إلى الـ URI الخاص بالـ Remote Object:
http://10.0.0.10:6600/MyCaptureScreenServer?wsdl
وستظهر لنا الـ Remote Interface Class بهيئة XML لاحظ الشكل التالي:
وتستطيع الاستفادة من التركيب السابق للـ Class في حالة لم تتمكن من الحصول على الـ Dll File الخاص بالـ Remote Class , حيث تستطيع تحويل الـ XML السابق إلى ملف Dll لوضعه مع الـ References في برنامج الـ Client ويتم ذلك باستخدام الـMetadata Namespace ومنها الـ w3cxsd2001 Standard حيث يوضع الجزء الأول الخاص بالـ XML Namespace في الـ Serializable Attribute وكما يلي كمثال:
C#:
using System;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Metadata;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
[Serializable,SoapType(XmlNamespace=@"http://http://schemas.microsoft.com/clr/nsassem/ScreenCapture/ScreenCapture%2C%20Version%3D1.0.2314.30913%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull")]
public class ScreenCapture : System.MarshalByRefObject
{// Your Methods with Return Values Only}
VB.NET:
Imports System
Imports System.Runtime.Remoting.Messaging
Imports System.Runtime.Remoting.Metadata
Imports System.Runtime.Remoting.Metadata.W3cXsd2001
<Serializable(), SoapTypeAttribute(XmlNamespace:="http://http://schemas.microsoft.com/clr/nsassem/ScreenCapture/ScreenCapture%2C%20Version%3D1.0.2314.30913%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull"), Serializable(), SoapTypeAttribute(XmlNamespace:="http://http://schemas.microsoft.com/clr/nsassem/ScreenCapture/ScreenCapture%2C%20Version%3D1.0.2314.30913%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull")> _
Public Class ScreenCapture : Inherits System.MarshalByRefObject
‘Your Methods with Return Values Only
End Classوللاختيار بين الـ TCP أو الـ HTTP Channel فيفضل دائما في حالة كان الـ Remote Object موجود على جهاز تتصل معه عبر الإنترنت استخدام الـ HTTP Channel بسبب محدودية سرعة الاتصال بالإضافة إلى كونه يتوافق مع أي نظام سواء Windows أو غيره لكن يبقى آداء وسرعة الـ TCP Channel أفضل فقط في الـ Local Network ...
وهكذا بينا كيفية استخدام الـ Remotting في الدوت نيت وطبقنا عملية إنشاء Distributed eLearning System , وبتأكيد تستطيع الآن إضافة ميزة نقل الصوت إلى جانب نقل صورة الشاشة والكاميرا الخاصة بالمحاضر (لمزيد من المعلومات ارجع إلى الفصل الخاص بالـVOIP) , وسنبين في الجزء التالي من هذا الفصل كيفية إنشاء برنامج Remote Desktop Application مع خاصية التحكم (والذي سيتم الحديث عنه في النسخة الورقية من الكتاب) ...
لتحميل الدرس والأمثلة انظر المرفقات:
تم تعديل هذه المشاركة بواسطة fmo_82 في 4 مايو 2006 في 20:39
