في الجزء الأول من هذه السلسلة كتبت عن أساسيات الشبكات ، فى هذه المقالة سوف نبدأ العمل مع صباح الخير ومكتبة CocoaAsyncSocket
نظرة عامة.
فى هذه المقالة ، نحن نركز على إقامة أتصال بين جهازين لتشغيل اللعبة التى نحن على وشك إنشاءها ، على الرغم من أن هذا قد يبدوا تافها , وهناك تماما عدد قليل من العناصر المشتركة لإنشاء هذا العمل ، و قبل أن تبدأ أيدينا فى التعب , إسمحوا لي أن أخذكم الى خطوات العملية خطوة بخطوة.
فى المقال السابق كتبت عن نموذج العميل والملقم ، قبل أن ننشىء اللعبة نفسها نحن بحاجة إلى تطبيق نموذج العميل – الملقم فى االتطبيق على جهازين كل جهاز سيكون مختلف عن الأخر، فى هذه الحالة لن يستطيعوا العثور على بعضهما على شبكة الأتصال وبدء اللعبة، بالتأكيد واحد منهم يجب تشغيله كخادم وجعله معروفا على الشبكة حتى تستطيع باقي الأجهزة الدخول الية .
فى المقال السابق كتبت عن نموذج العميل والملقم ، قبل أن ننشىء اللعبة نفسها نحن بحاجة إلى تطبيق نموذج العميل – الملقم فى االتطبيق على جهازين كل جهاز سيكون مختلف عن الأخر، فى هذه الحالة لن يستطيعوا العثور على بعضهما على شبكة الأتصال وبدء اللعبة، بالتأكيد واحد منهم يجب تشغيله كخادم وجعله معروفا على الشبكة حتى تستطيع باقي الأجهزة الدخول الية .
هناك نهج مشترك لحل هذه المشكله بالسماح للاعبين لإنشاء مضيف ، أو الدخول إلي اللعبة , الجهاز الخاص باللاعب المستضيف للعبة يجب أن يعمل كخادم , بينما يعمل الجهاز الخاص باللاعبين المراد دخولهم الى اللعبة كعميل ، للإتصال بالملقم او السيرفر.
الملقم يوفر خدمة إظهار ذلك على شبكة الأتصال بإستخدام صباح الخير، عندما يحاول لاعب الأنضمام إلي اللعبة يقوم بالبحث عن الشبكات والخدمات أيضا بأستخدام صباح الخير , عند إنضمامه الى اللعبة ’ يتم اصلاح السيرفر وتوفير وانشاء أتصال بين الجهازين عندها يتم بدء اللعبة.
إذا اختلط عليك الأمر من ناحية نموذج الملقم / السيرفر، ننصحك بأعادة النظر فى الجزء الأول من هذه السلسله التي تحتوي على مزيد من التفاصيل حول الملقم / السيرفر.
صباح الخير- Bonjour
ما هو دور صباح الخير فى هذا الإعداد صباح الخير هي المسؤلة عن النشر وإكتشاف الخدمات على الشبكة صباح الخير أيضا تستخدم فى أكتشاف النقاط او الأجهزة الأخري المتصله بالشبكة , مهمة جدا لتتذكر أن صباح الخير ليست مسؤولة عن إقامة إتصال بين الملقم والعميل ..
يمكننا الأستفاده من مكتبة CocoaAsyncSocket لإنجاز هذه المهمة .
)CocoaAsyncSocketمكتبة - (
مكتبة CocoaAsyncSocket يأتي دورها عندما يتعين علينا أن نتعامل مع مأخذ التوصيل ، والموانىء والأتصالات .
كما انها تساعدنا على إرسال البيانات من أحد طرفي الأتصال الي الطرف الأخر فى كلا الأتجاهين ، على الرغم من أن صباح الخير ليست مسؤؤلة عن إقامة اتصال بين العمليتين , فهي تزودنا بالمعلومات التى نحن بحاجة إليها لتأسيس الإتصال .
وكما ذكرت فى وقت سابق من هذه السلسة , صباح الخير و CocoaAsyncSocket لها تركيبة قوية كما ستشاهد فى هذه المقالة .
الإختبارات :
الإختبارات أحد الجوانب الرئيسية لتطوير البرمجيات، لا سيما عندما يتعلق الأمر بمشاركة الشبكات لإختبار عنصر الشبكة للتطبيق الخاص بنا , سوف تحتاج الى تشغيل مثيلين لها .
ويمكن القيام بذلك عن طريق تشغيل مثيل واحد فى iOS Simulator ونسخة ثانية على جهاز فعلي اخر جهاز واحد سيكون بمثابة خادم يقوم باستضافة اللعبة بينما المثيل الأخر سيكون بمثابة العميل (client) يقوم بالبحث عن الشبكات الخاصة باللعبة ليستطيع الدخول اليها .
1- إعداد المشروع (Project)
ويمكن القيام بذلك عن طريق تشغيل مثيل واحد فى iOS Simulator ونسخة ثانية على جهاز فعلي اخر جهاز واحد سيكون بمثابة خادم يقوم باستضافة اللعبة بينما المثيل الأخر سيكون بمثابة العميل (client) يقوم بالبحث عن الشبكات الخاصة باللعبة ليستطيع الدخول اليها .
1- إعداد المشروع (Project)
فتح Xcode وإنشاء مشروع جديد إستنادا الي القالب (الشكل 1) أسم المشروع أربعة فى صف واحد ، وتعيين أجهزة ايفون iPhone والتأكد من أنه تم تمكين القوس (Automatic Reference Counting) وتشغيله للمشروع (الشكل 2) نحن لن نستخدم القصص المصورة فى هذا البرنامج التعليمي .
2-إضافة ال ( CocoaAsyncSocket)
إضافة مكتبة CocoaAsyncSocket سهلة إذا أخترت إستخدام CocoaPods كما شرحت فى وظيفة سابقة, ومع ذلك حتي بدون CocoaPods اضف مكتبة CocoaAsyncSocket الي مشروعك هي ليست بعلم الصواريخ العملية بسيطة .
الخطوة الأولي :
قم بتحميل أحدث إصدار من مكتبة GitHub وتوسيع الأرشيف . حدد موقع المجلد المسمي ب GCD واسحب GCDAsyncSocketh و GCDAsyncSocket.m
داخل مشروع ال Xcode واضافتهم الي الأربعة فى صف واحد (الشكل 3) .
إضافة مكتبة CocoaAsyncSocket
الخطوة الثانية :
مكتبة CocoaAsyncSocket تعتمد على ال CFNetwork و Framework امن مما يعني اننا بحاجه الي ربط مشروعنا ضد Framework ، قم بفتح المشروع Project الخاص بك فى متصفح المشروع Project Navigator)) وحدد الأربعه فى صف .
من قائمة الأهداف ، أختر علامة التبويب "مراحل بناء" Build Phases tab فى الأعلى وفتح " الأرتباط الثنائي" Link Binary مع درج المكتبات . انقر فوق الزر زائد وربط المشروع Xcode ضد CFNetwork و Frameworks امن .
(الشكل 4)
ربط المشروع ضد CFNetwork و أمن ال Frameworks
الخطوة الثالثة :
قبل المتابعة , أضف عبارة إستيراد الي رأس مشروع (Projects) الملف المترجم مسبقا لإستيراد رأس الملف الذى قمنا بإضافتة الى Xcode الخاصة بمشروعنا , هذا يجعل من الممكن أن نستخدم فئة GCDAsyncSocket فى جميع مشروعاتنا
قبل المتابعة , أضف عبارة إستيراد الي رأس مشروع (Projects) الملف المترجم مسبقا لإستيراد رأس الملف الذى قمنا بإضافتة الى Xcode الخاصة بمشروعنا , هذا يجعل من الممكن أن نستخدم فئة GCDAsyncSocket فى جميع مشروعاتنا
ربما لاحظتم أننا لم نضيف الملف GCDAsyncUdpSocket.h و GCDAsyncUdpSocket.m لمشروعنا كالملف المسمي indiciate#import <Availability.h> #ifndef __IPHONE_4_0#warning "This project uses features only available in iOS SDK 4.0 and later."#endif #ifdef __OBJC__ #import <UIKit/UIKit.h> #import <Foundation/Foundation.h> #import "GCDAsyncSocket.h"#endif
هذه الملفات تعلن وتطبق فئة GCDAsyncUpdSocket المسؤؤلة عن العمل مع بروتوكول UDP على الرغم من أننا سوف نعمل فقط مع بروتوكول TCP فى هذه السلسلة, ضع فى اعتبارك أن المكتبة CocoaAsyncSocket تدعم ايضا بروتوكول UDP
3- إنشاء وحدة تحكم مضيف اللعبة
الخطوة الأولي"
عندما يقوم شخص بتشغيل التطبيق الخاص بنا , لديه اثنين من الخيارات الأختيار الأول (1) وهو إستضافة لعبة , الأختيار الثاني (2) هو الأنضمام الي اللعبة التي يتم أستضافتها بواسطة شخص لاعب اخر.
عندما يقوم شخص بتشغيل التطبيق الخاص بنا , لديه اثنين من الخيارات الأختيار الأول (1) وهو إستضافة لعبة , الأختيار الثاني (2) هو الأنضمام الي اللعبة التي يتم أستضافتها بواسطة شخص لاعب اخر.
الواجهه واضحه جدا كما يمكنك أن تتخيل , فتح MTViewController.Xib وإضافة اثنين من الأزرار لعرض وحدة التحكم وتعطي لك زر العنوان المناسب .
( الشكل 5)
إنشاء واجهة المستخدم
الخطوة الثانية "
إضافة اثنين من الإجراءات (Host Game) و Join Game)) لتنفيذ وحدة التحكم بعرض الملف .
وقم بتوصيل كل عمل مع الزر المناسب فى MTViewController.xib
(IBAction)hostGame:(id)sender {
(IBAction)joinGame:(id)sender {
الخطوة الثالثة "
عندما ينتقل المستخدم إلى زر عنوان مستضيف اللعبة Host a Game)) سوف يقدم للمستخدم مع طريقة عرض مشروطة تحت غطاء .
عندما ينتقل المستخدم إلى زر عنوان مستضيف اللعبة Host a Game)) سوف يقدم للمستخدم مع طريقة عرض مشروطة تحت غطاء .
سوف يقوم التطبيق
بنشر خدمة صباح الخير Bonjour)) ومكتبة CocoaAsyncSocket عندما ينضم لاعب آخر الى اللعبة .
سيتم إعلام المستخدم المستضيف للعبة ووقتها يمكن بدء اللعبه , قم بإنشاء UIViewController فئة فرعية
وقم بتسميته إلي MTHostGameViewController أخبر Xcode لأنشاء ملف XIB لفئة تحكم العرض الجديدة . ( الشكل 6 )
وقم بتسميته إلي MTHostGameViewController أخبر Xcode لأنشاء ملف XIB لفئة تحكم العرض الجديدة . ( الشكل 6 )
إنشاء وحدة تحكم عرض اللعبة المستضيفة
الخطوة الرابعة"
إضافة عبارة استيراد لجهاز التحكم
إضافة عبارة استيراد لجهاز التحكم
بطريقة عرض جديده , إلي MTViewController.m وتنفذ في HostGame كما هو موضح أدناه , عندما ينتقل المستخدم الي الأزرار العليا.
يتم إنشاء مثيل منMTHostGameView ’ وتعيين كوحدة تحكم عرض مشروط.
يتم إنشاء مثيل منMTHostGameView ’ وتعيين كوحدة تحكم عرض مشروط.
#import "MTHostGameViewController.h"
(IBAction)hostGame:(id)sender { // Initialize Host Game View Controller MTHostGameViewController *vc = [[MTHostGameViewController alloc] initWithNibName:@"MTHostGameViewController" bundle:[NSBundle mainBundle]]; // Initialize Navigation Controller UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc]; // Present Navigation Controller [self presentViewController:nc animated:YES completion:nil];}
الخطوة الخامسة"
فتح MTHostGameViewController.m وتنفيذ إسلوب ViewDidLoad كما هو موضح أدناه كل ما علينا القيام به استدعاء SetupView
وإضافة زر إلي شريط التنقل للسماح للمستخدم بإلغاء إستضافة اللعبه وهو أمر سوف ننفذه لاحقا فى هذا البرنامج التعليمي , أنشىء وقم بتشغيل التطبيق للمره الأولي لتري إذا كان كل شىء يعمل بشكل جيد كما متوقع أم هناك اى مشكلة .
(void)setupView { // Create Cancel Button self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel:)];}
(void)cancel:(id)sender { // Cancel Hosting Game // TODO // Dismiss View Controller [self dismissViewControllerAnimated:YES completion:nil];}
4- نشر الخدمة
عندما يقوم المستخدم بفتح وحدة عرض مستضيف اللعبة ينبغى نشر الخدمة تلقائيا على الشبكة .
للأجهزة المماثلة له والفئة التى سيتم إستخدامها لهذا الغرض , هي NSNetService على شكل ملفات NSNetService من فئة NSNetService يمثل خدمة شبكة الأتصال .
تذكر أن هذه الخدمة لا تقتصر على عملية أو تطبيق أو الطابعات أو غيرها .... من الأجهزة المتصلة بالشبكة هي أيضا يمكنها أن تجعل الخدمه معروفة وذلك بأستخدام صباح الخير (Bonjour) وهذا ما يجعل صباح الخير من أكبر المستهلكين.
والأن لتوضيح أكثر إسمحوا لي أن أعرض لكم ما يلزم لنشر خدمة أستخدام مكتبة CocoaAsyncSocket وتعديل أسلوب ViewDodload كما هو موضح ادناه ...
(void)viewDidLoad { [super viewDidLoad]; // Setup View [self setupView]; // Start Broadcast [self startBroadcast];}
قبل أن نلقي نظره على الأسلوب starBroadcast نحن بحاجة إلي , القيام ببعض الأعمال المنزلية .
إضافة ملحق فئة فى الجزء العلوي, من ملف تنفيذ وحدة تحكم ملف عرض لعبة المضيف وتعلن عن خاصيتين خاصة من نوع NSNetService ومأخذ من نوع GCDAsyncSocket فى ملحق فئة , ونحن أيضا نجعل توافقه مع عرض جهاز تحكم مضيف اللعبة .
إلي NSNetServiceDelegate و GCDAsyncSocketDelegate البروتوكول يظهر أدناه ......
إلي NSNetServiceDelegate و GCDAsyncSocketDelegate البروتوكول يظهر أدناه ......
@interface MTHostGameViewController () <NSNetServiceDelegate, GCDAsyncSocketDelegate> @property (strong, nonatomic) NSNetService *service;@property (strong, nonatomic) GCDAsyncSocket *socket; @end
تمثل خاصية خدمة الشبكة التى سنقوم بالنشر عليها بأستخدام صباح الخير Bonjour . خاصية المقبس Socket من نوع GCDAsyncSocket توفر واجهة للتفاعل مع مأخذ التوصيل التى سيتم استخدام الإصغاء للأتصالات الواردة مع الفئة الملحقة فى المكان دعونا نلقي نظره على تنفيذ الأسلوب StarBroadcast
نحن نقوم بتهيئة مثيل من فئة ال GCDAsyncSocket وتمرير عرض التحكم كمندوب لمأخذ التوصيل .
الوسيط الثاني من المهيء من قائمة انتظار GCD (Grand Central Dispatch) قائمة انتظار الأرسال الخاصة بالتطبيق الرئيسي ..
موضوعه فى هذا المثال لمكتبة CocoaAsyncSockets مما يجعلها مرنة وقوية.
(void)startBroadcast { // Initialize GCDAsyncSocket self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; // Start Listening for Incoming Connections NSError *error = nil; if ([self.socket acceptOnPort:0 error:&error]) { // Initialize Service self.service = [[NSNetService alloc] initWithDomain:@"local." type:@"_fourinarow._tcp." name:@"" port:[self.socket localPort]]; // Configure Service [self.service setDelegate:self]; // Publish Service [self.service publish]; } else { NSLog(@"Unable to create socket. Error %@ with user info %@.", error, [error userInfo]); }}
على الرغم من أن التكامل مع GCD إضافة هامة الى المكتبة CocoaAsyncSockets , الى انني لن أغطي هذا التكامل فى هذه السلسلة التعليمية.
الخطوة الثانية :
هي لأخبار وقبول الأتصالات القادمة عن طريق إرسال رسالة acceptOnPorterror ووضع 0 كرقم للمنفذ مما يعنى أن الامر متروك لنظام التشغيل لزويدنا بعدد المنافذ المتاحة هذا هو الحل الأكثر امانا عموما نحن نعرف دائما ما اذا كان منفذ معين قيد الأستخدام أو لا , قبل السماح لنظام اختيار منفذ بالنيابه عنا .
هي لأخبار وقبول الأتصالات القادمة عن طريق إرسال رسالة acceptOnPorterror ووضع 0 كرقم للمنفذ مما يعنى أن الامر متروك لنظام التشغيل لزويدنا بعدد المنافذ المتاحة هذا هو الحل الأكثر امانا عموما نحن نعرف دائما ما اذا كان منفذ معين قيد الأستخدام أو لا , قبل السماح لنظام اختيار منفذ بالنيابه عنا .
نحن يمكننا التأكد من المنفذ الذى حصلنا عليه مؤخرا اذا كان متاح او متوفر أذا كان الأتصال ناجحا , وذلك باظهار نعم Yes او اظهار خطأ Error يمكننا اذا ظبط خدمة شبكة الاتصال.
الترتيب الذي يجري فيها هام , خدمة الشبكة التى قمنا بتهيئتها تحتاج إلي رقم المنفذ الذي سنستقبل عليه الأتصال لتهيئة خدمة الشبكة قمنا بتمرير ....
(1) المجال الذى يستخدم دائما كمحلي للمجال المحلي .
(2) ونوع خدمة الشبكة هى السلسة التى تقوم بتأمين وتأكيد الأتصال بخدمة الشبكة (مثل التطبيق الخاص بنا ) .
(3) الأسم الذي يتم تحديده بواسطة شبكة الأتصال وتأكيده على الشبكة .
(4) والمنفذ الذي يتم نشرة عن طريق خدمات الشبكة .
(2) ونوع خدمة الشبكة هى السلسة التى تقوم بتأمين وتأكيد الأتصال بخدمة الشبكة (مثل التطبيق الخاص بنا ) .
(3) الأسم الذي يتم تحديده بواسطة شبكة الأتصال وتأكيده على الشبكة .
(4) والمنفذ الذي يتم نشرة عن طريق خدمات الشبكة .
النوع الذي عبرة نقوم بأستخدام initWithDomain:type:nameport: يحتاج الى تحديد نوع الخدمة وبروتوكول طبقة النقل (TCP فى هذا المثال)
العلامة السطرية _ هي لتحديد نوع الخدمة وبروتوكول طبقة النقل أمر هام أيضا . يمكنك الأطلاع على التفاصيل فى الوثائق
مع خدمة الشبكة الجاهزة للأستخدام , قمنا بتعيين الخدمة ونشرها ببساطة تعني الأعلان عن خدمة الشبكة اذا يستطيع العميل إكتشافها
اذا قمنا بتعيين كل هذابأستخدام CFNetwork بدلا من المكتبة CocoaAsyncSocket , سيتعين علينا أن نكتب قليلا من التعليمات البرمجية C المعقدة كما تري مكتبة CocoaAsyncSocket تجعل هذه العملية أسهل بكثير بتوفير واجهة "برمجة التطبيقات C API
5-الأستجابة للأحداث
لا نحتاج الي تنفيذ كل أسلوب من بروتوكول NSNetServiceDelegate فيما يلي أهمها . مندوب فى هذه الأساليب , ونحن ببساطة نقوم بأدخال رسالة الي وحدة التحكم البرمجيه حتى نتأكد ونستطيع تعقب كل ما يحدث .
(void)netServiceDidPublish:(NSNetService *)service { NSLog(@"Bonjour Service Published: domain(%@) type(%@) name(%@) port(%i)", [service domain], [service type], [service name], (int)[service port]);}
(void)netService:(NSNetService *)service didNotPublish:(NSDictionary *)errorDict { NSLog(@"Failed to Publish Service: domain(%@) type(%@) name(%@) - %@", [service domain], [service type], [service name], errorDict);}
بروتوكول GCDAsyncSocketDelegate لدية القليل من الأساليب كما تري فى تلك الوثيقة ’ أن لا تطغي عليك , سوف نقوم بتنفيذ أسلوب واحد فى وقت واحد .
الأسلوب الأول هو الأهم بالنسبة لنا socket:didAcceptNewsSocket: الذي يتم استدعاؤة عندما يقوم منفذ ألاستماع The server socket بقبول الأتصال (client connection) بسبب أننا نسمح فقط بأتصال واحد فقط فى التطبيق الخاص بنا .
نحن نتجاهل منفذ الأستماع القديم , ونرجع الي خاصية منفذ جديد من خصائص المنافذ لنكون قادرين على استخدام اتصال العميل Client Connection وهو المفتاح لإبقاء إشارة الأتصال التى مررت إلينا فى هذا الأسلوب.
وبإحصائيات الملفات مندوب ومندوب قائمة المنفذ الجديد,هو نفسه مندوب مندوب قائمة المنفذ القديمة والذي غالبا ما ينسى العديد من الأشخاص أننا بحاجة لأخبار المنفذ الجديد ليبدأ بقراءة البيانات وتعيين مهلة محددة للأنتهاء .
(void)socket:(GCDAsyncSocket *)socket didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
NSLog(@"Accepted New Socket from %@:%hu", [newSocket connectedHost], [newSocket connectedPort]);
// Socket
[self setSocket:newSocket];
// Read Data from Socket
[newSocket readDataToLength:sizeof(uint64_t) withTimeout:-1.0 tag:0];
}
مكتبة CocoaAsyncSocket تقوم بقراءة وكتابة تدفق بيانات التحميل لنا . ولكن يجب علينا اخبار المنفذ لرصد تيار قراءة البيانات الواردة.
أسلوب المندوب الثاني أو الطريقة الثانية التى يمكن أن تنفذ فى هذا الوقت هو الأسلوب الذي يتم استدعاؤه عند قطع الأتصال كل ما نفعلة فى هذا الأسلوب هو تنظيف الأشياء .
(void)socketDidDisconnect:(GCDAsyncSocket *)socket withError:(NSError *)error { NSLog(@"%s", __PRETTY_FUNCTION__); if (self.socket == socket) { [self.socket setDelegate:nil]; [self setSocket:nil]; }}لأنه يمكن أن يكون الربط الشبكى فوضوي قليلا من وقت لأخر, كنت قد لاحظت أنه قد تم أدراج عدد قليل من بيانات التسجيل هنا وهناك , التسجيل هو أفضل صديق عند انشاء التطبيقات التى تنطوي على شبكة الأتصال , لا تتردد فى رمي عدد قليل من سجل البيانات اذا لم تكن متاكدا من ما يحدث تحت هذا الغطاء.
بناء وتشغيل التطبيق الخاص بنا فى iOS Simulator (أو علي جهاز مادي) انقر فوق الزر العلوي لأستضافة اللعبة , اذا سار كل شىء على ما يرام , ستري رسالة فى وحدة التحكم ل Xcode التي تشير الى انه تم نشر خدمة شبكة الأتصال بنجاح , بسهولة كيف كان ذلك ؟ فى الخطوة التالية سوف ننشىء القطعة الثانية من اللغز اكتشاف وأتصال بخدمة شبكة اتصال.
2013-04-10 11:44:03.286 Four in a Row[3771:c07] Bonjour Service Published: domain(local.) type(_fourinarow._tcp.) name(Puma) port(51803)
إذا كنت تقوم بتشغيل التطبيق فى دائرة الرقابة الداخلية iOS Simulator ولديك جدار حماية مفعل على ماك الخاص بك Your Mac يجب أن تشاهد تحذير من نظام التشغيل يطلب الأذن الخاص بك للسماح للأتصال للتطبيق قيد التشغيل على iOS (الشكل 7 )
للتأكد من أن كل شىء يعمل بشكل جيد ومهم جدا لقبول الأتصالات الواردة للتطبيق الخاص بنا
نقول لجدار الحماية السماح للأتصالات الواردة
6-اكتشاف الخدمات
القطعة الثانية من اللغز , نحن بحاجه الى أنشاء فئة عرض وحدة تحكم أخري’ فئة فرعية ULTableBViewController أن تكون دقيقة ( الشكل 8 ). اسم الفئة الجديدة MTJoinGameViewController ليس هناك حاجة ل أنشاء ملف XIB للفئة الجديده
الخطوات التالية مماثلة لما قمنا به فى وقت سابق لتنفيذ الدخول الى اللعبة Join Game غالبا مطابق الي تنفيذ مستضيف اللعبة Host Game
لا تنسي اضافة عبارة استيراد لفئة MTJoinGameViewController
#import "MTJoinGameViewController.h"
(IBAction)joinGame:(id)sender { // Initialize Join Game View Controller MTJoinGameViewController *vc = [[MTJoinGameViewController alloc] initWithStyle:UITableViewStylePlain]; // Initialize Navigation Controller UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc]; // Present Navigation Controller [self presentViewController:nc animated:YES completion:nil];}
كما فعلنا فى فئة MTHostGameViewController نقوم بأستدعاء عرض الأعدادات فى قائمة عرض التحكم ViewDidLoad
(void)viewDidLoad { [super viewDidLoad]; // Setup View [self setupView];}
(void)setupView { // Create Cancel Button self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel:)];}
(void)cancel:(id)sender { // Stop Browsing Services [self stopBrowsing]; // Dismiss View Controller [self dismissViewControllerAnimated:YES completion:nil];}
والفرق الوحيد أننا تتوقف عن تصفح الخدمات فى زر إلغاء الأمر , للعمل قبل رفض عرض وحدة عرض التحكم . سوف ننفذ أسلوب التوقف عن التصفح StopBrowsing فى تلك اللحظة
خدمات التصفح
لتصفح الخدمات على الشبكة المحلية , نحن نستخدم فئة NSNetServiceBrowser قبل وضع فئة NSNetServiceBrowser للأستخدام نحتاج الي إنشاء بعض الخصائص الخاصة , اضافة ملحق الي فئةMTJoinGameViewController وتعريف الخصائص الثلاثة كما هو موضح ادناه .
نوع المنفذ الخاص ب GCDAsyncSocket سوف يتم إنشاؤة عندما تقوم الشبكة بحل الأتصال بنجاح سيتم تخزين خاصية الخدمات (NSMutableArray) جميع الخدمات الذي سيتم اكتشافها بواسطة سيرفر المستعرض على الشبكة, يجد المتصفح خدمة جديده سوف تقوم بتنبيهنا ويمكننا اضافتها الأصناف القابلة للتغيير كما سيعمل هذا الصنف كمصدر للبيانات من وحدة تحكم العرض وطريقة عرض الجدول .
الخاصية الثالثة ServiceBrowser من نوع NSNetServiceBrowser سيتم البحث عن الشبكات للحصول على خدمة الشبكة ذات الأهمية بالنسبة لنا .
نلاحظ أيضا ان MTJoinGameViewController متوافق مع البروتوكولات الثلاثة, يصبح هذا واضحا عندما ننفذ الأساليب لكل من هذه البروتوكولات
@interface MTJoinGameViewController () <NSNetServiceDelegate, NSNetServiceBrowserDelegate, GCDAsyncSocketDelegate> @property (strong, nonatomic) GCDAsyncSocket *socket;@property (strong, nonatomic) NSMutableArray *services;@property (strong, nonatomic) NSNetServiceBrowser *serviceBrowser; @end
تعديل طريقة فئة عرض وحدة تحكم viewDidLoad كما هو موضح أدناه , نحن نقوم بأستدعاء أسلوب مساعد أخر الذي نحن قمنا بتهيئته على خدمة المتصفح حتى يستطيع المستعرض بدء تصفح خدمة الشبكة التى نحن مهتمون بها , دعونا نلقي نظرة على الأسلوب startBrowsing
(void)viewDidLoad { [super viewDidLoad]; // Setup View [self setupView]; // Start Browsing [self startBrowsing];}
فى أسلوب startBrowsing نحن نبدأ فى إعداد مصدر البيانات عن طريق عرض الجدول صفيف القابل للتغيير والذي يخزن الخدمات التى تم اكتشافها على شبكة الأتصال , ايضا نقوم بتهيئة مثيل من فئة NSNetServiceBrowser وتعيين مندوبها , وأقول ذلك لبدء البحث عن الخدمات .
(void)startBrowsing { if (self.services) { [self.services removeAllObjects]; } else { self.services = [[NSMutableArray alloc] init]; } // Initialize Service Browser self.serviceBrowser = [[NSNetServiceBrowser alloc] init]; // Configure Service Browser [self.serviceBrowser setDelegate:self]; [self.serviceBrowser searchForServicesOfType:@"_fourinarow._tcp." inDomain:@"local."];}
هى مفتاح نوع خدمة الشبكات التى قمت بتخطيها كوسيط ل searchForServicesOfType:inDomain: مطابق للنوع الذى مر علينا فى الفئة MTHostGameViewController وهي فكرة جيدة لجعلها ثابته لمنع اي أخطاء مطبعية .
قبل أستدعاء searchForServicesOfType:inDomain:، يبدأ المستعرض خدمة البحث فى الشبكة للحصول على خدمات من النوع المحدد, فى كل مرة يجد المتصفح خدمة مهتم بها يقوم بأعلام مندوبها وذلك بأرسال رسالة netServiceBrowser:didFindService:moreComing:. خدمة شبكة الأتصال NSNetService التى تم اكتشافها تم تمريره كعلامة ثانية ونضيف هذا الكائن الى مجموعة الخدمات الخاصة بنا .
(void)netServiceBrowser:(NSNetServiceBrowser *)serviceBrowser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing { // Update Services [self.services addObject:service]; if(!moreComing) { // Sort Services [self.services sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]]]; // Update Table View [self.tableView reloadData]; }}
من الممكن أيضا أن خدمة تم اكتشافها سابقا تتوقف لأى سبب من الأسباب ولم يعد متوفرا . عندما يحدث هذا, يتم إستدعاءnetServiceBrowser:didRemoveService:moreComing:
أسلوب التفويض انها تعمل بنفس الطريقة السابقة وبنفس الفئة بدلا من اضافة خدمة شبكة الأتصال, التي يتم تمريرها كويسط ثاني ونقوم بأزالتة من مجموعة الخدمات وتحديث طريقة عرض الجدول تبعا لذلك .
(void)netServiceBrowser:(NSNetServiceBrowser *)serviceBrowser didRemoveService:(NSNetService *)service moreComing:(BOOL)moreComing { // Update Services [self.services removeObject:service]; if(!moreComing) { // Update Table View [self.tableView reloadData]; }}
هناك ايضا طريقتين اخرتين مهمين جدا بالنسبة لنا , عندما تتوقف خدمة المتصفح عن البحث او لا تستطيع بالبدء فى البحث في netServiceBrowserDidStopSearch: و netServiceBrowser:didNotSearch: تقوم بأستدعاء أساليب التفويض , على التوالي كل ما نفعله بهذه الأساليب هو تنظيف ما بدأناه على
مساعد stopBrowsing كما هو موضح أدناه ...
ليس من الصعب تنفيذ stopBrowsing كل ما نفعلة هو توجية خدمة المستعرض الى توقف الأستعراض أو التصفح وتنظيف كل شىء .
مساعد stopBrowsing كما هو موضح أدناه ...
(void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)serviceBrowser { [self stopBrowsing]; (void)netServiceBrowser:(NSNetServiceBrowser *)aBrowser didNotSearch:(NSDictionary *)userInfo { [self stopBrowsing];}
ليس من الصعب تنفيذ stopBrowsing كل ما نفعلة هو توجية خدمة المستعرض الى توقف الأستعراض أو التصفح وتنظيف كل شىء .
(void)stopBrowsing { if (self.serviceBrowser) { [self.serviceBrowser stop]; [self.serviceBrowser setDelegate:nil]; [self setServiceBrowser:nil]; }}
قبل أن نواصل رحلتنا , دعونا نرى كل هذا فى العمل أولا , ومع ذلك تنفيذ جدول عرض البيانات المصدر للبروتوكول كما هو موضح أدناه للتأكد من أن يتم نشر طريقة عرض الجدولة مع الخدمات التي يراها خدمة المستعرض على الشبكة .
كما يمكنك أن ترى فى الأسلوب tableView:cellForRowAtIndexPath: , نحن نقوم بعرض خاصية أسم خدمة الشبكة ,وهو الأسم الذي مر علينا فى MTHostGameViewController عندما نقوم بتهيئة خدمة الشبكة لن نقوم بتمرير الأسم فهو يستخدم أسم الجهاز تلقائيا.
(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return self.services ? 1 : 0;}
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.services count];
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ServiceCell]; if (!cell) { // Initialize Table View Cell cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ServiceCell]; } // Fetch Service NSNetService *service = [self.services objectAtIndex:[indexPath row]]; // Configure Cell [cell.textLabel setText:[service name]]; return cell;}}
لا تنسي أن تعلن بشكل ثابت إعادة استخدام الخلية التى نستخدمها في tableView:cellForRowAtIndexPath: لعرض بيانات الجدول المصدر للأسلوب .
static NSString *ServiceCell = @"ServiceCell";لأختبار ما بنيناه حتى الأن ’ تحتاج الى تشغيل مثيلين من التطبيق فى مثيل واحد لك اضغط زر مستضيف اللعبة وعلى المثيل الأخر يمكنك الأنتقال الي الدخول للعبه Join Game , يجب عليك مشاهدة القائمة بكافة المثيلات التي تقوم بنشر هذه الخدمة التى نحن مهتمون بها .
( الشكل 9) صباح الخير Bonjour يجعلك تكتشف الشبكة بكل سهولة كما تري الأن
7- إجراء الاتصال
هو الخطوة الأخيرة من العملية وهي بعمل أتصال بين جهازين عندما يقوم لاعب بالدخول الي خدمة اللعبة بالضغط على قائمة الخدمات التى تم اكتشافها .
إسمحوا لي عمل ذلك فى الممارسة العملية
عندما ينتقل المستخدم الي عرض الجدول , نحن نسحب خدمة الشبكة المطابقه من مجموعة الخدمات ومحاولة حل هذه الخدمه . يحدث هذا في tableView:didSelectRowAtIndexPath: الجدول يعرض أسلوب التفويض resolveWithTimeout: الأسلوب يقبل وسيط واحد كحد أقصي ل عدد الثواني لحل هذه الخدمة .
هناك أحد الأمرين سيحدث , النجاح أو الفشل اذا كان حل الخدمة غير ناجح netService:didNotResolve: يقوم بأستدعاء اسلوب التفويض , مثل ما نفعلة فى هذا الأسلوب هو تنظيف كل شىء.
اذا تم حل الخدمة بنجاح سوف نحاول انشاء اتصال بأعطاء تفويض إلي connectWithService: وتمرير الخدمة كوسيط , تذكر أن صباح الخير Bonjour ليس مسؤولا عن أنشاء الأتصال صباح الخير Bonjour فقط يزودنا بالمعلومات اللازمه لتأسيس اتصال, فحل الخدمة ليس مثل انشاء الاتصال يمكن الأطلاع على ما يلزم القيام به لتأسيس اتصال فى تنفيذ connectWithService:
علينا أولا انشاء مساعد متغير , _isConnected، ونسخه قابلة للتغيير من العناوين الخاصة بخدمة الشبكة .
ربما تفاجأ بأن الخدمة ربما تحتوى على العديد من العناوين ولكن فى الواقع يمكن أن يكون هذا الحال فى بعض الحالات , كل عنصر فى صفيف العناوين يكون NSData التي تحتوي على بنية سوكادر .
إذا لم تتم تهيئة الخاصية (No Connection Active) نقوم بفتح منفذ بالضبط كما فعلنا في MTHostGameViewercontroller ثم التكرار
عبر مجموعة عناوين , ومحاولة الأتصال بأحد العناوين الموجودة فى صفيف العناوين , إذا كان الامر ناجحا , نقوم بتعيين _isConnected الي Yes هذا يعني أنه تم تأسيس الأتصال بنجاح.
عبر مجموعة عناوين , ومحاولة الأتصال بأحد العناوين الموجودة فى صفيف العناوين , إذا كان الامر ناجحا , نقوم بتعيين _isConnected الي Yes هذا يعني أنه تم تأسيس الأتصال بنجاح.
عندما يتم إعداد أتصال المنفذ بنجاح socket:didConnectToHost: يقوم بأستدعاء أسلوب التفويض للبروتوكول وبصرف النظر عن تسجيل بعض المعلومات الى وحدة التحكم , نقوم بأخبار المنفذ أن يقوم ببدء مراقبة تدفق البيانات الوارده.
سوف اقوم بتوفير مزيد من التفاصيل فى المقال القادم من هذه السلسلة
وهذا ايضا الوقت المناسب لتنفيذ أسلوب أخر ل بروتوكول GCDAsyncSocketDelegate و SocketDidDisconnect:withError:
يتم استدعاءه عندما يتم قطع الأتصال كل ما علينا فعلة هو تسجيل بعض المعلومات عن المنفذ الى وحدة التحكم وتنظيف كل شىء .
تشغيل مثيلين من التطبيق للتأكد من أن كل شىء يعمل بشكل جيد ومن المهم أن يكون الجهاز المستضيف للعبة socket:didAcceptNewSocket: يقوم بإستدعاء أسلوب التفويض أذا كان كلا التفويض تستدعي باقى الأساليب , ثم علينا العلم أننا قد نجحنا فى انشاء أتصال بين الجهازين
الخاتمــــــة.
لا تزال هناك بعض الاشياء التى نحن بحاجهإ لى الاهتمام بها ولكن هذه الأشياء سوف نفعلها فى الدفعه القادمة من هذه السلسلة وأمل انك تستطيع تنفيذ Bonjour و مكتبة CocoaAsyncSocket ليست معقده .
أريد أن أوكد على أن المكتبة CocoaAsyncSocket تجعل العمل مع المنافذ وقراءة البيانات بشكل سهل جدا عن طريق توفير Objective-C API والتأكد من أنه لا يتعين علينا استخدام أطار CFNetwork framework عندنا
الأستنتاج .
فى هذا البرنامج التعليمي ارسينا أساسيات اللعبة من حيث الربط الشبكي وبطبيعة الحال لا يزال أمامنا الكثير على الأرض لتغطيتها , كما أننا لم نبدأ حتى فى تنفيذ اللعبة نفسها وهذا شىء نفعله فى الدفعه القادمة من هذه السلسلة كن على انتظار.
ليست هناك تعليقات: