مساحة اعلانية

آخر المواضيع

بروتوكول TCP


مقدمة

كما رأينا في البرنامج التعليمي السابق، مكتبة CocoaAsyncSocket يجعل العمل مع ال sockets سهل للغاية. ومع ذلك، هناك ما هو أكثر من إرسال نص بسيط من جهاز إلى آخر، كما فعلنا في البرنامج التعليمي السابق. في المقال  الأول من هذه السلسلة، ذكرت أن بروتوكول TCP يمكنه إدارة تدفق البيانات في اتجاهين. المشكلة، ومع ذلك، هو أنه دفق مستمر من البيانات. 


بروتوكول TCP يعتني إرسال البيانات من أحد طرفي الاتصال إلى الآخر، ولكن الأمر متروك إلى المتلقي لفهم ما يجري إرساله من خلال هذا الاتصال.

هناك عدة حلول لهذه المشكلة. بروتوكول HTTP، المبني على أعلى من بروتوكول TCP، يرسل header HTTP مع كل طلب ورد على الطلب. header HTTP يحتوي على معلومات حول الطلب أو الاستجابة، التي يمكن للمتلقي استخدامها لفهم البيانات الواردة. عنصر رئيسي من عناصر header HTTP هو طول ال body. إذا كان المتلقي يعرف طول الجسم من الطلب أو الرد، فيمكنه استخراج أصل البيانات المرسلة وتجريدها من المعلومات الاخرى المرسلة .
هذا جيد، ولكن كيف يمكن للمتلقي معرفة طول الheader ؟ ينتهي كل حقل header HTTP مع CRLF (Carriage Return, Line Feed)  HTTP header ينتهي أيضا ب CRLF. وهذا يعني أن رأس كل طلب HTTP واستجابة ينتهي مع اثنين من CRLFs. عندما يقرأ المتلقي البيانات الواردة ، فليس عليه سوى البحث عن هذا النمط (اثنان CRLFs على التوالي). بذلك، يمكن للمتلقي تحديد واستخراج رأس طلب HTTP أو الاستجابة. مع تحديد ال HTTp header، يمكن تحديد واستخلاص ال HTTP body  بسهولة.
الاستراتيجية التي سنتبعها تختلف عن كيفية عمل بروتوكولات HTTP. كل حزمة من البيانات التي نرسلها من خلال الاتصال تكون مدمجة مع ال header  له طول ثابت. header أقل تعقيداً من header HTTP. ال header الذي سوف نستخدمه يحتوي على جزئية واحدة من المعلومات، وطول الجسم أو الحزمة التي تأتي بعد ال header. وبعبارة أخرى، فإن الرأس هو ليس أكثر من الرقم الذي يبلغ المتلقي عن طول الجسم. بمعرفة هذا، يتمكن المتلقي من استخراج الجسم أو الحزمة بنجاح من تيار وارد من البيانات. على الرغم من أن هذا هو نهج بسيط، إلا أنه يعمل بشكل جيد كما سترون في هذا البرنامج التعليمي.

1. الحزم
من المهم أن نفهم أن هذه الأساليب المذكورة أعلاه صممت خصيصا لبروتوكول TCP وأنها تعمل فقط بسبب كيفية عمل بروتوكول TCP. بروتوكول TCP يضمن أن كل حزمة تصل إلى وجهتها في الترتيب الذي تم إرساله. وبالتالي، فإن الاستراتيجيات التي أشرت إليها تعمل بشكل جيد جدا.

الخطوة 1: إنشاء فئة حزم
على الرغم من أنه يمكننا إرسال أي نوع من البيانات عبر اتصال TCP، فمن المستحسن تخصيص  بنية معينة لاحتواء البيانات التي نود إرسالها. يمكننا تحقيق ذلك من خلال خلق طبقة حزمة مخصصة. وميزة هذا ستتضح عندما نبدأ باستخدام فئة الحزمة. الفكرة بسيطة. ال class هي class Objective-C  الذي يحمل البيانات؛ الجسم، اذا صح التعبير. فإنه يشمل أيضا بعض معلومات إضافية حول الحزمة، يُدعى ال header . والفرق الرئيسي مع بروتوكول HTTP هو أن الرأس والجسم ليسا منفصلين تماماً. سوف يحتاج class الحزمة أيضا أن يتوافق مع بروتوكول NSCoding، وهو ما يعني أن مثيلات الفئة يمكن أن يتم تشفيرها وفك الشفرة. هذا هو مفتاح الحل إذا أردنا أن نرسل مثيلات class الحزمة من خلال اتصال TCP.
إنشاء class جديد من ال Objective-C  وجعلها فئة فرعية من NSObject، وتسميته MTPacket (الشكل 1). لهذه اللعبة التي نقوم ببنائها، يمكن أن يكون class الحزمة بسيط إلى حد ما. ال class  له ثلاثة خصائص، type، action، وdata . يتم استخدام الخاصية type لتحديد الغرض من الحزمة في حين أن خاصية action تتضمن نية الحزمة. يتم استخدام خاصية data لتخزين المحتويات الفعلية أو تحميل الحزمة. وسيصبح الأمر أكثر وضوحا بمجرد البدء باستخدام class الحزمة في لعبتنا.


إنشاء class الحزم
سننتوقف لحظة لتفقد واجهة ال class MTPacket المبين أدناه. كما ذكرت، فمن الضروري أن class instances يمكن تشفيرها وفك الشفرة التي تتفق مع بروتوكول NSCoding. لتتوافق مع بروتوكول NSCoding، نحن بحاجة فقط لبناء two methods، encodeWithCoder: وinitWithCoder :.
من المهم ذكره أن الخصائص type و action هي من نوع MTPacketType وMTPacketAction، على التوالي. يمكنك العثور على تعريفات type في الجزء العلوي من MTPacket.h. إذا لم تكن الرموز المميزة ل typedef و enum  مألوفة لديك، يمكنك قراءة المزيد حول هذا الموضوع Stack Overflow. وسيكون العمل مع فئة MTPacket أسهل.
خاصية data class هي من نوع id. وهذا يعني أنه يمكن أن يكون أي كائن Objective-C. والشرط الوحيد هو أنه يتوافق مع بروتوكول NSCoding. معظم أعضاء foundation framework، مثل NSArray، NSDictionary، وNSNumber، تتفق مع بروتوكول NSCoding.
لتجعل من السهل تعريف مثيلات class MTPacket، فإننا سنقوم بتعريف مهيئ المعين أن يأخذ حزمة من البيانات، والنوع، والعمل كوسائط.

#import <Foundation/Foundation.h> extern NSString * const MTPacketKeyData;extern NSString * const MTPacketKeyType;extern NSString * const MTPacketKeyAction; typedef enum {    MTPacketTypeUnknown = -1} MTPacketType; typedef enum {    MTPacketActionUnknown = -1} MTPacketAction; @interface MTPacket : NSObject @property (strong, nonatomic) id data;@property (assign, nonatomic) MTPacketType type;@property (assign, nonatomic) MTPacketAction action; #pragma mark -#pragma mark Initialization- (id)initWithData:(id)data type:(MTPacketType)type action:(MTPacketAction)action; @end

لا ينبغي أن يكون تنفيذ الطبقة MTPacket من الصعب جدا إذا كنت معتادا على بروتوكول NSCoding. كما رأينا في وقت سابق، ويحدد بروتوكول NSCoding ، two methods وكلاهما مطلوب. يتم استدعاءهم تلقائيا عندما يتم ترميز مثيل من فئة (encodeWithCoder :) أو فك (initWithCoder :). وبعبارة أخرى، لن تحتاج أبداً لاستدعاء هذه الطرق بنفسك. سوف نرى كيف يعمل هذا في وقت لاحق في هذه المقالة.

كما ترون أدناه، بناء ال initWithData:type:action:   سيكون سهلاً في ملف التنفيذ، لقد أصبح واضحاً أيضا لماذا أعلنا ثلاثة ثوابت في واجهة ال class. ومن الجيد استخدام الثوابت لمفاتيح تستخدمها في بروتوكول NSCoding. السبب الرئيسي هو ليس عدم الأداء، ولكن أخطاء الكتابة. المفاتيح التي يمكنك تمرير عند ترميز خصائص الفئة التي يجب أن تكون مطابقة للمفاتيح التي يتم استخدامها عند فك مثيلات الفئة.

 #import "MTPacket.h"

NSString * const MTPacketKeyData = @"data";
NSString * const MTPacketKeyType = @"type";
NSString * const MTPacketKeyAction = @"action";

@implementation MTPacket

#pragma mark -
#pragma mark Initialization
- (id)initWithData:(id)data type:(MTPacketType)type action:(MTPacketAction)action {
    self = [super init];

    if (self) {
        self.data = data;
        self.type = type;
        self.action = action;
    }

    return self;
}

#pragma mark -
#pragma mark NSCoding Protocol
- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.data forKey:MTPacketKeyData];
    [coder encodeInteger:self.type forKey:MTPacketKeyType];
    [coder encodeInteger:self.action forKey:MTPacketKeyAction];
}

- (id)initWithCoder:(NSCoder *)decoder {
    self = [super init];

    if (self) {
        [self setData:[decoder decodeObjectForKey:MTPacketKeyData]];
        [self setType:[decoder decodeIntegerForKey:MTPacketKeyType]];
        [self setAction:[decoder decodeIntegerForKey:MTPacketKeyAction]];
    }

    return self;
}

@end

الخطوة 2: إرسال البيانات 
قبل أن نبدأ فى القطعة التالية فى تلك الأحجية, أريد أن أتأكد أن  MTPacket class  يعمل كما هو متوقع. و ليس هناك طريقة أفضل للأختبار من إرسال  حزمة حالما  تتكون صلة, حين يحدث ذلك  يصبح بإمكاننا إعادة هيكلة منطق الشبكة بوضع متحكم مختص.
حين تتكون الصلة, يصل إشعار للتطبيق الزى يستضيف اللعبة بإستدعاء   socket:didAcceptNewSocket:  , الطريقة المفوضة لبرتوكول   GCDAsyncSocketDelegate  .
و لقد قمنا بتنفيذ تلك الطريقة فى مقال سابق. ألقوا نظرة على طريقة التنفيذ فى الأسفل حتى تنعشوا ذاكرتكم, يجب أن يكون أخر سطر فى أمر التنفيذ واضحا الأن, سنخبر الSocket  أن يبدأ قراءة البيانات و نحن نمرر رقم صحيح , ولأنه الأمر الأخير فلا نحدد وقت للأنتهاء و لأننا لا نعرف وقت وصول الحزمة الأولى.
على كل الأحوال ما يهمنا فعلا  هوأول نقاش حول   readDataToLength:withTimeout:tag:  , لماذا نجعل من sizeof(uint64_t) ك الargument الأول؟

- (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];}


إن خاصية sizeof  تعطى طول ال argument الخاص بالدالة  بالنسبة لعدد البايتس, اما  uint64_t  فهى معرفة فى stdint.h كما ذكرت سابقا.
إن كل Header يسبق كل حزمة نرسلها له طول ثابت, و هو ما يختلف تماما عن Header  طلب أو رد HTTP, فى مثالنا للHeader هدف واحد و هو إخبار المستلم بطول كل حزمة تصل له, بكلمات أخرى هو إخبار الsocket أن يقرأ كمية بيانات مستلمة تساوى طول الHeader.

Pic (1)-2


أحضر header  الملف التابع لMTPacket class  و أبدأ تفعيل socket:didAcceptNewSocket: كما هو موضح بالأسفل:

- (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];     // Create Packet    NSString *message = @"This is a proof of concept.";    MTPacket *packet = [[MTPacket alloc] initWithData:message type:0 action:0];     // Send Packet    [self sendPacket:packet];

كما كتبت سابقا, يمكننا فقط إرسال بيانات زوجية من خلال وصلة TCP . و هذا يعنى إنه علينا ترجمة instance MTPacket التى أنشئناها لأن class MTPacket بدعم و يؤكد بروتوكول NSCoding<

الكــاتــب

    • مشاركة

ليست هناك تعليقات:

جميع الحقوق محفوظة لــ الشبح للمعلوميات 2019 ©