Skip to content

9. برنامه‌نویسی TCP-IP

9.1. عمومی

9.1.1. پروتکل‌های اینترنت

در اینجا مقدمه‌ای بر پروتکل‌های ارتباطی اینترنت ارائه می‌شود که به نام مجموعه پروتکل TCP/IP (پروتکل کنترل انتقال / پروتکل اینترنت) نیز شناخته می‌شود، که از نام دو پروتکل اصلی گرفته شده است. توصیه می‌شود خواننده پیش از پرداختن به توسعه برنامه‌های توزیع‌شده، درک کلی از نحوه کار شبکه‌ها و به‌ویژه پروتکل‌های TCP/IP داشته باشد.

متن زیر ترجمه‌ای جزئی از گزیده‌ای است که در سند «محیط کاری LAN برای DOS – راهنمای مدیر» اثر NOVELL، سندی از اوایل دهه ۱۹۹۰، یافت می‌شود.

-----------------------------------

مفهوم کلی ایجاد شبکه‌ای از کامپیوترهای ناهمگون از تحقیقاتی نشأت می‌گیرد که توسط DARPA (آژانس پروژه‌های تحقیقاتی پیشرفته دفاعی) در ایالات متحده انجام شده است. سازمان DARPA مجموعه پروتکل‌های شناخته‌شده با نام TCP/IP را توسعه داد که امکان ارتباط میان ماشین‌های ناهمگون را فراهم می‌کند. این پروتکل‌ها روی شبکه‌ای به نام ARPAnet آزمایش شدند که بعدها به شبکه INTERNET تبدیل شد. پروتکل‌های TCP/IP فرمت‌ها و قواعدی را برای انتقال و دریافت تعریف می‌کنند که از معماری شبکه و سخت‌افزار مورد استفاده مستقل هستند.

شبکه‌ای که توسط DARPA طراحی شده و توسط پروتکل‌های TCP/IP مدیریت می‌شود، یک شبکه سوئیچینگ بسته است. چنین شبکه‌ای اطلاعات را در قالب بسته‌های کوچک به نام پکت منتقل می‌کند. بنابراین، اگر یک رایانه فایلی بزرگ را ارسال کند، آن را به بسته‌های کوچک تقسیم می‌کند که از طریق شبکه ارسال شده و در مقصد دوباره مونتاژ می‌شوند. TCP/IP قالب این پکت‌ها را تعریف می‌کند، یعنی:

  • منبع بسته
  • مقصد
  • طول
  • نوع

9.1.2. مدل OSI

پروتکل‌های TCP/IP به طور کلی از مدل شبکه باز معروف به OSI (مدل مرجع اتصال سیستم‌های باز) که توسط ISO (سازمان بین‌المللی استانداردها) تعریف شده است، پیروی می‌کنند. این مدل یک شبکه ایده‌آل را توصیف می‌کند که در آن ارتباط بین ماشین‌ها را می‌توان با یک مدل هفت‌لایه نشان داد:

هر لایه خدماتی را از لایه زیرین دریافت می‌کند و خدمات خود را به لایه بالادست ارائه می‌دهد. فرض کنید دو برنامه کاربردی مستقر بر روی ماشین‌های مختلف، A و B، بخواهند با یکدیگر ارتباط برقرار کنند: آنها این کار را در لایه Application انجام می‌دهند. آنها نیازی به دانستن تمام جزئیات نحوه عملکرد شبکه ندارند: هر برنامه اطلاعات مورد نظر خود را برای ارسال به لایه زیرین - لایه Présentation - منتقل می‌کند. بنابراین، برنامه تنها نیاز به دانستن قوانین رابط با لایه Présentation دارد.

هنگامی که اطلاعات به لایه Présentation رسید، طبق قواعد دیگر به لایه Session منتقل می‌شود و این روند ادامه می‌یابد تا اطلاعات به رسانه فیزیکی برسد و به صورت فیزیکی به ماشین مقصد ارسال شود. در آنجا، این داده‌ها تحت فرآیند معکوس آنچه در ماشین فرستنده انجام شده بود، قرار می‌گیرند.

در هر لایه، فرآیند فرستنده مسئول ارسال اطلاعات، آن را به فرآیند گیرنده در ماشین دیگر که به همان لایه تعلق دارد، ارسال می‌کند. این کار بر اساس قواعدی مشخص که به پروتکل لایه معروف است، انجام می‌شود. بنابراین، نمودار نهایی ارتباطات به شرح زیر است:

نقش لایه‌های مختلف به شرح زیر است:

Physique
انتقال بیت‌ها را از طریق یک رسانه فیزیکی تضمین می‌کند. این لایه شامل تجهیزات پایانه پردازش داده (E.T.T.D)، مانند ترمینال‌ها یا کامپیوترها، و همچنین تجهیزات پایانه‌ی مدار داده (E.T.C.D)، مانند مودولاتورها/دمودولاتورها، چندراهی‌ها و متمرکزکننده‌ها است. نکات کلیدی در این سطح عبارتند از:
. انتخاب روش رمزگذاری اطلاعات (آنالوگ یا دیجیتال)
. انتخاب حالت انتقال (هم‌زمان یا ناهمزمان).
Liaison de données
ویژگی‌های فیزیکی لایه فیزیکی را پنهان می‌کند. خطاهای انتقال را تشخیص داده و اصلاح می‌کند.
Réseau
مسیر داده‌هایی را که از طریق شبکه ارسال می‌شوند مدیریت می‌کند. این به عنوان routage شناخته می‌شود: تعیین مسیری که داده‌ها باید برای رسیدن به مقصد طی کنند.
Transport
ارتباط بین دو برنامه را ممکن می‌سازد، در حالی که لایه‌های قبلی تنها ارتباط بین ماشین‌ها را مجاز می‌دانستند. یکی از خدمات ارائه شده توسط این لایه، چندراهی‌سازی (multiplexing) است: لایه حمل‌ونقل می‌تواند از یک اتصال شبکه واحد (از ماشینی به ماشین دیگر) برای انتقال داده‌های متعلق به چندین برنامه استفاده کند.
Session
این لایه خدماتی را فراهم می‌کند که به یک برنامه امکان می‌دهد یک جلسه کاری را روی یک ماشین راه دور باز و حفظ کند.
Présentation
هدف آن استانداردسازی نمایش داده‌ها در سراسر ماشین‌های مختلف است. بنابراین، داده‌های منشاء‌گرفته از ماشین A توسط لایه Présentation ماشین A بر اساس یک قالب استاندارد «قالب‌بندی» می‌شوند، پیش از آنکه از طریق شبکه ارسال شوند. پس از رسیدن به لایه Présentation در ماشین گیرنده B، که به لطف فرمت استاندارد آن‌ها را شناسایی می‌کند، داده‌ها به شکلی متفاوت قالب‌بندی می‌شوند تا برنامه در ماشین B بتواند آن‌ها را تشخیص دهد.
Application
در این سطح، برنامه‌هایی را می‌یابیم که عموماً به کاربر نزدیک هستند، مانند ایمیل یا انتقال فایل.

9.1.3. مدل TCP/IP

مدل OSI یک مدل ایده‌آل است که تاکنون محقق نشده است. مجموعه پروتکل‌های TCP/IP به شکل زیر به آن نزدیک می‌شود:

لایه فیزیکی

در شبکه‌های محلی، معمولاً از فناوری اترنت یا توکن رینگ استفاده می‌شود. ما در اینجا فقط فناوری اترنت را بررسی خواهیم کرد.

اترنت

این نامی است که به یک فناوری شبکه محلی سوئیچینگ بسته داده شده است که در اوایل دهه ۱۹۷۰ در شرکت PARC زیرکس اختراع شد و در سال ۱۹۷۸ توسط زیرکس، اینتل و دیجیتال اکویپمنت استانداردسازی گردید. شبکه از نظر فیزیکی شامل یک کابل کواکسیال با قطر تقریباً ۱.۲۷ سانتی‌متر و طول تا ۵۰۰ متر است. این کابل را می‌توان با استفاده از répéteurs گسترش داد، به طوری که حداکثر دو تکرارکننده بین هر دو دستگاه قرار گیرند. کابل غیرفعال است: تمام اجزای فعال روی دستگاه‌های متصل به کابل قرار دارند. هر دستگاه از طریق یک کارت دسترسی شبکه به کابل متصل می‌شود که شامل:

  • یک فرستنده (transceiver) که حضور سیگنال‌ها را روی کابل تشخیص می‌دهد و سیگنال‌های آنالوگ را به سیگنال‌های دیجیتال و بالعکس تبدیل می‌کند.
  • یک کوپلر که سیگنال‌های دیجیتال را از فرستنده دریافت کرده و آن‌ها را برای پردازش به کامپیوتر منتقل می‌کند، یا بالعکس.

ویژگی‌های اصلی فناوری اترنت به شرح زیر است:

  • ظرفیت ۱۰ مگابیت بر ثانیه.
  • تپولوژی باس: تمام دستگاه‌ها به یک کابل مشترک متصل هستند
  • شبکه پخش – یک دستگاه فرستنده اطلاعات را با آدرس دستگاه مقصد از طریق کابل ارسال می‌کند. سپس تمام دستگاه‌های متصل این اطلاعات را دریافت می‌کنند و تنها گیرنده مورد نظر آن را حفظ می‌کند.
  • روش دسترسی به این صورت است: فرستنده‌ای که مایل به ارسال داده است، به کابل گوش می‌دهد – سپس تشخیص می‌دهد که آیا سیگنال حامل وجود دارد یا خیر؛ وجود سیگنال حامل نشان‌دهنده این است که یک انتقال در حال انجام است. این تکنیک، دسترسی چندگانه با تشخیص حامل (CSMA) نامیده می‌شود. اگر هیچ سیگنال حامل وجود نداشته باشد، فرستنده می‌تواند تصمیم بگیرد که نوبت به او برای ارسال رسیده است. چندین فرستنده ممکن است همزمان این تصمیم را بگیرند. سیگنال‌های ارسال‌شده با یکدیگر تداخل پیدا می‌کنند: به این وضعیت برخورد (collision) گفته می‌شود. فرستنده این وضعیت را تشخیص می‌دهد: در حالی که از طریق کابل در حال ارسال است، به آنچه واقعاً از آن عبور می‌کند نیز گوش می‌دهد. اگر تشخیص دهد که اطلاعاتی که در امتداد کابل در حال حرکت است با آنچه خود ارسال کرده یکسان نیست، نتیجه می‌گیرد که یک برخورد رخ داده است و ارسال را متوقف می‌کند. فرستنده‌های دیگر که در حال ارسال بودند نیز همین کار را انجام می‌دهند. هر کدام پس از یک تأخیر تصادفی که بسته به فرستندهٔ مربوطه متفاوت است، ارسال را از سر می‌گیرند. این تکنیک با نام CD (تشخیص برخورد) شناخته می‌شود. بنابراین، روش دسترسی با نام CSMA/CD شناخته می‌شود.
  • آدرس‌دهی ۴۸ بیتی. هر دستگاه دارای آدرسی است که در اینجا به آن آدرس فیزیکی گفته می‌شود و روی کارتی که آن را به کابل متصل می‌کند، نوشته شده است. این آدرس با نام آدرس Ethernet دستگاه شناخته می‌شود.

لایه شبکه

در این لایه، پروتکل‌های IP، ICMP، ARP و RARP را می‌یابیم.

IP (Internet Protocol)
ارسال بسته‌ها بین دو گره شبکه
ICMP 
(Internet Control Message Protocol)
ICMP ارتباط بین برنامه پروتکل IP را در یک ماشین و در ماشین دیگر تسهیل می‌کند. بنابراین این یک پروتکل تبادل پیام در خود پروتکل IP است.
ARP
(Address Resolution Protocol)
آدرس اینترنتی ماشین را به آدرس فیزیکی آن نگاشت می‌کند
RARP
(Reverse Address Resolution Protocol)
آدرس فیزیکی دستگاه را به آدرس اینترنتی آن نگاشت می‌کند

لایه‌های حمل‌ونقل/جلسه

این لایه شامل پروتکل‌های زیر است:

TCP (Transmission Control Protocol)
اطمینان از تحویل قابل‌اعتماد اطلاعات بین دو کلاینت
UDP (User Datagram Protocol)
ارائه تحویل غیرقابل‌اطمینان اطلاعات بین دو کلاینت

لایه‌های کاربردی/ارائه‌ای/جلسه

پروتکل‌های مختلف را می‌توانید در اینجا بیابید:

TELNET
شبیه‌ساز ترمینال که ماشین A را قادر می‌سازد به ماشین B به‌عنوان یک ترمینال متصل شود
FTP (File Transfer Protocol)
امکان انتقال فایل را فراهم می‌کند
TFTP (Trivial File
Transfer Protocol)
امکان انتقال فایل را فراهم می‌کند
SMTP (Simple Mail Transfer
protocol)
تبادل پیام‌ها را بین کاربران شبکه امکان‌پذیر می‌سازد
DNS (Domain Name System)
یک نام ماشین را به آدرس اینترنتی آن تبدیل می‌کند
XDR (eXternal Data 
Representation)
توسط Sun MicroSystems ایجاد شده است، این استاندارد، نمایش داده‌ها را به صورت مستقل از ماشین مشخص می‌کند
RPC(Remote Procedures Call)
که همچنین توسط Sun تعریف شده است، یک پروتکل ارتباطی بین برنامه‌های راه دور است که از لایه انتقال مستقل است. این پروتکل مهم است: برنامه‌نویس را از نیاز به دانستن جزئیات لایه انتقال رها می‌کند و برنامه‌ها را قابل حمل می‌سازد. این پروتکل بر اساس پروتکل XDR است.
NFS (Network File System)
که همچنین توسط Sun تعریف شده است؛ این پروتکل به یک ماشین امکان می‌دهد تا سیستم فایل ماشین دیگر را «ببیند». این پروتکل بر اساس پروتکل قبلی RPC است.

9.1.4. نحوه کار پروتکل‌های اینترنت

برنامه‌های کاربردی توسعه‌یافته در محیط TCP/IP معمولاً از چندین پروتکل در این محیط استفاده می‌کنند. یک برنامه کاربردی با بالاترین لایه پروتکل‌ها ارتباط برقرار می‌کند. این لایه اطلاعات را به لایه زیرین خود منتقل می‌کند، و این روند به همین ترتیب ادامه می‌یابد تا زمانی که به رسانه فیزیکی برسد. در آنجا، اطلاعات به صورت فیزیکی به ماشین گیرنده منتقل می‌شود، جایی که دوباره از همان لایه‌ها عبور می‌کند – این بار در جهت مخالف – تا به برنامه‌ای که قرار است اطلاعات ارسال‌شده را دریافت کند، برسد. نمودار زیر مسیری را که اطلاعات طی می‌کند نشان می‌دهد:

بیایید یک مثال بزنیم: برنامه کاربردی FTP، که در لایه Application تعریف شده و امکان انتقال فایل بین دستگاه‌ها را فراهم می‌کند.

  • این برنامه یک دنباله بایت را برای ارسال به لایه transport. تحویل می‌دهد
  • لایه transport این دنباله بایت‌ها را به segments و TCP تقسیم می‌کند و شماره سگمنت را به ابتدای هر سگمنت اضافه می‌کند. این بخش‌ها به لایه شبکه ارسال می‌شوند که توسط پروتکل IP اداره می‌شود.
  • لایه IP یک بسته ایجاد می‌کند که شامل بخش دریافت‌شده TCP است. در ابتدای این بسته، آدرس‌های اینترنتی ماشین‌های مبدأ و مقصد قرار می‌گیرد. همچنین آدرس فیزیکی ماشین مقصد تعیین می‌شود. کل مجموعه به لایه پیوند داده و فیزیکی، یعنی به کارت شبکه که ماشین را به شبکه فیزیکی متصل می‌کند، ارسال می‌شود.
  • در آنجا، بسته IP به نوبه خود در یک فریم فیزیکی قرار داده شده و از طریق کابل برای گیرنده آن ارسال می‌شود.
  • در دستگاه گیرنده، لایه پیوند داده و فیزیکی عملیات معکوس را انجام می‌دهد: این لایه بسته IP را از فریم فیزیکی خارج کرده و آن را به لایه IP می‌سپارد.
  • لایه IP صحت بسته را بررسی می‌کند: این لایه یک چک‌سام (checksum) بر اساس بیت‌های دریافتی (checksum) محاسبه می‌کند که باید در هدر بسته پیدا شود. اگر اینطور نباشد، بسته دور انداخته می‌شود.
  • اگر بسته معتبر تشخیص داده شود، لایه IP، سگمنت TCP موجود در آن را از کپسول خارج کرده و آن را به سمت لایه transport ارسال می‌کند.
  • لایه transport – لایه TCP در مثال ما – شماره سگمنت را بررسی می‌کند تا اطمینان حاصل شود که سگمنت‌ها به ترتیب صحیح قرار دارند.
  • همچنین یک چک‌سام برای سگمنت TCP محاسبه می‌کند. اگر این چک‌سام صحیح باشد، لایه TCP یک تأیید (acknowledgement) به ماشین مبدأ ارسال می‌کند؛ در غیر این صورت، سگمنت TCP رد می‌شود.
  • تنها کاری که برای لایه TCP باقی می‌ماند این است که بخش داده‌ای سگمنت را به برنامه‌ای که در لایه بالاتر قرار است آن را دریافت کند، منتقل نماید.

9.1.5. نشانی‌گذاری در اینترنت

یک noeud در یک شبکه می‌تواند یک رایانه، یک چاپگر هوشمند، یک سرور فایل – در واقع، هر چیزی باشد که قادر به برقراری ارتباط با استفاده از پروتکل‌های TCP/IP باشد. هر گره دارای یک آدرس فیزیکی است که قالب آن به نوع شبکه بستگی دارد. در یک شبکه اترنت، آدرس فیزیکی در ۶ بایت رمزگذاری می‌شود. یک آدرس شبکه X25 یک عدد ۱۴ رقمی است.

آدرس اینترنتی یک گره یک آدرس منطقی است: این آدرس از سخت‌افزار و شبکه مورد استفاده مستقل است. این یک آدرس ۴ بایتی است که هم یک شبکه محلی و هم یک گره در آن شبکه را شناسایی می‌کند. آدرس اینترنتی معمولاً به صورت چهار عدد—مقادیر چهار بایت—که با نقطه از هم جدا شده‌اند، نمایش داده می‌شود. بنابراین، آدرس ماشین «Lagaffe» در دانشکده علوم در آنژ به صورت 193.49.144.1 و آدرس ماشین «Liny» به صورت 193.49.144.9 نوشته می‌شود. از این طریق، می‌توان نتیجه گرفت که آدرس اینترنتی شبکه محلی 193.49.144.0 است. حداکثر 254 گره در این شبکه وجود دارد.

از آنجایی که آدرس‌های اینترنتی یا آدرس‌های IP از شبکه مستقل هستند، یک رایانه در شبکه A می‌تواند بدون نیاز به دانستن نوع شبکه‌ای که در آن قرار دارد، با رایانه‌ای در شبکه B ارتباط برقرار کند: این رایانه تنها نیاز دارد که آدرس IP خود را بداند. پروتکل IP در هر شبکه، تبدیل بین آدرس IP و آدرس فیزیکی را در هر دو جهت انجام می‌دهد.

تمام آدرس‌های IP باید با هم متفاوت باشند. در فرانسه، این INRIA است که مسئول تخصیص آدرس‌های IP است. در واقع، این سازمان یک آدرس برای شبکه محلی شما صادر می‌کند، برای مثال 193.49.144.0 برای شبکه دانشکده علوم در آنژ. مدیر این شبکه سپس می‌تواند آدرس‌های IP با محدوده 193.49.144.1 تا 193.49.144.254 را بنا به صلاحدید خود تخصیص دهد. این آدرس معمولاً در یک فایل مشخص روی هر دستگاه متصل به شبکه ذخیره می‌شود.

9.1.5.1. کلاس‌های آدرس IP

آدرس‌هایی مانند IP یک دنباله از ۴ بایت هستند که اغلب به صورت I1.I2.I3.I4 نوشته می‌شوند و در واقع شامل دو آدرس هستند:

  • آدرس شبکه
  • آدرس یک گره در آن شبکه

بسته به اندازه این دو فیلد، آدرس‌های IP به سه کلاس تقسیم می‌شوند: کلاس‌های A، B و C.

کلاس A

آدرس IP: I1.I2.I3.I4 به شکل R1.N1.N2.N3 است، که در آن

R1
آدرس شبکه است
N1.N2.N3
آدرس یک میزبان در آن شبکه است

به طور دقیق‌تر، فرمت یک آدرس کلاس A مانند IP به شرح زیر است:

آدرس شبکه ۷ بیت و آدرس گره ۲۴ بیت طول دارد. بنابراین می‌توان ۱۲۷ شبکه کلاس A داشت که هر کدام شامل حداکثر ۲²⁴ گره می‌شوند.

کلاس B

در اینجا، آدرس IP:I1.I2.I3.I4 به شکل R1.R2.N1.N2 درمی‌آید، که در آن

R1.R2
آدرس شبکه است
N1.N2
آدرس یک میزبان در آن شبکه است

به طور دقیق‌تر، یک آدرس کلاس B به شکل IP به شرح زیر است:

هم آدرس شبکه و هم آدرس گره ۲ بایت (دقیق‌تر ۱۴ بیت) هستند. بنابراین می‌توان ۲^۱⁴ شبکه کلاس B داشت که هر کدام شامل حداکثر ۲^۱⁶ گره می‌شوند.

کلاس C

در این کلاس، آدرس IP:I1.I2.I3.I4 به شکل R1.R2.R3.N1 درمی‌آید، که در آن

R1.R2.R3
نشانگر آدرس شبکه است
N1
آدرس یک ماشین در آن شبکه است

به طور دقیق‌تر، یک آدرس کلاس C به شکل IP به شرح زیر است:

آدرس شبکه ۳ بایت (منهای ۳ بیت) و آدرس میزبان ۱ بایت است. بنابراین می‌توان ۲²¹ شبکه کلاس C وجود داشته باشد که هر کدام تا سقف ۲۵۶ میزبان را در خود جای می‌دهند.

از آنجایی که آدرس ماشین Lagaffe در دانشکده علوم آنژ، 193.49.144.1 است، می‌توانیم ببینیم که بایت مهم‌تر 193 است که در مبنای دو عدد 11000001 است. بنابراین می‌توان نتیجه گرفت که این شبکه یک شبکه کلاس C است.

آدرس‌های رزرو شده

  • برخی از آدرس‌های IP، آدرس‌های شبکه‌ای هستند، نه آدرس گره‌ها در شبکه. اینها آدرس‌هایی هستند که در آنها آدرس گره روی 0 تنظیم شده است. بنابراین، آدرس 193.49.144.0 آدرس شبکه IP برای دانشکده علوم در آنژ است. در نتیجه، هیچ گره‌ای در یک شبکه نمی‌تواند آدرس صفر را داشته باشد.
  • وقتی در آدرسی مانند IP، آدرس گره صرفاً از بیت‌های یک تشکیل شده باشد، این آدرس به عنوان آدرس پخش شناخته می‌شود: این آدرس به تمام گره‌های شبکه اشاره دارد.
  • در یک شبکه کلاس C که از نظر تئوری امکان وجود 2⁸ = 256 گره را فراهم می‌کند، اگر دو آدرس ممنوعه حذف شوند، تنها 254 آدرس مجاز باقی می‌ماند.

9.1.5.2. پروتکل‌های تبدیل آدرس اینترنتی <--> آدرس فیزیکی

دیدیم که وقتی داده‌ها از یک دستگاه به دستگاه دیگر ارسال می‌شوند، هنگام عبور از لایه IP، در بسته‌ها (packets) جاسازی (encapsulated) می‌شوند. این بسته‌ها شکل زیر را دارند:

بنابراین بسته IP شامل آدرس‌های اینترنتی ماشین‌های مبدأ و مقصد است. هنگامی که این بسته به لایه مسئول ارسال آن بر روی شبکه فیزیکی ارسال می‌شود، اطلاعات بیشتری به آن اضافه می‌شود تا فریم فیزیکی را که در نهایت از طریق شبکه ارسال خواهد شد، تشکیل دهد. برای مثال، فرمت یک فریم در یک شبکه اترنت به شرح زیر است:

فریم نهایی حاوی آدرس‌های فیزیکی دستگاه‌های مبدأ و مقصد است. این آدرس‌ها چگونه به دست می‌آیند؟

دستگاه فرستنده، با دانستن آدرس IP دستگاهی که می‌خواهد با آن ارتباط برقرار کند، آدرس فیزیکی آن را با استفاده از یک پروتکل خاص به نام ARP (پروتکل حل نشانی) به دست می‌آورد.

  • این ماشین یک نوع بسته ویژه به نام ARP را ارسال می‌کند که حاوی آدرس IP ماشینی است که آدرس فیزیکی آن مورد جستجو قرار دارد. همچنین با دقت آدرس خود، IP، و همچنین آدرس فیزیکی خود را نیز در آن گنجانده است.
  • این بسته به همه گره‌های شبکه ارسال می‌شود.
  • این گره‌ها ماهیت ویژه این بسته را تشخیص می‌دهند. گره‌ای که آدرس خود، IP، را در بسته تشخیص می‌دهد، با ارسال آدرس فیزیکی خود به فرستنده بسته پاسخ می‌دهد. این کار را چگونه انجام می‌دهد؟ آن آدرس فرستنده، IP، و آدرس فیزیکی او را در داخل بسته پیدا کرده است.
  • بنابراین فرستنده، آدرس فیزیکی مورد نظر خود را دریافت می‌کند. این آدرس را در حافظه ذخیره می‌کند تا در صورتی که نیاز به ارسال بسته‌های بیشتر به همان گیرنده باشد، از آن استفاده کند.

آدرس IP یک دستگاه معمولاً در یکی از فایل‌های آن ذخیره می‌شود، که بنابراین می‌تواند برای بازیابی آن به آن مراجعه کند. این آدرس قابل تغییر است: تنها کاری که لازم است انجام شود ویرایش فایل است. با این حال، آدرس فیزیکی در حافظه کارت شبکه ذخیره می‌شود و قابل تغییر نیست.

وقتی یک مدیر بخواهد شبکه خود را بازسازی کند، ممکن است لازم باشد آدرس‌های IP تمام گره‌ها را تغییر دهد و در نتیجه فایل‌های پیکربندی هر گره را ویرایش کند. این کار در صورت وجود تعداد زیادی ماشین می‌تواند خسته‌کننده و مستعد خطا باشد. یک روش این است که به ماشین‌ها آدرس IP اختصاص داده نشود: در عوض، یک کد ویژه در فایلی وارد می‌شود که انتظار می‌رود ماشین از آن آدرس IP خود را بازیابی کند. هنگامی که متوجه می‌شود آدرس IP را ندارد، ماشین با استفاده از پروتکلی به نام RARP (پروتکل حل نشانی معکوس) آن را درخواست می‌کند. سپس یک بسته ویژه به نام بسته RARP را از طریق شبکه ارسال می‌کند که مشابه بسته قبلی ARP است و در آن آدرس فیزیکی خود را درج می‌کند. این بسته به همه گره‌ها ارسال می‌شود و آن‌ها آن را به عنوان یک بسته RARP تشخیص می‌دهند. یکی از این‌ها که به عنوان سرور RARP شناخته می‌شود، فایلی دارد که نگاشت بین آدرس‌های فیزیکی و آدرس‌های IP را برای همه گره‌ها در خود جای داده است. سپس به فرستنده بسته RARP پاسخ می‌دهد و آدرس IP خود را ارسال می‌کند. بنابراین، یک مدیر که مایل به پیکربندی مجدد شبکه خود است، به سادگی کافی است فایل نگاشت را روی سرور RARP ویرایش کند. این سرور معمولاً باید یک آدرس ثابت، IP، داشته باشد که باید بتواند آن را بدون نیاز به استفاده از پروتکل RARP خود بداند.

9.1.6. لایه شبکه، که به نام لایه IP اینترنت شناخته می‌شود

پروتکل IP (پروتکل اینترنت) قالب بسته‌ها و نحوهٔ پردازش آن‌ها هنگام ارسال یا دریافت را تعریف می‌کند. این نوع خاص بسته، دیتاگرام IP نامیده می‌شود. ما قبلاً در این مورد بحث کرده‌ایم:

نکته مهم این است که، علاوه بر داده‌های ارسالی، دیتاگرام IP حاوی آدرس‌های اینترنتی ماشین‌های مبدأ و مقصد است. این امر به ماشین مقصد امکان می‌دهد تا بداند چه کسی در حال ارسال پیام برای آن است.

برخلاف یک فریم شبکه که طول آن توسط ویژگی‌های فیزیکی شبکه‌ای که در آن حرکت می‌کند تعیین می‌شود، طول دیتاگرام IP توسط نرم‌افزار ثابت می‌شود و بنابراین در شبکه‌های فیزیکی مختلف یکسان خواهد بود. دیدیم که هنگام حرکت از لایه شبکه به سمت لایه فیزیکی، دیتاگرام IP در یک فریم فیزیکی قرار می‌گیرد. ما مثال فریم فیزیکی برای یک شبکه اترنت را ارائه داده‌ایم:

فریم‌های فیزیکی از گره به گره به سمت مقصد خود حرکت می‌کنند، که ممکن است در همان شبکه فیزیکی که ماشین فرستنده در آن قرار دارد نباشد. بنابراین، بسته IP ممکن است به صورت متوالی در فریم‌های فیزیکی مختلف در گره‌هایی که محل اتصال دو شبکه با انواع متفاوت هستند، جاسازی شود. همچنین ممکن است بسته IP برای جاسازی شدن در یک فریم فیزیکی بیش از حد بزرگ باشد. سپس نرم‌افزار IP در گره‌ای که این مشکل در آن رخ می‌دهد، بسته IP را طبق قوانین خاصی به fragments تقسیم می‌کند و هر بخش به نوبه خود از طریق شبکه فیزیکی ارسال می‌شود. این بخش‌ها تنها در مقصد نهایی خود دوباره به هم متصل خواهند شد.

9.1.6.1. مسیر یابی

روتینگ روشی است برای هدایت بسته‌های IP به مقصدشان. دو روش وجود دارد: مسیریابی مستقیم و مسیریابی غیرمستقیم.

مسیر‌یابی مستقیم

مسیر‌یابی مستقیم به انتقال یک بسته IP مستقیماً از فرستنده به گیرنده در همان شبکه اشاره دارد:

  • ماشینی که یک دیتاگرام IP را ارسال می‌کند، آدرس گیرنده، IP را دارد.
  • این پروتکل آدرس فیزیکی گیرنده را از طریق ARP یا از جدول‌های خود، در صورتی که قبلاً این آدرس به‌دست آمده باشد، به‌دست می‌آورد.
  • این بسته را از طریق شبکه به آن آدرس فیزیکی ارسال می‌کند.

مسیر‌یابی غیرمستقیم

مسیر یابی غیرمستقیم به فوروارد کردن یک بسته IP به مقصدی در شبکه‌ای غیر از شبکه‌ای که فرستنده به آن تعلق دارد، اشاره دارد. در این حالت، بخش‌های آدرس شبکه در آدرس‌های IP ماشین‌های مبدا و مقصد متفاوت هستند. ماشین مبدا این موضوع را تشخیص می‌دهد. سپس بسته را به یک گره ویژه به نام روتر (router) ارسال می‌کند؛ گره‌ای که یک شبکه محلی را به شبکه‌های دیگر متصل می‌کند و آدرس آن (IP) را در جدول‌های خود پیدا می‌کند؛ این آدرس در ابتدا یا از یک فایل، یا از حافظه دائمی، یا از طریق اطلاعاتی که در شبکه در گردش است، به دست آمده است.

یک روتر به دو شبکه متصل است و در هر دوی این شبکه‌ها آدرسی به نام IP دارد.

در مثال بالا:

  • شبکه شماره ۱ آدرس اینترنتی 193.49.144.0 و شبکه شماره ۲ آدرس 193.49.145.0 را دارد.
  • در شبکه شماره ۱، روتر آدرس ۱۹۳.۴۹.۱۴۴.۶ و در شبکه شماره ۲ آدرس ۱۹۳.۴۹.۱۴۵.۳ را دارد.

نقش روتر این است که بسته IP را که دریافت می‌کند—که در یک فریم فیزیکی متداول در شبکه شماره ۱ قرار دارد—به یک فریم فیزیکی تبدیل کند که بتواند از طریق شبکه شماره ۲ ارسال شود. اگر آدرس گیرنده بسته IP در شبکه شماره ۲ باشد، روتر بسته را مستقیماً برای آن ارسال می‌کند؛ در غیر این صورت، آن را به روتر دیگری که شبکه شماره ۲ را به شبکه شماره ۳ متصل می‌کند، و به همین ترتیب، ارسال خواهد کرد.

9.1.6.2. پیام‌های خطا و کنترل

همچنین در لایه شبکه – و بنابراین در همان سطح پروتکل IP – پروتکل ICMP (پروتکل پیام کنترلی اینترنت) قرار دارد. از این پروتکل برای ارسال پیام‌هایی در مورد عملکرد داخلی شبکه استفاده می‌شود: گره‌های از کار افتاده، تراکم در یک روتر و غیره. پیام‌های ICMP در بسته‌های IP جاسازی شده و از طریق شبکه ارسال می‌شوند. لایه‌های IP در گره‌های مختلف بر اساس پیام‌های ICMP که دریافت می‌کنند، اقدامات مناسب را انجام می‌دهند. در نتیجه، خودِ یک برنامه هرگز این مسائل خاص شبکه را مشاهده نمی‌کند. یک گره از اطلاعات ICMP برای به‌روزرسانی جدول‌های مسیریابی خود استفاده می‌کند.

9.1.7. لایه انتقال: پروتکل‌های UDP و TCP

9.1.7.1. پروتکل UDP: پروتکل دادگرام کاربر

پروتکل UDP امکان تبادل ناسازگار داده‌ها بین دو نقطه را فراهم می‌کند؛ به عبارت دیگر، تحویل صحیح یک بسته به مقصدش تضمین نشده است. برنامه کاربردی، در صورت تمایل، می‌تواند این کار را خود مدیریت کند، برای مثال با منتظر ماندن برای تأیید دریافت پس از ارسال یک پیام، پیش از ارسال پیام بعدی.

تا اینجای کار، در سطح شبکه، در مورد آدرس‌های IP برای ماشین‌ها بحث کرده‌ایم. با این حال، روی یک ماشین واحد، فرآیندهای مختلفی می‌توانند همزمان وجود داشته باشند که همگی می‌توانند با یکدیگر ارتباط برقرار کنند. بنابراین، هنگام ارسال یک پیام، لازم است نه تنها آدرس IP ماشین گیرنده، بلکه «نام» فرآیند گیرنده را نیز مشخص کرد. این نام در واقع یک عدد است که به آن شماره پورت گفته می‌شود. برخی اعداد برای برنامه‌های استاندارد رزرو شده‌اند: برای مثال، پورت ۶۹ برای برنامه TFTP (پروتکل انتقال فایل ساده). بسته‌هایی که توسط پروتکل UDP پردازش می‌شوند، همچنین به نام دیتاگرام شناخته می‌شوند. آن‌ها شکل زیر را دارند:

این دیتاگرام‌ها در بسته‌های IP و سپس در فریم‌های فیزیکی قرار می‌گیرند.

9.1.7.2. پروتکل TCP: پروتکل کنترل انتقال

برای ارتباطات امن، پروتکل UDP نامناسب است: توسعه‌دهندهٔ برنامه باید پروتکل خود را طراحی کند تا اطمینان حاصل شود که بسته‌ها به‌درستی مسیریابی می‌شوند.

پروتکل TCP (پروتکل کنترل انتقال) از این مشکلات جلوگیری می‌کند. ویژگی‌های آن به شرح زیر است:

  • فرآیندی که قصد ارسال داده را دارد، ابتدا یک اتصال با فرایندی که قرار است اطلاعات را دریافت کند، برقرار می‌نماید. این اتصال بین یک پورت در ماشین ارسال‌کننده و یک پورت در ماشین دریافت‌کننده برقرار می‌شود. بدین ترتیب یک مسیر مجازی بین دو پورت ایجاد می‌شود که منحصراً برای دو فرایندی که اتصال را برقرار کرده‌اند، رزرو شده است.
  • تمام بسته‌های ارسالی توسط فرآیند مبدأ از این مسیر مجازی پیروی می‌کنند و به ترتیبی که ارسال شده‌اند، می‌رسند؛ امری که در پروتکل UDP تضمین نشده بود، زیرا بسته‌ها می‌توانستند از مسیرهای متفاوتی عبور کنند.
  • داده‌های ارسال‌شده پیوسته به نظر می‌رسند. فرآیند ارسال‌کننده داده‌ها را با نرخ خود ارسال می‌کند. این داده‌ها لزوماً بلافاصله ارسال نمی‌شوند: پروتکل TCP منتظر می‌ماند تا داده‌ی کافی برای ارسال داشته باشد. این داده‌ها در ساختاری به نام سگمنت TCP ذخیره می‌شوند. به محض اینکه این بخش پر شود، به لایه IP منتقل می‌شود، جایی که در یک بسته IP جاسازی خواهد شد.
  • هر بخش ارسال‌شده توسط پروتکل TCP شماره‌گذاری می‌شود. پروتکل دریافت‌کننده TCP بررسی می‌کند که بخش‌ها را به ترتیب دریافت می‌کند. برای هر بخش که به درستی دریافت می‌شود، یک تأیید به فرستنده ارسال می‌کند.
  • هنگامی که فرستنده این تأیید را دریافت می‌کند، فرآیند ارسال را مطلع می‌سازد. بنابراین، فرآیند ارسال می‌تواند تأیید کند که یک بخش به سلامت رسیده است، امری که با پروتکل UDP امکان‌پذیر نبود.
  • اگر پروتکل TCP که یک سگمنت را ارسال کرده است، پس از گذشت مدت زمان معینی، هیچ تأییدیه‌ای دریافت نکند، آن سگمنت را مجدداً ارسال می‌کند و بدین ترتیب کیفیت سرویس تحویل اطلاعات را تضمین می‌نماید.
  • مدار مجازی ایجاد شده بین دو فرآیند ارتباطی full-duplex است: این بدان معناست که اطلاعات می‌تواند در هر دو جهت جریان یابد. بنابراین، فرآیند مقصد می‌تواند حتی زمانی که فرآیند منبع به ارسال اطلاعات ادامه می‌دهد، تأییدها را ارسال کند. این امر به پروتکل منبع TCP اجازه می‌دهد تا چندین بخش را بدون انتظار برای تأیید ارسال کند. اگر پس از مدتی متوجه شود که برای بخش خاصی با شماره n تأییدی دریافت نکرده است، ارسال بخش‌ها را از آن نقطه از سر خواهد گرفت.

9.1.8. لایه کاربردی

در بالای پروتکل‌های UDP و TCP، پروتکل‌های استاندارد مختلفی وجود دارند:

TELNET

این پروتکل به یک کاربر روی ماشین A در شبکه اجازه می‌دهد تا به ماشین B (که اغلب به آن ماشین میزبان گفته می‌شود) متصل شود. TELNET یک ترمینال به اصطلاح جهانی را روی ماشین A شبیه‌سازی می‌کند. بنابراین کاربر طوری رفتار می‌کند که گویی یک ترمینال به ماشین B متصل است. تل‌نت بر پایه پروتکل TCP است.

FTP: (پروتکل انتقال فایل)

این پروتکل امکان تبادل فایل بین دو ماشین راه دور و همچنین عملیات‌های فایل مانند ایجاد دایرکتوری را فراهم می‌کند. این پروتکل بر اساس پروتکل TCP ساخته شده است.

TFTP: (کنترل انتقال فایل پیش‌پاافتاده)

این پروتکل یک گونه‌ی FTP است. این پروتکل بر اساس پروتکل UDP ساخته شده و نسبت به FTP از پیچیدگی کمتری برخوردار است.

DNS: (سیستم نام دامنه)

هنگامی که یک کاربر بخواهد با یک ماشین راه دور، برای مثال با استفاده از FTP، فایل مبادله کند، باید آدرس اینترنتی آن ماشین را بداند. برای مثال، برای انجام FTP روی ماشین «Lagaffe» در دانشگاه آنژ، لازم است FTP را به شکل زیر اجرا کرد: FTP 193.49.144.1

این کار نیازمند یک نگاشت دایرکتوری برای تبدیل نام ماشین‌ها به آدرس‌های IP است. احتمالاً در این دایرکتوری، ماشین‌ها با نام‌های نمادین مانند زیر مشخص خواهند شد:

ماشین DPX2/320 در دانشگاه آنژ

ماشین Sun در دانشگاه آنژه (ISERPA)

واضح است که ارجاع به یک ماشین با نام، راحت‌تر از آدرس آن یعنی IP است. این مسئله، موضوع یکتایی نام را مطرح می‌کند: میلیون‌ها ماشین به هم متصل وجود دارند. می‌توان تصور کرد که یک نهاد متمرکز نام‌ها را اختصاص دهد. این کار بدون شک بسیار دست‌وپاگیر خواهد بود. در واقع، کنترل نام‌ها در میان دامنه‌ها توزیع شده است. هر دامنه توسط سازمانی مدیریت می‌شود که عموماً بسیار چابک است و در انتخاب نام ماشین‌ها آزادی کامل دارد. بنابراین، ماشین‌های فرانسه به دامنه «fr» تعلق دارند که توسط Inria در پاریس مدیریت می‌شود. برای ساده‌تر کردن موضوع، کنترل بار دیگر توزیع می‌شود: دامنه‌هایی در داخل دامنه «fr» ایجاد می‌شوند. بنابراین، دانشگاه آنژه به دامنه «univ-Angers» تعلق دارد. بخش مدیریت‌کننده این دامنه آزادی کامل برای نام‌گذاری رایانه‌های موجود در شبکه دانشگاه آنر دارد. در حال حاضر، این دامنه به زیرمجموعه‌هایی تقسیم نشده است. با این حال، در یک دانشگاه بزرگ با رایانه‌های فراوان متصل به شبکه، می‌توان آن را به زیرمجموعه‌هایی تقسیم کرد.

رایانه DPX2/رایانه 320 در دانشگاه آنژه با نام Lagaffe نام‌گذاری شده است، در حالی که یک رایانه با شناسه PC و 486DX50 با نام liny نام‌گذاری شده است. این ماشین‌ها چگونه باید به‌طور خارجی ارجاع داده شوند؟ با مشخص کردن سلسله‌مراتب دامنه‌هایی که به آن‌ها تعلق دارند. بنابراین، نام کامل ماشین «Lagaffe» خواهد بود:

Lagaffe.univ-Angers.fr

در داخل دامنه‌ها می‌توان از نام‌های نسبی استفاده کرد. بنابراین، در داخل دامنهٔ «fr» و خارج از دامنهٔ «univ-Angers»، می‌توان به ماشین «Lagaffe» به صورت اشاره کرد

Lagaffe.univ-Angers

در نهایت، در داخل دامنه univ-Angers، می‌توان به آن به سادگی به صورت

لاگاف

بنابراین یک برنامه می‌تواند با استفاده از نام به یک ماشین ارجاع دهد. با این حال، در نهایت همچنان باید آدرس اینترنتی آن ماشین را به دست آورد. این کار چگونه انجام می‌شود؟ فرض کنید که از ماشین A می‌خواهیم با ماشین B ارتباط برقرار کنیم.

  • اگر ماشین B به همان دامنه‌ای که ماشین A در آن قرار دارد تعلق داشته باشد، آدرس آن IP احتمالاً در فایلی روی ماشین A یافت می‌شود.
  • در غیر این صورت، ماشین A در فایلی دیگر یا همان فایل قبلی، فهرستی از چندین سرور نام به همراه آدرس‌هایشان IP را پیدا خواهد کرد. یک سرور نام مسئول نگاشت نام یک ماشین به آدرس آن IP است. ماشین A یک پرسش ویژه به اولین سرور نام در فهرست خود ارسال می‌کند که به آن پرسش DNS گفته می‌شود و بنابراین شامل نام ماشینی است که مورد جستجو قرار دارد. اگر سرور مورد پرسش این نام را در سوابق خود داشته باشد، آدرس متناظر، IP، را برای ماشین A ارسال خواهد کرد. در غیر این صورت، سرور در فایل‌های خود فهرستی از سرورهای نامی را که می‌تواند از آن‌ها پرس‌وجو کند، پیدا خواهد کرد. سپس این کار را انجام می‌دهد. بدین ترتیب، از تعدادی سرور نامی پرس‌وجو می‌شود، نه به صورت تصادفی، بلکه به گونه‌ای که تعداد پرس‌وجوها را به حداقل می‌رساند. اگر در نهایت ماشین پیدا شود، پاسخ به ماشین A بازگردانده خواهد شد.

XDR: (نمایش داده eXternal)

این پروتکل که توسط Sun MicroSystems ایجاد شده است، یک نمایش استاندارد و مستقل از ماشین از داده‌ها را مشخص می‌کند.

RPC: (فراخوانی رویه‌ای از راه دور)

همچنین توسط Sun تعریف شده است، این یک پروتکل ارتباطی مستقل از لایه انتقال برای برنامه‌های کاربردی راه دور است. این پروتکل مهم است: برنامه‌نویس را از نیاز به دانستن جزئیات لایه انتقال رها می‌کند و برنامه‌های کاربردی را قابل حمل می‌سازد. این پروتکل بر اساس پروتکل XDR است.

NFS: سیستم فایل شبکه

این پروتکل که توسط شرکت Sun نیز تعریف شده است، به یک دستگاه امکان می‌دهد تا سیستم فایل دستگاه دیگر را «ببیند». این پروتکل بر اساس پروتکل قبلی RPC ساخته شده است.

9.1.9. نتیجه‌گیری

در این مقدمه، برخی از جنبه‌های کلیدی پروتکل‌های اینترنت را تشریح کردیم. برای بررسی عمیق‌تر این موضوع، خوانندگان می‌توانند به کتاب عالی داگلاس کامر مراجعه کنند:

عنوان
TCP/IP: معماری، پروتکل‌ها، کاربردها.
نویسنده
داگلاس COMER
ناشر
InterEditions

9.2. مدیریت آدرس شبکه

یک دستگاه در اینترنت به‌طور منحصربه‌فرد با یک آدرس پروتکل اینترنت (IP) از شکل IP شناسایی می‌شود، که در آن In عددی بین ۱ و ۲۵۴ است. همچنین می‌توان آن را با یک نام منحصربه‌فرد شناسایی کرد. استفاده از این نام اجباری نیست، زیرا در نهایت برنامه‌ها همیشه از آدرس‌های IP ماشین‌ها استفاده می‌کنند. این نام‌ها برای آسان‌تر کردن کار کاربران وجود دارند. بنابراین، درخواست کردن http://www.ibm.com با استفاده از یک مرورگر وب آسان‌تر از URL http://129.42.17.99 است، هرچند هر دو روش امکان‌پذیر هستند. نقشه‌برداری بین آدرس‌های IP و nomMachine توسط یک سرویس توزیع‌شده اینترنتی به نام DNS (سیستم نام دامنه) فراهم می‌شود. پلتفرم .NET کلاس Dns را برای مدیریت آدرس‌های اینترنتی فراهم می‌کند:

Image

اکثر متدهای این کلاس ایستا هستند. بیایید نگاهی به متدهایی بیندازیم که برای ما جالب هستند:

Overloads Public Shared Function
 GetHostByAddress(ByVal address As
 String) As IPHostEntry
یک آدرس IPHostEntry را از یک آدرس IP به شکل "I1.I2.I3.I4" بازمی‌گرداند. اگر ماشین address پیدا نشود، یک استثنا پرتاب می‌کند.
Public Shared Function
 GetHostByName(ByVal hostName As
 String) As IPHostEntry
یک آدرس IPHostEntry را از روی نام ماشین برمی‌گرداند. اگر ماشین name پیدا نشود، یک استثنا پرتاب می‌کند.
Public Shared Function 
GetHostName() As String
نام ماشینی را که برنامهٔ در حال اجرای این دستور روی آن در حال اجرا است، بازمی‌گرداند

آدرس‌های شبکه از نوع IPHostEntry شکل زیر را دارند:

ویژگی‌های مورد توجه ما عبارتند از:

Public Property AddressList 
As IPAddress ()
فهرست آدرس‌های IP برای یک ماشین. در حالی که یک آدرس IP به یک و تنها یک ماشین فیزیکی اشاره دارد، یک ماشین فیزیکی ممکن است چندین آدرس IP داشته باشد. این حالت زمانی رخ می‌دهد که چندین کارت شبکه آن را به شبکه‌های مختلف متصل کرده باشد.
Public Property Aliases 
As String ()
فهرست نام‌های مستعار برای یک ماشین، که ممکن است با یک نام اصلی و نام‌های مستعار شناسایی شود
Public Property HostName 
As String
نام ماشین، در صورت وجود

از کلاس IPAddress، ما سازنده، ویژگی‌ها و متدهای زیر را مشاهده خواهیم کرد:

Image

یک شیء [IPAddress] را می‌توان با استفاده از متد ToString() به رشته I1.I2.I3.I4 تبدیل کرد. برعکس، می‌توان یک شیء IPAddress را از یک رشته I1.I2.I3.I4 با استفاده از متد استاتیک IPAddress.Parse("I1.I2.I3.I4") به دست آورد. برنامه زیر را در نظر بگیرید که نام ماشینی را که روی آن اجرا می‌شود نمایش می‌دهد و سپس، به صورت تعاملی، نگاشت‌های بین آدرس IP و نام ماشین را ارائه می‌دهد:

dos>address1
Machine Locale=tahe

Machine recherchée (fin pour arrêter) : istia.univ-angers.fr
Machine : istia.univ-angers.fr
Adresses IP : 193.49.146.171

Machine recherchée (fin pour arrêter) : 193.49.146.171
Machine : istia.istia.univ-angers.fr
Adresses IP : 193.49.146.171
Alias : 171.146.49.193.in-addr.arpa

Machine recherchée (fin pour arrêter) : www.ibm.com
Machine : www.ibm.com
Adresses IP : 129.42.17.99,129.42.18.99,129.42.19.99,129.42.16.99

Machine recherchée (fin pour arrêter) : 129.42.17.99
Machine : www.ibm.com
Adresses IP : 129.42.17.99

Machine recherchée (fin pour arrêter) : x.y.z
Impossible de trouver la machine [x.y.z]

Machine recherchée (fin pour arrêter) : localhost
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : 127.0.0.1
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : tahe
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : fin

برنامه به شرح زیر است:


' گزینه‌ها
Option Explicit On 
Option Strict On

' فضاهای نام
Imports System
Imports System.Net
Imports System.Text.RegularExpressions

' ماژول تست
Public Module adresses

    Sub Main()
        'نام ماشین محلی را نمایش می‌دهد
        'سپس به‌صورت تعاملی اطلاعاتی دربارهٔ ماشین‌های شبکه ارائه می‌دهد
        'شناسایی‌شده توسط یک نام یا یک آدرس IP
        ' ماشین محلی
        Dim localHost As String = Dns.GetHostName()
        Console.Out.WriteLine(("Machine Locale=" + localHost))

        ' جلسه تعاملی پرسش و پاسخ
        Dim machine As String
        Dim adresseMachine As IPHostEntry
        While True
            ' وارد کردن نام ماشینی که در حال جستجو است
            Console.Out.Write("Machine recherchée (fin pour arrêter) : ")
            machine = Console.In.ReadLine().Trim().ToLower()
            ' انجام شد؟
            If machine = "fin" Then
                Exit While
            End If

            ' I1.I2.I3.I4 آدرس یا نام ماشین؟
            Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$")
            ' پردازش استثنا
            Try
                If isIPV4 Then
                    adresseMachine = Dns.GetHostByAddress(machine)
                Else
                    adresseMachine = Dns.GetHostByName(machine)
                End If
                ' نام
                Console.Out.WriteLine(("Machine : " + adresseMachine.HostName))
                ' آدرس‌های IP
                Console.Out.Write(("Adresses IP : " + adresseMachine.AddressList(0).ToString))
                Dim i As Integer
                For i = 1 To adresseMachine.AddressList.Length - 1
                    Console.Out.Write(("," + adresseMachine.AddressList(i).ToString))
                Next i
                Console.Out.WriteLine()
                'نام‌های مستعار
                If adresseMachine.Aliases.Length <> 0 Then
                    Console.Out.Write(("Alias : " + adresseMachine.Aliases(0)))
                    For i = 1 To adresseMachine.Aliases.Length - 1
                        Console.Out.Write(("," + adresseMachine.Aliases(i)))
                    Next i
                    Console.Out.WriteLine()
                End If
            Catch
                ' ماشین وجود ندارد
                Console.Out.WriteLine("Impossible de trouver la machine [" + machine + "]")
            End Try
        End While
    End Sub
End Module

9.3. برنامه TCP-IP

9.3.1. عمومی

بیایید ارتباط بین دو ماشین دور، A و B را در نظر بگیریم:

وقتی یک برنامه کاربردی AppA روی ماشین A می‌خواهد با یک برنامه کاربردی AppB روی ماشین B در اینترنت ارتباط برقرار کند، باید چندین چیز را بداند:

  • آدرس IP یا نام ماشین B
  • شماره پورت مورد استفاده توسط برنامه AppB. این به آن دلیل است که ماشین B ممکن است میزبان برنامه‌های متعددی باشد که در اینترنت در حال اجرا هستند. هنگامی که اطلاعاتی را از شبکه دریافت می‌کند، باید بداند آن اطلاعات برای کدام برنامه در نظر گرفته شده است. برنامه‌های روی ماشین B از طریق رابط‌هایی که به نام پورت‌های ارتباطی نیز شناخته می‌شوند، به شبکه دسترسی پیدا می‌کنند. این اطلاعات در بسته دریافتی توسط ماشین B گنجانده شده است تا بتواند به برنامه صحیح تحویل داده شود.
  • پروتکل‌های ارتباطی که ماشین B از آن‌ها پشتیبانی می‌کند. در مطالعه ما، تنها از پروتکل‌های TCP-IP استفاده خواهیم کرد.
  • پروتکل گفتگوی پذیرفته‌شده توسط برنامه AppB. در واقع، ماشین‌های A و B با یکدیگر «صحبت» خواهند کرد. آنچه می‌گویند در پروتکل‌های TCP-IP قرار داده خواهد شد. با این حال، زمانی که در سوی دیگر این زنجیره، برنامه AppB اطلاعاتی را که توسط برنامه AppA ارسال شده است دریافت می‌کند، باید بتواند آن را تفسیر کند. این مشابه وضعیتی است که دو نفر، A و B، از طریق تلفن با یکدیگر ارتباط برقرار می‌کنند: مکالمه آنها توسط تلفن منتقل می‌شود. گفتار توسط تلفن A به صورت سیگنال رمزگذاری شده و از طریق خطوط تلفن منتقل می‌شود و به تلفن B می‌رسد که در آنجا رمزگشایی می‌شود. سپس شخص B گفتار را می‌شنود. اینجاست که مفهوم پروتکل ارتباطی مطرح می‌شود: اگر A به زبان فرانسوی صحبت کند و B آن زبان را نفهمد، A و B قادر به برقراری ارتباط مؤثر نخواهند بود.

بنابراین، دو برنامه در حال ارتباط باید بر سر نوع دیالوگی که استفاده خواهند کرد، توافق کنند. برای مثال، دیالوگ با سرویس ftp با دیالوگ با سرویس pop یکسان نیست: این دو سرویس دستورات یکسانی را قبول نمی‌کنند. آنها پروتکل دیالوگ متفاوتی دارند.

9.3.2. ویژگی‌های پروتکل TCP

در اینجا، ما فقط ارتباطات شبکه‌ای را که از پروتکل حمل‌ونقل TCP استفاده می‌کنند، بررسی خواهیم کرد. بیایید ویژگی‌های آن را به یاد بیاوریم:

  • فرآیندی که مایل به ارسال داده است، ابتدا یک اتصال با فرایندی که قرار است اطلاعات را دریافت کند، برقرار می‌کند. این اتصال بین یک پورت در ماشین ارسال‌کننده و یک پورت در ماشین دریافت‌کننده برقرار می‌شود. بدین ترتیب یک مسیر مجازی بین دو پورت ایجاد می‌شود که منحصراً برای دو فرایندی که اتصال را برقرار کرده‌اند، رزرو خواهد شد.
  • تمام بسته‌های ارسالی توسط فرآیند مبدأ از این مسیر مجازی پیروی می‌کنند و به ترتیبی که ارسال شده‌اند، می‌رسند
  • داده‌های ارسال‌شده به صورت یک جریان پیوسته ظاهر می‌شوند. فرآیند ارسال‌کننده داده‌ها را با سرعت خود ارسال می‌کند. این داده‌ها لزوماً بلافاصله ارسال نمی‌شوند: پروتکل TCP منتظر می‌ماند تا داده‌ی کافی برای ارسال داشته باشد. این داده‌ها در ساختاری به نام «بخش TCP» ذخیره می‌شوند. به محض اینکه این بخش پر شود، به لایه IP منتقل می‌شود، جایی که در یک بسته IP جاسازی خواهد شد.
  • هر بخش ارسال‌شده توسط پروتکل TCP شماره‌گذاری می‌شود. پروتکل دریافت‌کننده TCP بررسی می‌کند که بخش‌ها را به ترتیب دریافت می‌کند. برای هر بخش که به درستی دریافت می‌شود، یک تأیید به فرستنده ارسال می‌کند.
  • هنگامی که فرستنده این تأیید را دریافت می‌کند، فرآیند ارسال را مطلع می‌سازد. بنابراین، فرآیند ارسال می‌تواند تأیید کند که یک بخش با موفقیت تحویل داده شده است.
  • اگر پروتکل TCP که یک سگمنت را ارسال کرده است، پس از گذشت مدت زمان معینی، تأیید دریافت را دریافت نکند، آن سگمنت را مجدداً ارسال می‌کند و بدین ترتیب کیفیت سرویس تحویل اطلاعات را تضمین می‌نماید.
  • مدار مجازی ایجاد شده بین دو فرآیند ارتباطی، full-duplex است: این بدان معناست که اطلاعات می‌تواند در هر دو جهت جریان یابد. بنابراین، فرآیند مقصد می‌تواند حتی زمانی که فرآیند منبع به ارسال اطلاعات ادامه می‌دهد، تأییدها را ارسال کند. این امر به پروتکل منشأ TCP اجازه می‌دهد تا چندین بخش را بدون انتظار برای تأیید ارسال کند. اگر پس از گذشت مدت زمانی مشخص، متوجه شود که برای بخش خاصی با شماره n تأییدی دریافت نکرده است، ارسال بخش‌ها را از آن نقطه از سر خواهد گرفت.

9.3.3. رابطهٔ کلاینت-سرور

ارتباطات در اینترنت اغلب ناهم‌زمان (asymmetric) است: ماشین A یک اتصال را برای درخواست یک سرویس از ماشین B آغاز می‌کند و مشخص می‌کند که می‌خواهد یک اتصال با سرویس SB1 روی ماشین B برقرار کند. ماشین B یا آن را می‌پذیرد یا رد می‌کند. اگر درخواست پذیرفته شود، ماشین A می‌تواند درخواست‌های خود را به سرویس SB1 ارسال کند. این درخواست‌ها باید مطابق با پروتکل گفتگوی مورد توافق سرویس SB1 باشند. بدین ترتیب یک دیالوگ درخواست-پاسخ بین ماشین A، که به عنوان ماشین کلاینت شناخته می‌شود، و ماشین B، که به عنوان ماشین سرور شناخته می‌شود، برقرار می‌گردد. یکی از این دو طرف اتصال را قطع خواهد کرد.

9.3.4. معماری کلاینت

معماری یک برنامه شبکه‌ای که از خدمات یک برنامه سرور استفاده می‌کند به شرح زیر خواهد بود:

ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
    tant que ce n'est pas fini
        préparer une demande
        l'émettre vers la machine B
        attendre et récupérer la réponse
        la traiter
    fin tant que
finsi

9.3.5. معماری سرور

معماری برنامه‌ای که خدمات ارائه می‌دهد به شرح زیر خواهد بود:

ouvrir le service sur la machine locale
tant que le service est ouvert
        se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que

برنامه سرور، درخواست اتصال اولیه یک کلاینت را متفاوت از درخواست‌های بعدی او برای یک سرویس مدیریت می‌کند. این برنامه خود سرویس را ارائه نمی‌دهد. اگر این کار را انجام می‌داد، در حین ارائه سرویس دیگر منتظر درخواست‌های اتصال نمی‌ماند و در نتیجه به کلاینت‌ها خدماتی ارائه نمی‌شد. بنابراین، روند کار به این صورت است: به محض دریافت و پذیرش یک درخواست اتصال در پورت گوش‌دادن، سرور وظیفه‌ای را ایجاد می‌کند که مسئول ارائه خدمتی است که توسط کلاینت درخواست شده است. این خدمت در پورت دیگری روی ماشین سرور ارائه می‌شود که به آن پورت سرویس گفته می‌شود. این امر امکان سرویس‌دهی به چندین کلاینت را به طور همزمان فراهم می‌کند. یک وظیفه سرویس ساختار زیر را خواهد داشت:

tant que le service n'a pas été rendu totalement
        attendre une demande sur le port de service
        lorsqu'il y en a une, élaborer la réponse
        transmettre la réponse via le port de service
fin tant que
     libérer le port de service

9.3.6. کلاس TcpClient

کلاس TcpClient کلاس مناسبی برای نمایش مشتری سرویس TCP است. این کلاس به شرح زیر تعریف شده است:

Image

سازنده‌ها، متدها و ویژگی‌های مورد توجه ما به شرح زیر هستند:

Public Sub New(ByVal hostname
 As String,ByVal port As Integer)
یک اتصال TCP با سرور در حال اجرا روی پورت مشخص‌شده (port) از ماشین مشخص‌شده (hostname) ایجاد می‌کند. برای مثال، `new TcpClient("istia.univ-angers.fr",80)` برای اتصال به پورت 80 روی ماشین istia.univ-angers.fr
Public Sub Close()
اتصال به سرور TCP را قطع می‌کند
Public Function GetStream()
 As NetworkStream
یک جریان NetworkStream را برای خواندن و نوشتن از سرور به دست می‌آورد. این جریان است که ارتباط کلاینت-سرور را ممکن می‌سازد.

9.3.7. کلاس NetworkStream

کلاس NetworkStream نمایانگر جریان شبکه بین کلاینت و سرور است. این کلاس به صورت زیر تعریف شده است:

Image

کلاس NetworkStream از کلاس Stream مشتق شده است. بسیاری از برنامه‌های مشتری-سرور خطوط متنی را که با کاراکترهای پایان خط «\r\n» خاتمه یافته‌اند، مبادله می‌کنند. بنابراین، استفاده از اشیاء StreamReader و StreamWriter برای خواندن و نوشتن این خطوط در جریان شبکه مفید است. هنگامی که دو ماشین با یکدیگر ارتباط برقرار می‌کنند، در هر انتهای اتصال یک شیء TcpClient وجود دارد. متد GetStream این شیء دسترسی به جریان شبکه (NetworkStream) را که دو ماشین را به هم متصل می‌کند، فراهم می‌آورد. بنابراین، اگر یک ماشین M1 یک اتصال را با یک ماشین M2 با استفاده از یک شیء TcpClient client1 که از طریق آن خطوط متن را مبادله می‌کنند، برقرار کرده باشد، می‌تواند استریم‌های خواندن و نوشتن خود را به صورت زیر ایجاد کند:

Dim in1 as StreamReader=new StreamReader(client1.GetStream())
Dim out1 as StreamWriter=new StreamWriter(client1.GetStream())
out1.AutoFlush=true

دستور

out1.AutoFlush=true

به این معنی است که جریان نوشتن از client1 از یک بافر واسطه عبور نخواهد کرد و مستقیماً به شبکه ارسال می‌شود. این نکته مهمی است. به طور کلی، هنگامی که client1 یک خط متن را به شریک خود ارسال می‌کند، انتظار پاسخ دارد. این پاسخ هرگز نخواهد رسید اگر خط در واقع در ماشین M1 بافر شده و هرگز ارسال نشده باشد. برای ارسال یک خط متن به ماشین M2، باید نوشت:

client1.WriteLine("un texte")

برای خواندن پاسخ از M2، به این صورت می‌نویسیم:

Dim réponse as String=client1.ReadLine()

9.3.8. معماری پایه یک کلاینت وب

اکنون ما عناصر لازم برای تعریف معماری پایه یک کلاینت وب را داریم:


    Dim client As TcpClient = Nothing     ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing     ' le flux d'écriture du client
    Dim demande As String = Nothing         ' demande du client
    Dim réponse As String = Nothing         ' réponse du serveur

    Try
      ' اتصال به سرویس در حال اجرا روی پورت P ماشین M
      client = New TcpClient(nomServeur, port)

      'جریان‌های ورودی-خروجی مشتری ایجاد می‌شوند TCP
      [IN] = New StreamReader(client.GetStream())
      OUT = New StreamWriter(client.GetStream())
      OUT.AutoFlush = True

      'حلقه درخواست-پاسخ
      While True
        ' آماده‌سازی درخواست
        demande = ...
        'آن را به سرور ارسال کنید
        OUT.WriteLine(demande)
        'پاسخ سرور را می‌خواند
        réponse = [IN].ReadLine()
        'پردازش پاسخ
        ...
            End While
            ' انجام شد
            client.Close()
        Catch ex As Exception
      ' پردازش استثنا
...
        End Try

9.3.9. کلاس TcpListener

کلاس TcpListener کلاس مناسبی برای نمایش یک سرویس TCP است. این کلاس به صورت زیر تعریف شده است:

Image

سازنده‌ها، متدها و ویژگی‌های مورد توجه ما به شرح زیر هستند:

Public Sub New(ByVal localaddr 
As IPAddress,ByVal port As Integer)
یک سرویس TCP ایجاد می‌کند که منتظر خواهد ماند (listen) برای درخواست‌های مشتری روی پورتی که به‌عنوان پارامتر ارسال می‌شود (port) که به‌عنوان پورت گوش‌دادن ماشین محلی با آدرس IP localadr شناخته می‌شود.
Public Function AcceptTcpClient() 
As TcpClient
درخواست مشتری را می‌پذیرد. یک شیء TcpClient مرتبط با یک پورت دیگر، که به عنوان پورت سرویس شناخته می‌شود، را بازمی‌گرداند.
Public Sub Start()
شروع به گوش دادن به درخواست‌های مشتری می‌کند
Public Sub Stop()
شنود درخواست‌های مشتری را متوقف می‌کند

9.3.10. معماری پایه یک سرور اینترنت

از آنچه قبلاً دیده‌ایم، می‌توانیم ساختار پایه یک سرور را استنتاج کنیم:


    'سرویس گوش دادن ایجاد شد
    Dim ecoute As TcpListener = Nothing
    Dim port As Integer = ...
    Try
      'سرویس ایجاد شد
      ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
      'سرویس راه‌اندازی شد
      ecoute.Start()
      'حلقه سرویس
      Dim liaisonClient As TcpClient = Nothing
            While not fini
                ' در انتظار یک مشتری
                liaisonClient = ecoute.AcceptTcpClient()
                ' سرویس توسط یک وظیفه دیگر مدیریت می‌شود
                Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
                tache.Start()
            End While
        Catch ex As Exception
            'خطا گزارش می‌شود
....
        End Try
        'پایان سرویس
        ecoute.Stop()

کلاس Service یک thread است که ممکن است به شکل زیر باشد:


Public Class Service

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie

    ' سازنده
    Public Sub New(ByVal liaisonClient As TcpClient, ...)
        Me.liaisonClient = liaisonClient
        ...
    End Sub

    ' روش اجرا
    Public Sub Run()
        ' سرویس را به مشتری بازمی‌گرداند
        Try
            'جریان ورودی
            [IN] = New StreamReader(liaisonClient.GetStream())
            'جریان خروجی
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            'حلقه خواندن/نوشتن درخواست/پاسخ
            Dim demande As String = Nothing
            Dim reponse As String = Nothing
            demande = [IN].ReadLine
            While Not (demande Is Nothing)
                ' درخواست پردازش می‌شود
                ...
                'ارسال پاسخ
                reponse = "[" + demande + "]"
                OUT.WriteLine(reponse)
                'درخواست بعدی
                demande = [IN].ReadLine
            End While
            'پایان اتصال
            liaisonClient.Close()
        Catch e As Exception
            ...
        End Try
        'پایان سرویس
    End Sub

9.4. مثال‌ها

9.4.1. سرور اکو

ما پیشنهاد می‌کنیم یک سرور اکو بنویسیم که با استفاده از دستور زیر از یک پنجره DOS راه‌اندازی شود:

serveurEcho port

سرور روی پورت مشخص‌شده به‌عنوان پارامتر اجرا می‌شود. این برنامه به‌سادگی درخواستی را که کلاینت ارسال کرده است، بازمی‌فرستد. برنامه به شرح زیر است:


' گزینه‌ها
Option Explicit On 
Option Strict On

' فضاهای نام
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

'فراخوانی: serveurEcho پورت
' سرور اکو
'خطی را که به کلاینت ارسال کرده بود به آن بازمی‌گرداند

Public Class serveurEcho
  Private Shared syntaxe As String = "Syntaxe : serveurEcho port"

  ' برنامهٔ اصلی
  Public Shared Sub Main(ByVal args() As String)

    'آیا آرگومانی وجود دارد
    If args.Length <> 1 Then
      erreur(syntaxe, 1)
    End If
    ' این آرگومان باید یک عدد صحیح بزرگ‌تر از 0 باشد
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(0))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
    ' سرویس گوش دادن ایجاد می‌شود
    Dim ecoute As TcpListener = Nothing
    Dim nbClients As Integer = 0 ' nbre de clients traités
    Try
      ' سرویس ایجاد می‌شود
      ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
      ' ما آن را راه‌اندازی می‌کنیم
      ecoute.Start()
      ' نظارت
      Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
      Console.Out.WriteLine(ecoute.LocalEndpoint)

      'حلقه سرویس
      Dim liaisonClient As TcpClient = Nothing
            While True
                'حلقه بی‌نهایت – با Ctrl-C خاتمه می‌یابد
                ' منتظر یک مشتری
                liaisonClient = ecoute.AcceptTcpClient()

                'سرویس توسط وظیفهٔ دیگری مدیریت می‌شود
                nbClients += 1
                Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
                tache.Start()
            End While
            ' به شنود درخواست‌ها بازمی‌گردد
        Catch ex As Exception
            ' گزارش خطا
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
        'پایان سرویس
        ecoute.Stop()
    End Sub

    ' نمایش خطاها
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' خطا نمایش داده شد
        System.Console.Error.WriteLine(msg)
        ' خاموش شدن با خطا
        Environment.Exit(exitCode)
    End Sub
End Class

' -------------------------------------------------------
'ارائه خدمات به مشتری سرور اکو
Public Class traiteClientEcho

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private numClient As Integer    ' n° de client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie

    ' سازنده
    Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer)
        Me.liaisonClient = liaisonClient
        Me.numClient = numClient
    End Sub

    ' روش اجرا
    Public Sub Run()
        ' به مشتری خدمت می‌کند
        Console.Out.WriteLine(("Début de service au client " & numClient))
        Try
            'جریان ورودی
            [IN] = New StreamReader(liaisonClient.GetStream())
            'جریان خروجی
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            'حلقه درخواست/پاسخ نوشتن
            Dim demande As String = Nothing
            Dim reponse As String = Nothing
            demande = [IN].ReadLine
            While Not (demande Is Nothing)
                ' نظارت
                Console.Out.WriteLine(("Client " & numClient & " : " & demande))
                ' سرویس زمانی متوقف می‌شود که کلاینت نشانگر پایان فایل را ارسال کند
                reponse = "[" + demande + "]"
                OUT.WriteLine(reponse)
                ' سرویس زمانی متوقف می‌شود که کلاینت «end» را ارسال کند
                If demande.Trim().ToLower() = "fin" Then
                    Exit While
                End If
                ' درخواست بعدی
                demande = [IN].ReadLine
            End While
            ' اتصال قطع شد
            liaisonClient.Close()
        Catch e As Exception
            erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2)
        End Try
        'پایان سرویس
        Console.Out.WriteLine(("Fin de service au client " & numClient))
    End Sub

    ' نمایش خطاها
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' خطا نمایش داده شد
        System.Console.Error.WriteLine(msg)
        ' خاموشی با خطا
        Environment.Exit(exitCode)
    End Sub
End Class

ساختار سرور مطابق معماری کلی سرورهای TCP است.

9.4.2. یک کلاینت برای سرور اکو

اکنون یک کلاینت برای سرور قبلی می‌نویسیم. این کلاینت به صورت زیر فراخوانی خواهد شد:

clientEcho nomServeur port

این برنامه به ماشین nomServeur روی پورت port متصل می‌شود و سپس خطوط متنی را به سرور ارسال می‌کند که سرور آن‌ها را منعکس می‌کند.


' گزینه‌ها
Option Explicit On 
Option Strict On

' فضاهای نام
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

Public Class clientEcho

  'به یک سرور اکو متصل می‌شود
  ' هر خطی که روی صفحه‌کلید تایپ می‌شود سپس بازتاب داده می‌شود
  Public Shared Sub Main(ByVal args() As String)
    ' دستور زبان
    Const syntaxe As String = "pg machine port"

    ' تعداد آرگومان‌ها
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' توجه به نام سرور
    Dim nomServeur As String = args(0)

    ' پورت باید یک عدد صحیح مثبت باشد
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(1))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If

        ' می‌توانید ادامه دهید
    Dim client As TcpClient = Nothing ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
    Dim demande As String = Nothing ' demande du client
    Dim réponse As String = Nothing ' réponse du serveur
    Try
      ' اتصال به سرویسی که روی پورت P ماشین M در حال اجرا است
      client = New TcpClient(nomServeur, port)

      'ایجاد جریان‌های ورودی-خروجی کلاینت TCP
      [IN] = New StreamReader(client.GetStream())
      OUT = New StreamWriter(client.GetStream())
      OUT.AutoFlush = True

      'حلقه درخواست-پاسخ
      While True
        'درخواست از صفحه‌کلید می‌آید
        Console.Out.Write("demande (fin pour arrêter) : ")
        demande = Console.In.ReadLine()
        ' این درخواست به سرور ارسال می‌شود
        OUT.WriteLine(demande)
        'پاسخ سرور را می‌خواند
        réponse = [IN].ReadLine()
        'پاسخ پردازش می‌شود
        Console.Out.WriteLine(("Réponse : " + réponse))
        ' انجام شد؟
        If demande.Trim().ToLower() = "fin" Then
          Exit While
                End If
            End While
            'کار تمام شد
            client.Close()
        Catch ex As Exception
      ' استثنا را مدیریت می‌کند
      erreur(ex.Message, 3)
        End Try
    End Sub

    'نمایش خطاها
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' نمایش خطا
        System.Console.Error.WriteLine(msg)
        ' با خطا خاتمه یافت
        Environment.Exit(exitCode)
    End Sub
End Class

ساختار این کلاینت مطابق با معماری کلی کلاینت‌های tcp.Voici است؛ نتایج حاصل در پیکربندی زیر:

  • سرور در پورت ۱۰۰ در یک پنجره DOS در حال اجرا است
  • روی همان ماشین، دو کلاینت در دو پنجرهٔ دیگر DOS در حال اجرا هستند

در پنجره مربوط به کلاینت ۱، نتایج زیر به دست می‌آید:

dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne1
Réponse : [ligne1]
demande (fin pour arrêter) : ligne1B
Réponse : [ligne1B]
demande (fin pour arrêter) : ligne1C
Réponse : [ligne1C]
demande (fin pour arrêter) : fin
Réponse : [fin]

در کلاینت ۲:

dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne2A
Réponse : [ligne2A]
demande (fin pour arrêter) : ligne2B
Réponse : [ligne2B]
demande (fin pour arrêter) : fin
Réponse : [fin]

در فایل سرور:

dos>serveurEcho 100
Serveur d'écho lancé sur le port 100
0.0.0.0:100
Début de service au client 1
Client 1 : ligne1
Début de service au client 2
Client 2 : ligne2A
Client 2 : ligne2B
Client 1 : ligne1B
Client 1 : ligne1C
Client 2 : fin
Fin de service au client 2
Client 1 : fin
Fin de service au client 1
^C

توجه داشته باشید که سرور واقعاً توانست به طور همزمان به دو مشتری سرویس دهد.

9.4.3. یک کلاینت عمومی TCP

بسیاری از سرویس‌هایی که در سپیده‌دم اینترنت ایجاد شدند، بر اساس مدل سرور اکو که پیش‌تر مورد بحث قرار گرفت، عمل می‌کنند: ارتباط کلاینت-سرور از طریق تبادل خطوط متن انجام می‌شود. ما قصد داریم یک کلاینت عمومی TCP بنویسیم که به صورت زیر اجرا خواهد شد: cltgen server port

این کلاینت، TCP، به پورت port روی سرور serveur متصل خواهد شد. پس از انجام این کار، دو تِرد ایجاد خواهد کرد:

  1. یک تِرد مسئول خواندن دستورات تایپ‌شده روی صفحه‌کلید و ارسال آن‌ها به سرور
  2. یک تِرد مسئول خواندن پاسخ‌های سرور و نمایش آن‌ها روی صفحه نمایش

چرا وقتی در برنامه قبلی نیازی به این نبود، به دو نخ نیاز است؟ در آن برنامه پروتکل گفتگو ثابت بود: کلاینت یک خط می‌فرستاد و سرور با یک خط پاسخ می‌داد. هر سرویس پروتکل خاص خود را دارد و ممکن است شرایط زیر نیز پیش بیاید:

  • کلاینت باید قبل از دریافت پاسخ، چندین خط متن ارسال کند
  • پاسخ یک سرور ممکن است شامل چندین خط متن باشد

بنابراین، حلقه مربوط به ارسال یک خط به سرور و دریافت یک خط از سرور همیشه مناسب نیست. بنابراین ما دو حلقه مجزا ایجاد خواهیم کرد:

  • یک حلقه برای خواندن دستورات تایپ‌شده روی صفحه‌کلید جهت ارسال به سرور. کاربر پایان دستورات را با کلمه کلیدی fin علامت‌گذاری خواهد کرد.
  • یک حلقه برای دریافت و نمایش پاسخ‌های سرور. این یک حلقه بی‌نهایت خواهد بود که تنها زمانی متوقف می‌شود که سرور اتصال شبکه را قطع کند یا کاربر فرمان fin را تایپ کند.

برای جدا نگه داشتن این دو حلقه، به دو نخ (thread) مستقل نیاز داریم. بیایید مثالی از اجرا را بررسی کنیم که در آن کلاینت عمومی TCP ما به سرویس SMTP (پروتکل انتقال SendMail) متصل می‌شود. این سرویس مسئول مسیریابی ایمیل به گیرندگان است. این سرویس بر روی پورت ۲۵ کار می‌کند و از یک پروتکل گفتگوی مبتنی بر متن استفاده می‌کند.

dos>cltgen istia.univ-angers.fr 25
Commandes :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
help
<-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented
mail from: machin@univ-angers.fr
<-- 250 2.1.0 machin@univ-angers.fr... Sender ok
rcpt to: serge.tahe@istia.univ-angers.fr
<-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok
data
<-- 354 Enter mail, end with "." on a line by itself
Subject: test

ligne1
ligne2
ligne3
.
<-- 250 2.0.0 g4D6bks25951 Message accepted for delivery
quit
<-- 221 2.0.0 istia.univ-angers.fr closing connection
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

بیایید این مبادلات کلاینت-سرور را تحلیل کنیم:

  • سرویس SMTP هنگام اتصال یک کلاینت به آن، یک پیام خوش‌آمدگویی ارسال می‌کند:
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • برخی سرویس‌ها دارای یک فرمان help هستند که اطلاعاتی دربارهٔ فرمان‌هایی که می‌توان با آن سرویس استفاده کرد، ارائه می‌دهد. در اینجا چنین نیست. فرمان‌های SMTP که در مثال استفاده شده‌اند به شرح زیر هستند:
    • mail from: expéditeur، برای مشخص کردن آدرس ایمیل فرستنده
    • rcpt to: destinataire، برای مشخص کردن آدرس ایمیل گیرنده پیام. اگر گیرندگان متعددی وجود داشته باشند، دستور rcpt to: به تعداد لازم برای هر گیرنده تکرار می‌شود.
    • data، که به سرور SMTP سیگنال می‌دهد که پیام آماده ارسال است. همان‌طور که در پاسخ سرور نشان داده شده است، این شامل مجموعه‌ای از خطوط است که با خطی حاوی تنها یک نقطه پایان می‌یابد. یک پیام ممکن است دارای سربرگ‌هایی باشد که با یک خط خالی از بدنه پیام جدا شده‌اند. در مثال ما، با استفاده از کلمه کلیدی Subject: یک موضوع را گنجانده‌ایم:
  • پس از ارسال پیام، می‌توانید با استفاده از دستور quit به سرور اطلاع دهید که کارتان تمام شده است. سپس سرور اتصال réseau.Le را قطع می‌کند؛ رشتهٔ خواندن (read thread) می‌تواند این رویداد را تشخیص داده و متوقف شود.
  • سپس کاربر برای متوقف کردن خواندن دستورات صفحه‌کلید، کلید 'end' را تایپ می‌کند.

اگر ایمیل دریافتی را بررسی کنیم، موارد زیر را مشاهده می‌کنیم (آوت‌لوک):

Image

توجه داشته باشید که سرویس SMTP نمی‌تواند تشخیص دهد که فرستنده معتبر است یا خیر. بنابراین، فیلد from در یک پیام هرگز قابل اعتماد نیست. در این مورد، فرستنده «machin@univ-angers.fr» وجود نداشت. این کلاینت عمومی TCP می‌تواند به ما کمک کند تا پروتکل ارتباطی مورد استفاده توسط سرویس‌های اینترنتی را شناسایی کرده و از آنجا، کلاس‌های تخصصی برای کلاینت‌های این سرویس‌ها بسازیم. بیایید پروتکل ارتباطی سرویس POP (پروتکل دفتر پستی) را بررسی کنیم که به کاربران اجازه می‌دهد ایمیل‌های ذخیره‌شده خود را از روی یک سرور بازیابی کنند. این پروتکل بر روی پورت ۱۱۰ کار می‌کند.

dos>cltgen istia.univ-angers.fr 110
Commandes :
<-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting.
help
<-- -ERR Unknown command: "help".
user st
<-- +OK Password required for st.
pass monpassword
<-- +OK st has 157 visible messages (0 hidden) in 11755927 octets.
list
<-- +OK 157 visible messages (11755927 octets)
<-- 1 892847
<-- 2 171661
...
<-- 156 2843
<-- 157 2796
<-- .
retr 157
<-- +OK 2796 octets
<-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1])
<--     by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600;
<--     Mon, 13 May 2002 08:58:35 +0200
<-- Received: from jaume ([193.49.146.242])
<--     by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691;
<--     Mon, 13 May 2002 08:58:28 +0200 (CEST)
...
<-- ------------------------------------------------------------------------
<-- NOC-RENATER2                  Tl.  : 0800 77 47 95
<-- Fax : (+33) 01 40 78 64 00 ,  Email : noc-r2@cssi.renater.fr
<-- ------------------------------------------------------------------------
<--
<-- .
quit
<-- +OK Pop server at istia.univ-angers.fr signing off.
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

دستورهای اصلی به شرح زیر است:

  • user login، جایی که جزئیات ورود خود را برای ماشینی که ایمیل‌های شما را میزبانی می‌کند وارد می‌کنید
  • pass password، که در آن رمز عبور مرتبط با ورود قبلی را وارد می‌کنید
  • list، برای نمایش فهرستی از پیام‌ها که شماره و حجم آن‌ها را به بایت نشان می‌دهد
  • retr i، برای خواندن پیام شماره i
  • quit، برای پایان دادن به جلسه.

اکنون بیایید به پروتکل ارتباط بین یک کلاینت و یک وب‌سرور که معمولاً روی پورت ۸۰ اجرا می‌شود، نگاهی بیندازیم:

dos>cltgen istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
<--
<-- <head>
<-- <meta http-equiv="Content-Type"
<-- content="text/html; charset=iso-8859-1">
<-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title>
<-- </head>
....
<-- face="Verdana"> - Dernire mise  jour le <b>10 janvier 2002</b></font></p>
<-- </body>
<-- </html>
<--
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

یک کلاینت وب دستورات خود را بر اساس الگوی زیر به سرور ارسال می‌کند:

commande1
commande2
...
commanden
[ligne vide]

سرور وب تنها پس از دریافت خط خالی پاسخ می‌دهد. در این مثال، ما فقط از یک دستور استفاده کرده‌ایم:

GET /index.html HTTP/1.0

که از سرور می‌خواهد URL /index.html را ارسال کند و نشان می‌دهد که از نسخه ۱.۰ پروتکل HTTP استفاده می‌کند. جدیدترین نسخه این پروتکل ۱.۱ است. این مثال نشان می‌دهد که سرور با بازگرداندن محتوای فایل index.html پاسخ داده و سپس اتصال را قطع کرده است، همان‌طور که می‌توان دید نخ خواندن پاسخ‌ها متوقف شده است. قبل از ارسال محتوای فایل index.html، وب‌سرور مجموعه‌ای از سربرگ‌ها را ارسال کرد که با یک خط خالی پایان می‌یابد:

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>

خط <html> اولین خط فایل /index.html است. خطوط قبلی به عنوان سرآیندهای HTTP (پروتکل انتقال HyperText) شناخته می‌شوند. در اینجا وارد جزئیات این سربرگ‌ها نمی‌شویم، اما شایان ذکر است که کلاینت عمومی ما به آن‌ها دسترسی می‌دهد که ممکن است برای درک آن‌ها مفید باشد. به‌عنوان مثال، خط اول را در نظر بگیرید:

<-- HTTP/1.1 200 OK

نشان می‌دهد که سرور وب تماس‌گرفته پشتیبانی‌کننده پروتکل HTTP/1.1 است و با موفقیت فایل درخواست‌شده (200 OK) را پیدا کرده است، که در آن 200 یک کد پاسخ HTTP است. خطوط

<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html

به کلاینت اطلاع دهید که ۱۱٬۲۵۱ بایت متن را در HTML (زبان نشانه‌گذاری HyperText) دریافت خواهد کرد و پس از اتمام انتقال، اتصال بسته خواهد شد. پس در اینجا یک کلاینت TCP بسیار کاربردی داریم. در واقع، این کلاینت از قبل روی سیستم‌ها وجود دارد که با نام telnet شناخته می‌شود، اما نوشتن آن به صورت دستی جالب بود. برنامه کلی کلاینت TCP به شرح زیر است:


' فضاهای نام
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

' کلاس
Public Class clientTcpGénérique
   
    
   ' ویژگی‌های یک سرویس را به صورت پارامتر در قالب می‌گیرد
   ' پورت سرور
   ' به سرویس متصل می‌شود
   ' یک نخ ایجاد می‌کند تا دستورات تایپ‌شده روی صفحه‌کلید را بخواند
   'این‌ها به سرور ارسال خواهند شد
   ' یک نخ ایجاد می‌کند تا پاسخ‌ها را از سرور بخواند
   'این‌ها روی صفحه نمایش داده خواهند شد
   'کل فرایند با تایپ دستور «end» روی صفحه‌کلید پایان می‌یابد
  Public Shared Sub Main(ByVal args() As String)

    ' سینتکس
    Const syntaxe As String = "pg serveur port"

    ' تعداد آرگومان‌ها
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' نام سرور را وارد کنید
    Dim serveur As String = args(0)

    ' پورت باید یک عدد صحیح بزرگ‌تر از 0 باشد
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(1))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
    Dim client As TcpClient = Nothing
    'ممکن است مشکلاتی وجود داشته باشد
    Try
      ' اتصال به سرویس
      client = New TcpClient(serveur, port)
    Catch ex As Exception
      ' خطا
      Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message))
      ' پایان
      Return
        End Try
        ' رشته‌های خواندن/نوشتن ایجاد می‌شوند
        Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run))
        Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run))

        ' هر دو نخ راه‌اندازی شدند
        thSend.Start()
        thReceive.Start()

        'پایان نخ اصلی
        Return
    End Sub

    'نمایش خطاها
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        'خطا نمایش داده شد
        System.Console.Error.WriteLine(msg)
        ' پایان با خطا
        Environment.Exit(exitCode)
    End Sub
End Class

Public Class clientSend
    ' کلاسی که مسئول خواندن دستورات واردشده از طریق صفحه‌کلید است
    ' و ارسال آن‌ها به یک سرور از طریق یک کلاینت TCP که به سازنده پاس داده شده است
    Private client As TcpClient    ' le client tcp

    ' سازنده
    Public Sub New(ByVal client As TcpClient)
        ' کلاینت TCP راه‌اندازی می‌شود
        Me.client = client
    End Sub

    ' متد Runِ نخ
    Public Sub Run()

        ' داده‌های محلی
        Dim OUT As StreamWriter = Nothing        ' flux d'écriture réseau
        Dim commande As String = Nothing        ' commande lue au clavier
        ' مدیریت خطا
        Try
            ' ایجاد جریان نوشتن شبکه
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
            'حلقهٔ ورودی و ارسال فرمان
            Console.Out.WriteLine("Commandes : ")
            While True
                'خواندن دستوری که روی صفحه‌کلید تایپ شده است
                commande = Console.In.ReadLine().Trim()
                ' تمام شد؟
                If commande.ToLower() = "fin" Then
                    Exit While
                End If
                'ارسال فرمان به سرور
                OUT.WriteLine(commande)
            End While
        Catch ex As Exception
            ' خطا
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        'پایان – بستن استریم‌ها
        Try
            OUT.Close()
            client.Close()
        Catch
        End Try
        ' اعلام پایان نخ
        Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
    End Sub
End Class


Public Class clientReceive
    ' کلاسی که مسئول خواندن خطوط متنی است که برای یک 
    ' کلاینت TCP به سازنده پاس داده شده است
    Private client As TcpClient    ' le client tcp

    ' سازنده
    Public Sub New(ByVal client As TcpClient)
        ' سرور TCP ثبت شده است
        Me.client = client
    End Sub

    'سازنده
    ' متد Runِ نخ
    Public Sub Run()

        ' داده‌های محلی
        Dim [IN] As StreamReader = Nothing        ' flux lecture réseau
        Dim réponse As String = Nothing        ' réponse serveur
        ' مدیریت خطا
        Try
            ' ایجاد جریان خواندن شبکه
            [IN] = New StreamReader(client.GetStream())
            'حلقه برای خواندن خطوط متن از جریان IN
            While True
                'خواندن جریان شبکه
                réponse = [IN].ReadLine()
                'آیا جریان بسته است؟
                If réponse Is Nothing Then
                    Exit While
                End If
                'نمایش
                Console.Out.WriteLine(("<-- " + réponse))
            End While
        Catch ex As Exception
            ' خطا
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        'پایان – بستن استریم‌ها
        Try
            [IN].Close()
            client.Close()
        Catch
        End Try
        ' اعلام پایان نخ
        Console.Out.WriteLine("[fin du thread de lecture des réponses du serveur]")
    End Sub
End Class

9.4.4. یک سرور TCP عمومی

اکنون به یک سرور نگاه می‌کنیم

  • که دستورات ارسال‌شده توسط کلاینت‌های خود را روی صفحه نمایش می‌دهد
  • و در پاسخ، خطوط متنی را که کاربر از روی صفحه‌کلید تایپ می‌کند، برای آنها ارسال می‌کند. بنابراین این کاربر است که نقش سرور را ایفا می‌کند.

برنامه با دستور زیر اجرا می‌شود: srvgen portEcoute، که در آن portEcoute پورتی است که کلاینت‌ها باید به آن متصل شوند. سرویس کلاینت توسط دو نخ (thread) مدیریت می‌شود:

  • یک تِرد که منحصراً به خواندن خطوط متنی ارسال‌شده توسط کلاینت اختصاص دارد
  • یک تِرد که منحصراً به خواندن پاسخ‌های تایپ‌شده توسط کاربر اختصاص دارد. این تِرد با استفاده از دستور 'fin' سیگنال پایان اتصال با کلاینت را ارسال می‌کند.

سرور برای هر کلاینت دو تِرد ایجاد می‌کند. اگر n کلاینت وجود داشته باشد، در یک زمان 2n تِرد فعال خواهند بود. خودِ سرور هرگز متوقف نمی‌شود، مگر اینکه کاربر کلیدهای Ctrl-C را روی صفحه‌کلید فشار دهد. بیایید چند مثال را بررسی کنیم.

سرور روی پورت ۱۰۰ در حال اجرا است و ما از کلاینت عمومی برای ارتباط با آن استفاده می‌کنیم. پنجره کلاینت به این شکل است:

dos>cltgen localhost 100
Commandes :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]

خطوطی که با <-- شروع می‌شوند، ارسال‌شده از سرور به کلاینت هستند؛ سایر خطوط ارسال‌شده از کلاینت به سرور هستند. پنجرهٔ سرور به شرح زیر است:

dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]

خطوطی که با <-- شروع می‌شوند، ارسال‌شده از سمت کلاینت به سرور هستند. خطوطی که با N: علامت‌گذاری شده‌اند، ارسال‌شده از سمت سرور به کلاینت شماره N هستند. سرور بالا هنوز فعال است، در حالی که کلاینت ۱ کارش را تمام کرده است. یک کلاینت دوم برای همان سرور راه‌اندازی می‌شود:

dos>cltgen localhost 100
Commandes :
commande 3 du client 2
<-- réponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]

پنجرهٔ سرور سپس به این شکل به نظر می‌رسد:

dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- commande 3 du client 2
réponse 3 au client 2
2 : [fin du Thread de lecture des demandes du client 2]
fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C

اکنون بیایید با اجرای سرور عمومی خود روی پورت ۸۸، یک سرور وب را شبیه‌سازی کنیم:

dos>srvgen 88
Serveur générique lancé sur le port 88

حالا یک مرورگر را باز کرده و صفحه http://localhost:88/exemple.html را درخواست می‌کنیم. مرورگر سپس به پورت 88 روی ماشین localhost متصل شده و صفحه /exemple.html را درخواست می‌کند:

Image

حال بیایید به پنجره سرور خود نگاه کنیم:

dos>srvgen 88
Serveur générique lancé sur le port 88
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

بدین ترتیب می‌توانیم سربرگ‌های HTTP ارسال‌شده توسط مرورگر را مشاهده کنیم. این امر به ما امکان می‌دهد تا پروتکل HTTP را به تدریج کشف کنیم. در مثال قبلی، ما یک کلاینت وب ساختیم که تنها فرمان GET را ارسال می‌کرد. این کافی بود. در اینجا می‌بینیم که مرورگر اطلاعات دیگری را به سرور ارسال می‌کند. هدف از این کار، اطلاع‌رسانی به سرور در مورد نوع کلاینتی است که با آن سر و کار دارد. همچنین می‌توانیم ببینیم که هدرهای HTTP با یک خط خالی پایان می‌یابند. بیایید یک پاسخ برای کلاینت خود بسازیم. کاربر پشت کیبورد در این مورد، خود سرور است و می‌تواند پاسخ را به صورت دستی بسازد. بیایید پاسخ تولید شده توسط یک وب سرور را در مثال قبلی به یاد بیاوریم:

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>

بیایید سعی کنیم پاسخ مشابهی ارائه دهیم:

...
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
2 : HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
2 : <html>
2 :   <head><title>Serveur generique</title></head>
2 :   <body>
2 :     <center>
2 :       <h2>Reponse du serveur generique</h2>
2 :     </center>
2 :    </body>
2 : </html>
2 : fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des réponses du serveur au client 2]

خطوطی که با 2 شروع می‌شوند از سرور به کلاینت شماره 2 ارسال می‌شوند. فرمان fin ارتباط بین سرور و کلاینت را خاتمه می‌دهد. در پاسخ خود، ما خود را به سربرگ‌های زیر HTTP محدود کرده‌ایم:

HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :

ما اندازه فایلی را که قصد ارسال آن را داریم (Content-Length) مشخص نمی‌کنیم، بلکه صرفاً اعلام می‌کنیم که پس از ارسال آن، اتصال را بست خواهیم کرد (Connection: close). این برای مرورگر کافی است. وقتی مرورگر ببیند که اتصال بسته شده است، متوجه می‌شود که پاسخ سرور کامل است و صفحه‌ای را که برایش ارسال شده (HTML) نمایش می‌دهد. این صفحه به شرح زیر است:

2 : <html>
2 :   <head><title>Serveur generique</title></head>
2 :   <body>
2 :     <center>
2 :       <h2>Reponse du serveur generique</h2>
2 :     </center>
2 :    </body>
2 : </html>

سپس کاربر با وارد کردن دستور fin اتصال به کلاینت را قطع می‌کند. مرورگر در این صورت می‌داند که پاسخ سرور کامل است و می‌تواند آن را نمایش دهد:

Image

اگر، همانطور که در بالا نشان داده شده، دستور Affichage/Source را وارد کنیم تا ببینیم مرورگر چه چیزی دریافت کرده است، با خروجی زیر مواجه می‌شویم:

Image

یعنی دقیقاً همان چیزی که از سرور عمومی ارسال شده بود. کد سرور عمومی TCP به شرح زیر است:


' فضاهای نام
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

Public Class serveurTcpGénérique

    ' برنامهٔ اصلی
    Public Shared Sub Main(ByVal args() As String)

        ' روی پورت برای دریافت درخواست‌های مشتری گوش می‌دهد
        ' یک نخ ایجاد می‌کند تا درخواست‌های مشتری را بخواند
        'این‌ها روی صفحه نمایش داده خواهند شد
        ' یک نخ ایجاد می‌کند تا دستورات تایپ‌شده روی صفحه‌کلید را بخواند
        'این‌ها به‌عنوان پاسخ برای کلاینت ارسال خواهند شد
        'کل فرآیند زمانی پایان می‌یابد که دستور «end» از طریق صفحه‌کلید وارد شود

        Const syntaxe As String = "Syntaxe : pg port"

        'آیا آرگومانی وجود دارد
        If args.Length <> 1 Then
            erreur(syntaxe, 1)
        End If
        'این آرگومان باید یک عدد صحیح بزرگ‌تر از 0 باشد
        Dim port As Integer = 0
        Dim erreurPort As Boolean = False
        Dim E As Exception = Nothing
        Try
            port = Integer.Parse(args(0))
        Catch ex As Exception
            E = ex
            erreurPort = True
        End Try
        erreurPort = erreurPort Or port <= 0
        If erreurPort Then
            erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
        End If
        'سرویس شنود ایجاد می‌شود
        Dim ecoute As TcpListener = Nothing
        Dim nbClients As Integer = 0     ' nbre de clients traités
        Try
            ' سرویس ایجاد می‌شود
            ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
            ' ما آن را راه‌اندازی می‌کنیم
            ecoute.Start()
            ' نظارت
            Console.Out.WriteLine(("Serveur générique lancé sur le port " & port))

            'حلقه سرویس مشتری
            Dim client As TcpClient = Nothing
            While True        ' boucle infinie - sera arrêtée par Ctrl-C
                ' منتظر مشتری
                client = ecoute.AcceptTcpClient()

                ' سرویس توسط نخ‌های جداگانه پردازش می‌شود
                nbClients += 1
                ' رشته برای خواندن درخواست‌های مشتری
                Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
                ' رشته برای خواندن ورودی صفحه‌کلید کاربر
                Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run))

                ' هر دو نخ راه‌اندازی شده‌اند
                thSend.Start()
                thReceive.Start()
            End While
            ' به شنود درخواست‌ها بازمی‌گردد
        Catch ex As Exception
            'خطا گزارش می‌شود
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
    End Sub

    'نمایش خطاها
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' خطا نمایش داده شد
        System.Console.Error.WriteLine(msg)
        'خاتمه با خطا
        Environment.Exit(exitCode)
    End Sub
End Class


Public Class serveurSend
    'کلاس مسئول خواندن ورودی صفحه‌کلید
    ' و ارسال آن‌ها به کلاینت از طریق کلاینت TCP که به سازنده پاس داده شده است
    Private client As TcpClient    ' le client tcp
    Private numClient As Integer    ' n° de client

    ' سازنده
    Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
        ' کلاینت TCP ثبت شده است
        Me.client = client
        ' و شمارهٔ آن
        Me.numClient = numClient
    End Sub

    ' متد Run نخ
    Public Sub Run()

        ' داده‌های محلی
        Dim OUT As StreamWriter = Nothing        ' flux d'écriture réseau
        Dim réponse As String = Nothing        ' réponse lue au clavier
        ' ردیابی
        Console.Out.WriteLine(("Thread de lecture des réponses du serveur au client " & numClient & " lancé"))
        ' مدیریت خطا
        Try
            'ایجاد جریان نوشتن شبکه
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
            'حلقهٔ ورودی و ارسال فرمان
            While True
                'شناسایی مشتری
                Console.Out.Write((numClient & " : "))
                'پاسخ تایپ‌شده روی صفحه‌کلید را بخوانید
                réponse = Console.In.ReadLine().Trim()
                'تمام شد؟
                If réponse.ToLower() = "fin" Then
                    Exit While
                End If
                ' ارسال پاسخ به سرور
                OUT.WriteLine(réponse)
            End While
            ' پاسخ بعدی
        Catch ex As Exception
            ' خطا
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        'پایان – بستن استریم‌ها
        Try
            OUT.Close()
            client.Close()
        Catch
        End Try
        ' نشان‌دهنده پایان نخ
        Console.Out.WriteLine(("[fin du Thread de lecture des réponses du serveur au client " & numClient & "]"))
    End Sub
End Class

Public Class serveurReceive
    'کلاسی که مسئول خواندن خطوط متنی ارسال‌شده به سرور است 
    ' از طریق یک کلاینت TCP که به سازنده پاس داده شده است
    Private client As TcpClient     ' le client tcp
    Private numClient As Integer    ' n° de client

    ' سازنده
    Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
        ' کلاینت TCP ثبت شده است
        Me.client = client
        ' و شمارهٔ آن
        Me.numClient = numClient
    End Sub

    ' متد Runِ نخ
    Public Sub Run()
        ' داده‌های محلی
        Dim [IN] As StreamReader = Nothing        ' flux lecture réseau
        Dim réponse As String = Nothing        ' réponse serveur
        ' ردیابی
        Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lancé"))
        ' مدیریت خطا
        Try
            ' ایجاد جریان خواندن شبکه
            [IN] = New StreamReader(client.GetStream())
            'حلقه برای خواندن خطوط متن از جریان IN
            While True
                ' خواندن جریان شبکه
                réponse = [IN].ReadLine()
                'آیا جریان بسته است؟
                If réponse Is Nothing Then
                    Exit While
                End If
                'نمایش
                Console.Out.WriteLine(("<-- " + réponse))
            End While
        Catch ex As Exception
            ' خطا
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        'پایان – بستن استریم‌ها
        Try
            [IN].Close()
            client.Close()
        Catch
        End Try
        ' اعلام پایان نخ
        Console.Out.WriteLine(("[fin du Thread de lecture des demandes du client " & numClient & "]"))
    End Sub
End Class

9.4.5. یک کلاینت وب

در مثال قبلی، ما برخی از سربرگ‌های HTTP را که توسط یک مرورگر ارسال شده‌اند، دیدیم:

<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

ما قصد داریم یک کلاینت وب بنویسیم که یک URL به‌عنوان پارامتر به آن ارسال شود و متنی را که سرور ارسال کرده است روی صفحه نمایش دهد. فرض می‌کنیم که سرور از پروتکل HTTP نسخه 1.1 پشتیبانی می‌کند. از میان سربرگ‌های فهرست‌شده در بالا، تنها موارد زیر را استفاده خواهیم کرد:

<-- GET /exemple.html HTTP/1.1
<-- Host: localhost:88
<-- Connection: close
  • سربرگ اول نشان می‌دهد که کدام صفحه را می‌خواهیم
  • دومین سربرگ مشخص می‌کند که از کدام سرور پرس‌وجو می‌کنیم
  • سومین سربرگ نشان می‌دهد که می‌خواهیم سرور پس از پاسخ دادن، اتصال را ببندد.

اگر در مثال بالا، GET را با HEAD جایگزین کنیم، سرور فقط سربرگ‌های HTTP را برای ما ارسال می‌کند و نه صفحه HTML را.

کلاینت وب ما به صورت زیر فراخوانی خواهد شد: webclient URL cmd، که در آن URL استURL مورد نظر و «cmd» یکی از دو کلمه کلیدی GET یا HEAD است که مشخص می‌کند آیا ما فقط سربرگ‌ها (HEAD) را می‌خواهیم یا محتوای صفحه را نیز می‌خواهیم (GET). بیایید به یک مثال اولیه نگاه کنیم. ما سرور IIS را راه‌اندازی می‌کنیم و سپس کلاینت وب را روی همان ماشین اجرا می‌کنیم:

dos>clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private

پاسخ

HTTP/1.1 302 Object moved

به این معنی است که صفحهٔ درخواست‌شده جابه‌جا شده است (یعنی از URL). URL جدید، URL، توسط هدر Location: ارائه می‌شود:

Location: /IISSamples/Default/welcome.htm

اگر در فراخوانی کلاینت وب به جای HEAD از GET استفاده کنیم:

dos>clientweb http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:33:36 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/
Cache-control: private

<head><title>L'objet a changé d'emplacement</title></head>
<body><h1>L'objet a changé d'emplacement</h1>Cet objet peut être trouvé <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>

ما همان نتیجه را مانند HEAD می‌گیریم، به‌علاوه بدنه صفحه HTML. برنامه به شرح زیر است:


' فضاهای نام
Imports System
Imports System.Net.Sockets
Imports System.IO


Public Class clientWeb1

    'درخواست URL می‌کند
    ' محتویات خود را روی صفحه نمایش می‌دهد
    Public Shared Sub Main(ByVal args() As String)
        ' دستور زبان
        Const syntaxe As String = "pg URI GET/HEAD"

        ' تعداد آرگومان‌ها
        If args.Length <> 2 Then
            erreur(syntaxe, 1)
        End If
        ' URI درخواستی ثبت شد
        Dim URIstring As String = args(0)
        Dim commande As String = args(1).ToUpper()

        'اعتبار URI را بررسی می‌کند
        Dim uri As Uri = Nothing
        Try
            uri = New Uri(URIstring)
        Catch ex As Exception
            ' URI نادرست است
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try
        ' تأیید سفارش
        If commande <> "GET" And commande <> "HEAD" Then
            ' سفارش نادرست
            erreur("Le second paramètre doit être GET ou HEAD", 3)
        End If

        ' می‌توانیم ادامه دهیم
        Dim client As TcpClient = Nothing        ' le client
        Dim [IN] As StreamReader = Nothing        ' le flux de lecture du client
        Dim OUT As StreamWriter = Nothing        ' le flux d'écriture du client
        Dim réponse As String = Nothing        ' réponse du serveur
        Try
            ' اتصال به سرور
            client = New TcpClient(uri.Host, uri.Port)

            'ایجاد جریان‌های ورودی/خروجی مشتری TCP
            [IN] = New StreamReader(client.GetStream())
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True

            'درخواست URL – ارسال سربرگ‌ها HTTP
            OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
            OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
            OUT.WriteLine("Connection: close")
            OUT.WriteLine()
            'پاسخ را خواند
            réponse = [IN].ReadLine()
            While Not (réponse Is Nothing)
                ' پردازش پاسخ
                Console.Out.WriteLine(réponse)
                'پاسخ را بخوانید
                réponse = [IN].ReadLine()
            End While
            'کار تمام شد
            client.Close()
        Catch e As Exception
            ' استثنا را مدیریت کنید
            erreur(e.Message, 4)
        End Try
    End Sub

    ' نمایش خطاها
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' نمایش خطا
        System.Console.Error.WriteLine(msg)
        ' با خطا خاتمه می‌یابد
        Environment.Exit(exitCode)
    End Sub
End Class

تنها ویژگی جدید در این برنامه، استفاده از کلاس Uri است. برنامه یک URL (مکان‌یاب یکنواخت منابع) یا URI (شناسه‌ی یکنواخت منابع) را به شکل http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... دریافت می‌کند. کلاس Uri به ما امکان می‌دهد رشته URL را به اجزای مختلف آن تجزیه کنیم. یک شیء Uri از رشته URIstring که به‌عنوان پارامتر دریافت شده، ساخته می‌شود:


        'بررسی اعتبار URI
        Dim uri As Uri = Nothing
        Try
            uri = New Uri(URIstring)
        Catch ex As Exception
            ' URI نادرست
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try

اگر رشته URI که به‌عنوان پارامتر دریافت شده، یک URI معتبر نباشد (مانند عدم وجود پروتکل، سرور و غیره)، یک استثنا (exception) پرتاب می‌شود. این امکان را به ما می‌دهد تا اعتبار پارامتر دریافتی را بررسی کنیم. پس از ساختن شی URI، به عناصر مختلف آن دسترسی داریم. بنابراین، اگر شی uri از کد قبلی از رشته http://serveur:port/cheminPageHTML?param1=val1;param2=val2;... ساخته شده باشد، خواهیم داشت:

uri.Host=serveur, uri.Port=port, uri.Path=cheminPageHTML, uri.Query=param1=val1;param2=val2;..., uri.pathAndQuery= cheminPageHTML?param1=val1;param2=val2;..., uri.Scheme=http.

9.4.6. کلاینت وب رسیدگی به هدایت‌ها

کلاینت وب قبلی هیچ‌گونه هدایت احتمالی URL را که درخواست کرده بود، مدیریت نمی‌کند. کلاینت بعدی آن را مدیریت می‌کند.

  1. این خط اول هدرهای HTTP ارسال‌شده توسط سرور را می‌خواند تا بررسی کند آیا حاوی رشته «302 Object moved» است که نشان‌دهنده یک هدایت (redirection) است
  2. سپس سربرگ‌های بعدی را می‌خواند. اگر هدایت وجود داشته باشد، به دنبال خط «Location: url» می‌گردد که URL جدید صفحهٔ درخواست‌شده را در URL ارائه می‌دهد و این URL را یادداشت می‌کند.
  3. بقیه پاسخ سرور را نمایش می‌دهد. اگر هدایت وجود داشته باشد، مراحل ۱ تا ۳ با URL جدید تکرار می‌شوند. برنامه بیش از یک هدایت را قبول نمی‌کند. این محدودیت توسط یک ثابت تعریف شده است که قابل تغییر است.

در اینجا یک مثال آورده شده است:

dos>clientweb2 http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 11:38:55 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/
Cache-control: private

<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>

<--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm-->

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Connection: close
Date: Mon, 13 May 2002 11:38:55 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 16 Feb 1998 21:16:22 GMT
ETag: "0174e21203bbd1:978"
Content-Length: 4781

<html>

<head>
<title>Bienvenue dans le Serveur Web personnel</title>
</head>
....
</body>
</html>

برنامه به شرح زیر است:


' فضاهای نام
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic

'کلاس کلاینت وب
Public Class clientWeb
   
  'یک URL را درخواست می‌کند و محتویات آن را روی صفحه نمایش می‌دهد
  Public Shared Sub Main(ByVal args() As String)
    ' دستور زبان
    Const syntaxe As String = "pg URI GET/HEAD"

    ' تعداد آرگومان‌ها
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' URI درخواستی ثبت می‌شود
    Dim URIstring As String = args(0)
    Dim commande As String = args(1).ToUpper()

    'اعتبارسنجی URI
    Dim uri As Uri = Nothing
    Try
      uri = New Uri(URIstring)
    Catch ex As Exception
      ' URI نادرست است
      erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
    End Try 'catch
    ' تأیید سفارش
    If commande <> "GET" And commande <> "HEAD" Then
      ' سفارش نادرست
      erreur("Le second paramètre doit être GET ou HEAD", 3)
    End If

    'قابل پردازش است
    Dim client As TcpClient = Nothing ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
    Dim réponse As String = Nothing ' réponse du serveur
    Const nbRedirsMax As Integer = 1 ' pas plus d'une redirection acceptée
    Dim nbRedirs As Integer = 0 ' nombre de redirections en cours
    Dim premièreLigne As String ' 1ère ligne de la réponse
    Dim redir As Boolean = False ' indique s'il y a redirection ou non
    Dim locationString As String = "" ' la chaîne URI d'une éventuelle redirection
    ' بیان منظم برای یافتن هدایت URL
    Dim location As New Regex("^Location: (.+?)$") '

        ' مدیریت خطا
    Try
      'ممکن است چندین URL برای بررسی هدایت‌ها وجود داشته باشد
      While nbRedirs <= nbRedirsMax
        'اتصال به سرور
        client = New TcpClient(uri.Host, uri.Port)

        'ایجاد جریان‌های ورودی و خروجی کلاینت TCP
        [IN] = New StreamReader(client.GetStream())
        OUT = New StreamWriter(client.GetStream())
        OUT.AutoFlush = True

        'ارسال سربرگ‌ها HTTP برای درخواست URL
        OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
        OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
        OUT.WriteLine("Connection: close")
        OUT.WriteLine()

        ' اولین خط پاسخ را می‌خوانیم
        premièreLigne = [IN].ReadLine()
        ' اکوی صفحه
        Console.Out.WriteLine(premièreLigne)

        ' بازراست‌نمایی؟
        If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
          ' یک هدایت وجود دارد
          redir = True
          nbRedirs += 1
                End If

                ' سربرگ‌های زیر HTTP را تا زمانی که یک خط خالی یافت شود، که پایان سربرگ‌ها را نشان می‌دهد، بررسی می‌کند
                Dim locationFound As Boolean = False
                réponse = [IN].ReadLine()
                While réponse <> ""
                    ' پاسخ را نمایش دهید
                    Console.Out.WriteLine(réponse)
                    ' اگر ریدایرکت وجود داشته باشد، به دنبال هدر Location بگردید
                    If redir And Not locationFound Then
                        ' خط را با عبارت رابط `location` مقایسه کنید
                        Dim résultat As Match = location.Match(réponse)
                        If résultat.Success Then
                            ' اگر یافت شد، تغییر مسیر را ثبت می‌کنیم URL
                            locationString = résultat.Groups(1).Value
                            ' ما متوجه می‌شویم که یک تطابق یافت شده است
                            locationFound = True
                        End If
                    End If
                    ' خط بعدی
                    réponse = [IN].ReadLine()
                End While

                ' خطوط بعدی پاسخ
                Console.Out.WriteLine(réponse)
                réponse = [IN].ReadLine()
                While Not (réponse Is Nothing)
                    ' پاسخ را نمایش دهید
                    Console.Out.WriteLine(réponse)
                    ' خط بعدی
                    réponse = [IN].ReadLine()
                End While

                'بستن اتصال
                client.Close()
                ' آیا تمام شد؟
                If Not locationFound Or nbRedirs > nbRedirsMax Then
                    Exit While
                End If

                ' یک هدایت لازم است – در حال ساخت URI جدید
                URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString
                uri = New Uri(URIstring)
                'پیگیری
                Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf))
            End While
        Catch e As Exception
      ' پردازش استثنا
      erreur(e.Message, 4)
        End Try
    End Sub

    ' نمایش خطاها
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' نمایش خطا
        System.Console.Error.WriteLine(msg)
        ' پایان با خطا
        Environment.Exit(exitCode)
    End Sub
End Class

9.4.7. سرور محاسبه مالیات

ما در حال بازنگری تمرین IMPOTS هستیم که پیش‌تر به اشکال مختلف پوشش داده شده است. بیایید آخرین نسخه را مرور کنیم. یک کلاس مالیاتی ایجاد شده است. ویژگی‌های آن شامل سه آرایه عددی است:


Public Class impôt
    'داده‌های مورد نیاز برای محاسبه مالیات
    ' از منبع خارجی می‌آید
    Private limites(), coeffR(), coeffN() as double

این کلاس دو سازنده دارد:

  • یک سازنده که سه آرایهٔ دادهٔ مورد نیاز برای محاسبهٔ مالیات به آن پاس می‌شوند
     // تولیدکننده ۱
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
         ' آرایه‌های سه‌گانه محدوده را مقداردهی اولیه می‌کند، coeffR، coeffN، از
         'پارامترهای ارسال‌شده به سازنده
  • یک سازنده که نام DSN از پایگاه داده ODBC به آن ارسال می‌شود

    'سازندهٔ ۲
    Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        'سه آرایه مرزی coeffR و coeffN را از
        'محتویات جدول Timpots در پایگاه داده ODBC DSNimpots
        '، colLimites، colCoeffR و colCoeffN سه ستون این جدول هستند
        'ممکن است یک استثنا پرتاب کند

یک برنامه آزمایشی نوشته شده بود:

dos>vbc /r:impots.dll testimpots.vb

dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F

در اینجا، برنامهٔ تست و شیء impôt روی یک ماشین قرار داشتند. ما پیشنهاد می‌کنیم برنامهٔ تست و شیء impôt روی ماشین‌های مختلف قرار گیرند. ما یک برنامهٔ کلاینت-سرور خواهیم داشت که در آن شیء راه دور impôt به‌عنوان سرور عمل خواهد کرد. کلاس جدید ServeurImpots نام دارد و از کلاس impôt مشتق شده است:


Public Class ServeurImpots
    Inherits impôt

    'ویژگی‌ها
    Private portEcoute As Integer    ' le port d'écoute des demandes clients
    Private actif As Boolean    ' état du serveur

    ' سازنده
    Public Sub New(ByVal portEcoute As Integer, ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        MyBase.New(DSNimpots, Timpots, colLimites, colCoeffR, colCoeffN)
        ' به پورت گوش دادن توجه کنید
        Me.portEcoute = portEcoute
        'در حال حاضر غیرفعال است
        actif = False
        'یک تار (thread) ایجاد و راه‌اندازی می‌کند تا دستورات تایپ‌شده روی صفحه‌کلید را بخواند
        ' سرور با استفاده از این دستورات مدیریت خواهد شد
        Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin))
        threadLecture.Start()
    End Sub

تنها پارامتر جدید در سازنده، پورتی است که برای شنیدن درخواست‌های کلاینت روی آن گوش فرا می‌دهیم. سایر پارامترها مستقیماً به کلاس پایه impôt پاس داده می‌شوند. سرور مالیات از طریق دستورات وارد شده از طریق صفحه‌کلید کنترل می‌شود. بنابراین ما یک تِرد (thread) برای خواندن این دستورات ایجاد می‌کنیم. دو دستور ممکن وجود خواهد داشت: start برای راه‌اندازی سرویس، و stop برای متوقف کردن دائمی آن. متد admin که این دستورات را مدیریت می‌کند، به شرح زیر است:


    Public Sub admin()
        ' دستورات مدیریت سرور را که از طریق صفحه‌کلید وارد می‌شوند، می‌خواند
        'در یک حلقه بی‌نهایت
        Dim commande As String = Nothing
        While True
            ' پرامپت
            Console.Out.Write("Serveur d'impôts>")
            ' دستور را بخوانید
            commande = Console.In.ReadLine().Trim().ToLower()
            ' اجرای فرمان
            If commande = "start" Then
                'فعال؟
                If actif Then
                    'خطا
                    Console.Out.WriteLine("Le serveur est déjà actif")
                Else
                    ' راه‌اندازی سرویس گوش دادن
                    Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute))
                    threadEcoute.Start()
                End If
            Else
                If commande = "stop" Then
                    ' تمام نخ‌های اجرا پایان یافته‌اند
                    Environment.Exit(0)
                Else
                    'خطا
                    Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)")
                End If
            End If
        End While
    End Sub

اگر دستوری که از طریق صفحه‌کلید وارد شود start باشد، یک نخ که به درخواست‌های مشتری گوش می‌دهد راه‌اندازی می‌شود. اگر دستوری که وارد شود stop باشد، همه نخ‌ها متوقف می‌شوند. نخ گوش‌دادن متد ecoute را اجرا می‌کند:


    Public Sub ecoute()
        ' رشته در حال گوش دادن به درخواست‌های مشتری
        ' ایجاد سرویس شنود
        Dim ecoute As TcpListener = Nothing
        Try
            ' سرویس ایجاد می‌شود
            ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
            ' راه‌اندازی آن
            ecoute.Start()
            ' نظارت
            Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & portEcoute))

            'حلقه سرویس
            Dim liaisonClient As TcpClient = Nothing
            While True            ' boucle infinie
                'در انتظار یک مشتری
                liaisonClient = ecoute.AcceptTcpClient()
                ' سرویس توسط یک وظیفه دیگر مدیریت می‌شود
                Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
                threadClient.Start()
            End While
            'به شنود درخواست‌ها بازمی‌گردد
        Catch ex As Exception
            ' گزارش خطا
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
    End Sub

    ' نمایش خطاها
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' نمایش خطا
        System.Console.Error.WriteLine(msg)
        ' پایان با خطا
        Environment.Exit(exitCode)
    End Sub

یک سرور استاندارد TCP در پورت portEcoute در حال گوش دادن است. درخواست‌های کلاینت توسط متد Run یک شیء که دو پارامتر به آن ارسال می‌شود، پردازش می‌شوند:

  1. شیء TcpClient که برای دسترسی به کلاینت استفاده خواهد شد
  2. شیء impôt this، که دسترسی به متد this.calculer برای محاسبه مالیات را فراهم می‌کند.

' -------------------------------------------------------
'ارائه خدمات به مشتری سرور مالیاتی
Public Class traiteClientImpots

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie
    Private objImpôt As impôt     ' objet Impôt

    ' سازنده
    Public Sub New(ByVal liaisonClient As TcpClient, ByVal objImpôt As impôt)
        Me.liaisonClient = liaisonClient
        Me.objImpôt = objImpôt
    End Sub

روش Run درخواست‌های مشتری را پردازش می‌کند. این درخواست‌ها می‌توانند دو شکل داشته باشند:

  1. محاسبه مالیات متأهلین (بله/خیر) nbEnfants salaireAnnuel
  2. محاسبات نهایی

فرم ۱ امکان محاسبه مالیات را فراهم می‌کند؛ فرم ۲ اتصال کلاینت-سرور را قطع می‌کند.


    ' روش اجرا
    Public Sub Run()
        ' خدمات را به مشتری ارائه می‌دهد
        Try
            'جریان ورودی
            [IN] = New StreamReader(liaisonClient.GetStream())
            'جریان خروجی
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            'ارسال پیام خوش‌آمدگویی به مشتری
            OUT.WriteLine("Bienvenue sur le serveur d'impôts")

            'حلقه: خواندن درخواست/نوشتن پاسخ
            Dim demande As String = Nothing
            Dim champs As String() = Nothing            ' les éléments de la demande
            Dim commande As String = Nothing            ' la commande du client : calcul ou fincalculs
            demande = [IN].ReadLine()
            While Not (demande Is Nothing)
                ' تقسیم درخواست به فیلدها
                champs = Regex.Split(demande.Trim().ToLower(), "\s+")
                'دو درخواست پذیرفته شده: محاسبه و پایان محاسبه
                commande = champs(0)
                Dim erreur As Boolean = False
                If commande <> "calcul" And commande <> "fincalculs" Then
                    'خطای مشتری
                    OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).")
                End If
                If commande = "calcul" Then
                    calculerImpôt(champs)
                End If
                If commande = "fincalculs" Then
                    ' پیام خداحافظی به مشتری
                    OUT.WriteLine("Au revoir...")
                    'منابع آزاد شدند
                    Try
                        OUT.Close()
                        [IN].Close()
                        liaisonClient.Close()
                    Catch
                    End Try
                    'پایان
                    Return
                End If
                ' درخواست جدید
                demande = [IN].ReadLine()
            End While
        Catch e As Exception
            erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2)
        End Try
    End Sub

محاسبه مالیات توسط متد calculerImpôt انجام می‌شود که آرایه فیلدها را به عنوان پارامتر از درخواست کلاینت دریافت می‌کند. اعتبار درخواست بررسی شده و در صورت لزوم، مالیات محاسبه و به کلاینت بازگردانده می‌شود.


    'محاسبه مالیات
    Public Sub calculerImpôt(ByVal champs() As String)
        ' درخواست را پردازش می‌کند: محاسبه برای زوج‌های متأهل nbEnfants salaireAnnuel
        ' به فیلدها در جدول فیلدها تفکیک می‌شود
        Dim marié As String = Nothing
        Dim nbEnfants As Integer = 0
        Dim salaireAnnuel As Integer = 0

        ' اعتبار آرگومان‌ها
        Try
            'حداقل ۴ فیلد الزامی است
            If champs.Length <> 4 Then
                Throw New Exception
            End If
            ' متأهل
            marié = champs(1)
            If marié <> "o" And marié <> "n" Then
                Throw New Exception
            End If
            ' فرزندان
            nbEnfants = Integer.Parse(champs(2))
            ' حقوق
            salaireAnnuel = Integer.Parse(champs(3))
        Catch
            OUT.WriteLine(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel")
            'پایان
            Exit Sub
        End Try
        ' مالیات قابل محاسبه است
        Dim impot As Long = objImpôt.calculer(marié = "o", nbEnfants, salaireAnnuel)
        ' پاسخ را برای مشتری ارسال کنید
        OUT.WriteLine(impot.ToString)
    End Sub

    ' نمایش خطاها
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' نمایش خطا
        System.Console.Error.WriteLine(msg)
        ' پایان با خطا
        Environment.Exit(exitCode)
    End Sub

این کلاس توسط

dos>vbc /r:impots.dll /r:system.dll /t:library srvimpots.vb

که در آن impots.dll حاوی کد کلاس برای impôt است. یک برنامه آزمایشی ممکن است به این صورت باشد:


' فضاهای نام
Imports System
Imports System.IO
Imports Microsoft.VisualBasic

Public Class testServeurImpots
    Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN"

    ' برنامهٔ اصلی
    Public Shared Sub Main(ByVal args() As String)

        ' ۶ آرگومان لازم است
        If args.Length <> 6 Then
            erreur(syntaxe, 1)
        End If
        ' پورت باید یک عدد صحیح >0 باشد
        Dim port As Integer = 0
        Dim erreurPort As Boolean = False
        Dim E As Exception = Nothing
        Try
            port = Integer.Parse(args(0))
        Catch ex As Exception
            E = ex
            erreurPort = True
        End Try
        erreurPort = erreurPort Or port <= 0
        If erreurPort Then
            erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
        End If
        ' ایجاد سرور مالیات
        Try
            Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5))
        Catch ex As Exception
            'خطا
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
    End Sub

    ' نمایش خطاها
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' پیام خطا
        System.Console.Error.WriteLine(msg)
        'با خطا متوقف شد
        Environment.Exit(exitCode)
    End Sub
End Class

برنامهٔ آزمون با داده‌های لازم برای ساخت یک شیء ServeurImpots تأمین می‌شود و از آنجا این شیء را ایجاد می‌کند. این برنامهٔ آزمون توسط:

dos>vbc /r:srvimpots.dll /r:impots.dll testimpots.vb

در اینجا یک آزمون اولیه است:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

خط

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn

یک شیء ServeurImpots ایجاد می‌کند که هنوز در حال گوش دادن به درخواست‌های کلاینت نیست. دستور 'start' که از طریق صفحه‌کلید وارد می‌شود، این فرآیند گوش دادن را آغاز می‌کند. دستور 'stop' سرور را متوقف می‌کند. اکنون بیایید از یک کلاینت استفاده کنیم. ما از کلاینت عمومی که قبلاً ایجاد شده است استفاده خواهیم کرد. سرور راه‌اندازی می‌شود:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124

کلاینت عمومی در یک پنجره DOS دیگر اجرا می‌شود:

dos> clttcpgenerique localhost 124Commandes :
<-- Bienvenue sur le serveur d'impôts

می‌توانیم ببینیم که کلاینت با موفقیت پیام خوش‌آمدگویی را از سرور دریافت کرده است. ما چند دستور دیگر می‌فرستیم:

x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<--  syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

برای متوقف کردن، به پنجره سرور بازگردید:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop