8. برنامه TCP-IP
8.1. اطلاعات عمومی
8.1.1. پروتکلهای اینترنت
این بخش مقدمهای بر پروتکلهای ارتباطی اینترنت ارائه میدهد که به نام مجموعه پروتکل TCP/IP (پروتکل کنترل انتقال / پروتکل اینترنت) نیز شناخته میشود، که از نام دو پروتکل اصلی آن گرفته شده است. توصیه میشود خواننده پیش از پرداختن به توسعه برنامههای توزیعشده، درک کلی از نحوه کار شبکهها و بهویژه پروتکلهای TCP/IP داشته باشد.
متن زیر ترجمهای جزئی از گزیدهای است که در سند «محیط کاری LAN برای DOS – راهنمای مدیر» اثر NOVELL، سندی از اوایل دهه ۱۹۹۰، یافت میشود.
مفهوم کلی ایجاد شبکهای از کامپیوترهای ناهمگون از تحقیقاتی نشأت میگیرد که توسط DARPA (آژانس پروژههای تحقیقاتی پیشرفته دفاعی) در ایالات متحده انجام شده است. سازمان DARPA مجموعه پروتکلهای شناختهشده با نام TCP/IP را توسعه داد که امکان ارتباط ماشینهای ناهمگون با یکدیگر را فراهم میکند. این پروتکلها بر روی شبکهای به نام ARPAnet آزمایش شدند که بعداً به شبکه INTERNET تبدیل شد. پروتکلهای TCP/IP، قالبها و قوانینی را برای ارسال و دریافت تعریف میکنند که از معماری شبکه و سختافزار مورد استفاده مستقل هستند.
شبکهای که توسط DARPA طراحی شده و توسط پروتکلهای TCP/IP مدیریت میشود، یک شبکه سوئیچینگ بسته است. چنین شبکهای اطلاعات را در قالب بستههای کوچک به نام پکت منتقل میکند. بنابراین، اگر یک رایانه فایلی بزرگ را ارسال کند، آن را به بستههای کوچک تقسیم میکند که از طریق شبکه ارسال شده و در مقصد دوباره مونتاژ میشوند. TCP/IP قالب این پکتها را تعریف میکند، یعنی:
- منبع بسته
- مقصد
- طول
- نوع
8.1.2. مدل OSI
پروتکلهای TCP/IP به طور کلی از مدل شبکه باز معروف به OSI (مدل مرجع اتصال سیستمهای باز) که توسط ISO (سازمان بینالمللی استانداردها) تعریف شده است، پیروی میکنند. این مدل یک شبکه ایدهآل را توصیف میکند که در آن ارتباط بین ماشینها را میتوان با یک مدل هفتلایه نشان داد:

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

نقش لایههای مختلف به شرح زیر است:
انتقال بیتها را از طریق یک رسانه فیزیکی تضمین میکند. این لایه شامل تجهیزات پایانی پردازش داده (E.T.T.D)، مانند ترمینالها یا رایانهها، و همچنین تجهیزات پایاندهنده مدار داده (E.T.C.D)، مانند مودولاتورها/دمودولاتورها، چندراهیها و متمرکزکنندهها است. نکات کلیدی در این سطح عبارتند از: . انتخاب روش رمزگذاری اطلاعات (آنالوگ یا دیجیتال) . انتخاب حالت انتقال (همزمان یا ناهمزمان). | |
ویژگیهای فیزیکی لایه فیزیکی را پنهان میکند. خطاهای انتقال را تشخیص داده و اصلاح میکند. | |
مسیر دادههای ارسالی از طریق شبکه را مدیریت میکند. این به عنوان routage شناخته میشود: تعیین مسیری که دادهها باید برای رسیدن به مقصد طی کنند. | |
ارتباط بین دو برنامه را ممکن میسازد، در حالی که لایههای قبلی فقط ارتباط بین ماشینها را مجاز میدانستند. یکی از خدمات ارائه شده توسط این لایه، چندراهیسازی (multiplexing) است: لایه حملونقل میتواند از یک اتصال شبکه واحد (از ماشینی به ماشین دیگر) برای انتقال دادههای متعلق به چندین برنامه استفاده کند. | |
این لایه خدماتی را فراهم میکند که به یک برنامه امکان میدهد یک جلسه کاری را روی یک ماشین راه دور باز و حفظ کند. | |
هدف آن استانداردسازی نمایش دادهها در سراسر ماشینهای مختلف است. بنابراین، دادههایی که از ماشین A منشأ میگیرند، قبل از ارسال از طریق شبکه، توسط لایه Présentation ماشین A بر اساس یک قالب استاندارد «قالببندی» میشوند. پس از رسیدن به لایه Présentation در ماشین گیرنده B، که به لطف فرمت استاندارد آنها را شناسایی میکند، دادهها به شکلی متفاوت قالببندی میشوند تا برنامه در ماشین B بتواند آنها را تشخیص دهد. | |
در این سطح، برنامههایی را مییابیم که عموماً به کاربر نزدیک هستند، مانند ایمیل یا انتقال فایل. |
8.1.3. مدل TCP/IP
مدل OSI یک مدل ایدهآل است که تاکنون محقق نشده است. مجموعه پروتکلهای TCP/IP در شکل زیر به آن نزدیک میشود:

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

- شبکه پخش – یک دستگاه فرستنده اطلاعات را با آدرس دستگاه مقصد از طریق کابل ارسال میکند. سپس تمام دستگاههای متصل این اطلاعات را دریافت میکنند و تنها گیرنده مورد نظر آن را حفظ میکند.
- روش دسترسی به این صورت است: فرستندهای که مایل به ارسال داده است، به کابل گوش میدهد – سپس تشخیص میدهد که آیا سیگنال حامل وجود دارد یا خیر؛ وجود سیگنال حامل نشاندهنده این است که یک انتقال در حال انجام است. این تکنیک، دسترسی چندگانه با تشخیص حامل (CSMA) نامیده میشود. اگر هیچ موج حامل وجود نداشته باشد، فرستنده میتواند تصمیم بگیرد که نوبت به آن رسیده تا ارسال را آغاز کند. چندین فرستنده ممکن است همزمان این تصمیم را بگیرند. سیگنالهای ارسالشده با یکدیگر تداخل ایجاد میکنند: به این وضعیت تداخل (collision) گفته میشود. فرستنده این وضعیت را تشخیص میدهد: در حین ارسال بر روی کابل، به آنچه واقعاً از آن عبور میکند نیز گوش میدهد. اگر تشخیص دهد که اطلاعاتی که در امتداد کابل در حال حرکت است با آنچه خود ارسال کرده یکسان نیست، نتیجه میگیرد که یک برخورد رخ داده است و ارسال را متوقف میکند. سایر فرستندههایی که در حال ارسال بودند نیز همین کار را انجام میدهند. هر کدام پس از یک تأخیر تصادفی که بسته به فرستنده مربوطه متفاوت است، ارسال را از سر میگیرند. این تکنیک با نام CD (تشخیص برخورد) شناخته میشود. بنابراین، این روش دسترسی با نام CSMA/CD شناخته میشود.
- آدرسدهی ۴۸ بیتی. هر دستگاه یک آدرس دارد – که در اینجا به آن «آدرس فیزیکی» گفته میشود – که روی کارتی که آن را به کابل متصل میکند، چاپ شده است. این آدرس با نام Ethernet دستگاه شناخته میشود.
لایه شبکه
در این لایه، پروتکلهای IP، ICMP، ARP و RARP را مییابیم.
IP (پروتکل اینترنت) | انتقال بستهها بین دو گره شبکه |
ICMP (پروتکل پیام کنترلی اینترنت) | ICMP ارتباط بین برنامه پروتکل IP را در یک ماشین و در ماشین دیگر تسهیل میکند. بنابراین این یک پروتکل تبادل پیام در خود پروتکل IP است. |
ARP (پروتکل حل نشانی) | آدرس اینترنتی یک ماشین را به آدرس فیزیکی آن نگاشت میکند |
RARP (پروتکل حل نشانی معکوس) | آدرس فیزیکی یک دستگاه را به آدرس اینترنتی آن نگاشت میکند |
لایههای انتقال/جلسه
این لایه شامل پروتکلهای زیر است:
TCP (پروتکل کنترل انتقال) | اطمینان از تحویل قابلاعتماد اطلاعات بین دو کلاینت |
UDP (پروتکل دادگرام کاربر) | تحویل غیرقابلاطمینان اطلاعات بین دو کلاینت را تضمین میکند |
لایههای کاربردی/ارائهای/جلسه
پروتکلهای مختلفی در اینجا یافت میشوند:
شبیهساز ترمینال که ماشین A را قادر میسازد به ماشین B به عنوان یک ترمینال متصل شود | |
امکان انتقال فایل را فراهم میکند | |
امکان انتقال فایل را فراهم میکند | |
تبادل پیامها بین کاربران شبکه را امکانپذیر میسازد | |
یک نام ماشین را به آدرس اینترنتی آن تبدیل میکند | |
ایجاد شده توسط Sun MicroSystems، یک نمایش استاندارد و مستقل از ماشین از دادهها را مشخص میکند | |
که توسط Sun نیز تعریف شده است، یک پروتکل ارتباطی مستقل از لایه انتقال برای برنامههای کاربردی راه دور است. این پروتکل مهم است: برنامهنویس را از نیاز به دانستن جزئیات لایه انتقال رها میکند و برنامههای کاربردی را قابل حمل میسازد. این پروتکل بر اساس پروتکل XDR است. | |
که همچنین توسط Sun تعریف شده است؛ این پروتکل به یک دستگاه امکان میدهد تا سیستم فایل دستگاه دیگر را «ببیند». این پروتکل بر اساس پروتکل قبلی RPC است. |
8.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 یک تأیید را به ماشین مبدأ ارسال میکند؛ در غیر این صورت، سگمنت TCP رد میشود.
- تنها کاری که برای لایه TCP باقی میماند این است که بخش دادهٔ سگمنت را به برنامهای که در لایه بالاتر قرار است آن را دریافت کند، منتقل نماید.
8.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 باید یکتا باشند. نهادهای رسمی مسئول تخصیص آنها هستند. در واقع، این نهادها یک آدرس برای شبکههای محلی صادر میکنند، برای مثال 193.49.144.0 برای شبکه دانشکده علوم در آنژ. مدیر این شبکه سپس میتواند آدرسهای IP 193.49.144.1 تا 193.49.144.254 را به هر نحو که صلاح بداند تخصیص دهد. این آدرس معمولاً در یک فایل مشخص روی هر دستگاه متصل به شبکه ثبت میشود.
8.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^8 = 256 گره را فراهم میکند، اگر دو آدرس رزرو شده را حذف کنیم، تنها 254 آدرس معتبر باقی میماند.
8.1.5.2. پروتکلهای تبدیل آدرس اینترنتی <--> آدرس فیزیکی
دیدیم که وقتی دادهها از یک دستگاه به دستگاه دیگر منتقل میشوند، هنگام عبور از لایه IP، در بستهها (packets) جاسازی میشوند. این بستهها شکل زیر را دارند:

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

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

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

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

در مثال بالا:
- شبکه شماره ۱ آدرس اینترنتی 193.49.144.0 و شبکه شماره ۲ آدرس 193.49.145.0 را دارد.
- در شبکه شماره ۱، روتر آدرس ۱۹۳.۴۹.۱۴۴.۶ و در شبکه شماره ۲، آدرس ۱۹۳.۴۹.۱۴۵.۳ را دارد.
نقش روتر این است که بسته IP را که دریافت میکند—که در یک فریم فیزیکی متداول در شبکه شماره ۱ قرار دارد—به یک فریم فیزیکی تبدیل کند که بتواند از طریق شبکه شماره ۲ ارسال شود. اگر آدرس گیرنده بسته IP در شبکه شماره ۲ باشد، روتر بسته را مستقیماً برای آن ارسال میکند؛ در غیر این صورت، آن را به روتر دیگری که شبکه شماره ۲ را به شبکه شماره ۳ متصل میکند، و به همین ترتیب، ارسال خواهد کرد.
8.1.6.2. پیامهای خطا و کنترل
همچنین در لایه شبکه – و بنابراین در همان سطح پروتکل IP – پروتکل ICMP (پروتکل پیام کنترلی اینترنت) قرار دارد. این پروتکل برای ارسال پیامهایی در مورد عملکرد داخلی شبکه استفاده میشود: گرههای از کار افتاده، تراکم در یک روتر و غیره. پیامهای ICMP در بستههای IP جاسازی شده و از طریق شبکه ارسال میشوند. لایههای IP گرههای مختلف بر اساس پیامهای ICMP که دریافت میکنند، اقدامات مناسب را انجام میدهند. در نتیجه، خودِ یک برنامه هرگز این مسائل خاص شبکه را مشاهده نمیکند.
یک گره از اطلاعات ICMP برای بهروزرسانی جدولهای مسیریابی خود استفاده میکند.
8.1.7. لایه انتقال: پروتکلهای UDP و TCP
8.1.7.1. پروتکل UDP: پروتکل دادگرام کاربر
پروتکل UDP امکان تبادل غیرقابل اعتمادی دادهها بین دو نقطه را فراهم میکند؛ به عبارت دیگر، هیچ تضمینی وجود ندارد که یک بسته به مقصد خود به درستی هدایت شود. برنامه کاربردی، در صورت تمایل، میتواند این کار را خود مدیریت کند، برای مثال با منتظر ماندن برای دریافت رسید پس از ارسال یک پیام، پیش از ارسال پیام بعدی.
تا اینجای کار، در سطح شبکه، در مورد آدرسهای IP ماشینها بحث کردهایم. با این حال، روی یک ماشین واحد، فرآیندهای متفاوتی ممکن است همزمان وجود داشته باشند که همگی قادر به برقراری ارتباط هستند. بنابراین، هنگام ارسال یک پیام، لازم است نه تنها آدرس IP ماشین گیرنده، بلکه «نام» فرآیند گیرنده نیز مشخص شود. این نام در واقع یک عدد است که به آن شماره پورت گفته میشود. برخی اعداد برای برنامههای استاندارد رزرو شدهاند: برای مثال، پورت ۶۹ برای برنامه TFTP (پروتکل انتقال فایل ساده) رزرو شده است.
پکتهایی که توسط پروتکل UDP پردازش میشوند، همچنین به عنوان دیتاگرام شناخته میشوند. آنها شکل زیر را دارند:

این دیتاگرامها در بستههای IP و سپس در فریمهای فیزیکی قرار میگیرند.
8.1.7.2. پروتکل TCP: پروتکل کنترل انتقال
برای ارتباطات امن، پروتکل UDP ناکافی است: توسعهدهنده برنامه باید پروتکل خود را برای تأیید اینکه بستهها به درستی مسیریابی میشوند، طراحی کند.
پروتکل TCP (پروتکل انتقال کنترل) از این مشکلات جلوگیری میکند. ویژگیهای آن به شرح زیر است:
- فرآیندی که قصد ارسال داده را دارد، ابتدا یک اتصال با فرایندی که قرار است اطلاعات را دریافت کند، برقرار مینماید. این اتصال بین یک پورت در ماشین ارسالکننده و یک پورت در ماشین دریافتکننده برقرار میشود. بدین ترتیب یک مسیر مجازی بین دو پورت ایجاد میشود که منحصراً برای دو فرایندی که اتصال را برقرار کردهاند، رزرو شده است.
- تمام بستههای ارسالی توسط فرآیند مبدأ از این مسیر مجازی پیروی میکنند و به ترتیبی که ارسال شدهاند، میرسند؛ امری که در پروتکل UDP تضمین نشده بود، زیرا بستهها میتوانستند از مسیرهای متفاوتی عبور کنند.
- دادههای ارسالی پیوسته به نظر میرسند. فرآیند ارسال، دادهها را با نرخ خود ارسال میکند. این دادهها لزوماً بلافاصله ارسال نمیشوند: پروتکل TCP منتظر میماند تا دادهی کافی برای ارسال داشته باشد. این دادهها در ساختاری به نام سگمنت TCP ذخیره میشوند. به محض پر شدن این بخش، به لایه IP منتقل میشود، جایی که در یک بسته IP جاسازی خواهد شد.
- هر بخش ارسالشده توسط پروتکل TCP شمارهگذاری میشود. پروتکل دریافتکننده TCP بررسی میکند که بخشها را به ترتیب دریافت میکند. برای هر بخش که به درستی دریافت میشود، یک تأیید به فرستنده ارسال میکند.
- هنگامی که فرستنده این تأیید را دریافت میکند، فرآیند ارسال را مطلع میسازد. بنابراین، فرآیند ارسال میتواند تأیید کند که یک بخش به سلامت رسیده است، امری که با پروتکل UDP امکانپذیر نبود.
- اگر پروتکل TCP که یک سگمنت را ارسال کرده است، پس از گذشت مدت زمان معینی، تأیید دریافت را دریافت نکند، آن سگمنت را مجدداً ارسال میکند و بدین ترتیب کیفیت سرویس تحویل اطلاعات را تضمین مینماید.
- دورسوی مجازی ایجاد شده بین دو فرآیند ارتباطی، full-duplex است: این بدان معناست که اطلاعات میتواند در هر دو جهت جریان یابد. بنابراین، فرآیند مقصد میتواند حتی زمانی که فرآیند منبع به ارسال اطلاعات ادامه میدهد، تأییدها را ارسال کند. این امر به فرآیند منبع TCP اجازه میدهد تا چندین سگمنت را بدون انتظار برای تأیید ارسال کند. اگر پس از گذشت مدتی متوجه شود که برای یک سگمنت خاص با شماره n تأییدی دریافت نکرده است، ارسال سگمنتها را از همان نقطه از سر خواهد گرفت.
8.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» به این صورت خواهد بود:
در داخل دامنهها میتوان از نامهای نسبی استفاده کرد. بنابراین، در داخل دامنهٔ «fr» و خارج از دامنهٔ «univ-Angers»، میتوان به ماشین «Lagaffe» به صورت زیر اشاره کرد:
در نهایت، در داخل دامنه 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 است.
8.1.9. نتیجهگیری
در این مقدمه، برخی از جنبههای کلیدی پروتکلهای اینترنت را تشریح کردیم. برای بررسی عمیقتر این موضوع، خوانندگان ممکن است مایل باشند به کتاب عالی داگلاس کامر مراجعه کنند:
عنوان: TCP/IP: معماری، پروتکلها، کاربردها.
نویسنده: داگلاس COMER
ناشر: InterEditions
8.2. مدیریت آدرس شبکه در جاوا
8.2.1. تعریف
هر دستگاه در اینترنت با یک آدرس یا نام منحصربهفرد شناسایی میشود. در جاوا، این دو مفهوم توسط کلاس InetAddress مدیریت میشوند که برخی از متدهای آن در زیر فهرست شدهاند:
بازگرداندن ۴ بایت آدرس IP نمونه فعلی InetAddress | |
آدرس IP از نمونهٔ جاری InetAddress را برمیگرداند | |
نام اینترنتی نمونهٔ جاری InetAddress را بازمیگرداند | |
شناسه آدرس IP/نام اینترنتی نمونه فعلی InetAddress را ارائه میدهد | |
مثال InetAddress را برای ماشینی که با Host مشخص شده است ایجاد میکند. اگر Host ناشناخته باشد، یک استثنا تولید میکند. Host میتواند نام اینترنتی یک ماشین یا آدرس آن IP به شکل I1.I2.I3.I4 باشد. | |
مثال InetAddress را از ماشینی که برنامه حاوی این دستور روی آن در حال اجرا است، ایجاد میکند. |
8.2.2. چند مثال
8.2.2.1. شناسایی ماشین محلی
import java.net.*;
public class localhost{
public static void main (String arg[]){
try{
InetAddress adresse=InetAddress.getLocalHost();
byte[] IP=adresse.getAddress();
System.out.print("IP=");
int i;
for(i=0;i<IP.length-1;i++) System.out.print(IP[i]+".");
System.out.println(IP[i]);
System.out.println("adresse="+adresse.getHostAddress());
System.out.println("nom="+adresse.getHostName());
System.out.println("identité="+adresse);
} catch (UnknownHostException e){
System.out.println ("Erreur getLocalHost : "+e);
}// پایان تلاش
}// پایان main
}// پایان کلاس
نتایج اجرای برنامه به شرح زیر است:
هر ماشین یک آدرس داخلی به نام IP دارد که معادل 127.0.0.1 است. هنگامی که یک برنامه از این آدرس شبکه استفاده میکند، در واقع به همان ماشینی اشاره دارد که روی آن در حال اجراست. مزیت این آدرس آن است که نیازی به کارت شبکه ندارد. بنابراین، امکان تست برنامههای شبکه بدون اتصال به شبکه وجود دارد. راه دیگری برای اشاره به ماشین محلی، استفاده از نام «localhost» است.
8.2.2.2. شناسایی هر دستگاه
import java.net.*;
public class getbyname{
public static void main (String arg[]){
String nomMachine;
// بازیابی آرگومان
if(arg.length==0)
nomMachine="localhost";
else nomMachine=arg[0];
//تلاش برای بازیابی آدرس ماشین
try{
InetAddress adresse=InetAddress.getByName(nomMachine);
System.out.println("IP : "+ adresse.getHostAddress());
System.out.println("nom : "+ adresse.getHostName());
System.out.println("identité : "+ adresse);
} catch (UnknownHostException e){
System.out.println ("Erreur getByName : "+e);
}// پایان تلاش
}// پایان تابع main
}//پایان کلاس
با استفاده از دستور **java getbyname**، نتایج زیر به دست میآیند:
هنگام فراخوانی **java getbyname shiva.istia.univ-angers.fr**، خروجی زیر بازگردانده میشود:
هنگام فراخوانی متد جاوا getbyname www.ibm.com، خروجی زیر بازگردانده میشود:
8.3. ارتباطات TCP-IP
8.3.1. اطلاعات عمومی

وقتی یک برنامه کاربردی AppA روی ماشین A میخواهد با یک برنامه کاربردی AppB روی ماشین B در اینترنت ارتباط برقرار کند، باید چندین چیز را بداند:
- آدرس IP یا نام ماشین B
- شماره پورت مورد استفاده توسط برنامه AppB. این به آن دلیل است که ماشین B ممکن است میزبان برنامههای متعددی باشد که در اینترنت در حال اجرا هستند. هنگامی که اطلاعاتی را از شبکه دریافت میکند، باید بداند آن اطلاعات برای کدام برنامه در نظر گرفته شده است. برنامههای روی ماشین B از طریق رابطهایی که به نام پورتهای ارتباطی نیز شناخته میشوند، به شبکه دسترسی پیدا میکنند. این اطلاعات در بسته دریافتی توسط ماشین B گنجانده شده است تا بتواند به برنامه صحیح تحویل داده شود.
- پروتکلهای ارتباطی که ماشین B از آنها پشتیبانی میکند. در مطالعه ما، تنها از پروتکلهای TCP-IP استفاده خواهیم کرد.
- پروتکل گفتگوی پذیرفتهشده توسط برنامه AppB. در واقع، ماشینهای A و B با یکدیگر «صحبت» خواهند کرد. آنچه میگویند در پروتکلهای TCP-IP جاسازی خواهد شد. با این حال، هنگامی که در سوی دیگر این زنجیره، اپلیکیشن AppB اطلاعاتی را که توسط اپلیکیشن AppA ارسال شده است دریافت میکند، باید قادر به تفسیر آن باشد. این مشابه وضعیتی است که دو نفر، الف و ب، از طریق تلفن با یکدیگر ارتباط برقرار میکنند: مکالمه آنها توسط تلفن منتقل میشود. گفتار توسط تلفن الف به صورت سیگنال رمزگذاری شده، از طریق خطوط تلفن منتقل شده و به تلفن ب میرسد تا رمزگشایی شود. سپس شخص B صدای گفتار را میشنود. اینجاست که مفهوم پروتکل گفتگوی دوطرفه مطرح میشود: اگر A به فرانسوی صحبت کند و B آن زبان را نفهمد، A و B قادر به برقراری یک گفتگوی معنادار نخواهند بود.
بنابراین، دو برنامه در حال ارتباط باید بر سر نوع دیالوگی که استفاده خواهند کرد، توافق کنند. به عنوان مثال، دیالوگ با سرویسی به نام ftp با دیالوگ با سرویسی به نام pop یکسان نیست: این دو سرویس دستورات یکسانی را قبول نمیکنند. آنها پروتکل ارتباطی متفاوتی دارند.
8.3.2. ویژگیهای پروتکل TCP
در اینجا، ما فقط ارتباطات شبکهای را که از پروتکل حملونقل TCP استفاده میکنند، بررسی خواهیم کرد. بیایید ویژگیهای این پروتکل را به یاد بیاوریم:
- فرآیندی که قصد ارسال داده را دارد، ابتدا یک اتصال با فرایندی که قرار است اطلاعات را دریافت کند، برقرار میکند. این اتصال بین یک پورت در ماشین ارسالکننده و یک پورت در ماشین دریافتکننده برقرار میشود. بدین ترتیب یک مسیر مجازی بین دو پورت ایجاد میشود که منحصراً برای دو فرایندی که اتصال را برقرار کردهاند، رزرو خواهد شد.
- تمام بستههای ارسالی توسط فرآیند مبدأ از این مسیر مجازی پیروی میکنند و به ترتیبی که ارسال شدهاند، میرسند
- دادههای منتقلشده به صورت یک جریان پیوسته ظاهر میشوند. فرآیند ارسالکننده دادهها را با سرعت خود ارسال میکند. این دادهها لزوماً بلافاصله ارسال نمیشوند: پروتکل TCP منتظر میماند تا دادهی کافی برای ارسال داشته باشد. این دادهها در ساختاری به نام «بخش TCP» ذخیره میشوند. به محض اینکه این بخش پر شود، به لایه IP منتقل میشود، جایی که در یک بسته IP جاسازی خواهد شد.
- هر بخش ارسالشده توسط پروتکل TCP شمارهگذاری میشود. پروتکل دریافتکننده TCP بررسی میکند که بخشها را بهصورت متوالی دریافت میکند. برای هر بخش که بهدرستی دریافت شود، یک تأیید به فرستنده ارسال میکند.
- وقتی فرستنده این تأیید را دریافت میکند، فرایند ارسال را مطلع میسازد. بنابراین فرایند ارسال میتواند تأیید کند که یک بخش با موفقیت تحویل داده شده است.
- اگر پروتکل TCP که یک سگمنت را ارسال کرده است، پس از گذشت مدت زمان معینی، تأیید دریافت را دریافت نکند، آن سگمنت را مجدداً ارسال میکند و بدین ترتیب کیفیت سرویس تحویل اطلاعات را تضمین مینماید.
- مدار مجازی ایجاد شده بین دو فرآیند ارتباطی، full-duplex است: این بدان معناست که اطلاعات میتواند در هر دو جهت جریان یابد. بنابراین، فرآیند مقصد میتواند حتی زمانی که فرآیند مبدأ به ارسال اطلاعات ادامه میدهد، تأییدها را ارسال کند. این امر به پروتکل منبع TCP اجازه میدهد تا چندین بخش را بدون انتظار برای تأیید ارسال کند. اگر پس از مدتی متوجه شود که برای بخش خاصی با شماره n تأییدی دریافت نکرده است، ارسال بخشها را از آن نقطه از سر خواهد گرفت.
8.3.3. رابطهٔ کلاینت-سرور
ارتباطات از طریق اینترنت اغلب ناهمزمان (asymmetric) است: ماشین A یک اتصال را برای درخواست یک سرویس از ماشین B آغاز میکند و مشخص میکند که میخواهد یک اتصال با سرویس SB1 روی ماشین B برقرار کند. ماشین B یا آن را میپذیرد یا رد میکند. اگر درخواست پذیرفته شود، ماشین A میتواند درخواستهای خود را به سرویس SB1 ارسال کند. این درخواستها باید مطابق با پروتکل گفتگوی مورد توافق سرویس SB1 باشند. بدین ترتیب یک دیالوگ درخواست-پاسخ بین ماشین A، که به عنوان ماشین کلاینت شناخته میشود، و ماشین B، که به عنوان ماشین سرور شناخته میشود، برقرار میگردد. یکی از این دو طرف اتصال را قطع خواهد کرد.
8.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
8.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
8.3.6. کلاس Socket
8.3.6.1. Définition
ابزار پایهای که برنامههای در حال ارتباط از طریق اینترنت از آن استفاده میکنند، socket است. این کلمه انگلیسی به معنای «پریز برق» است. در اینجا معنای آن به «ساکت شبکه» گسترش یافته است. برای اینکه یک برنامه بتواند اطلاعات را از طریق اینترنت ارسال و دریافت کند، به یک سوکت شبکه، یعنی socket، نیاز دارد. این ابزار در ابتدا در نسخههای یونیکس در دانشگاه برکلی توسعه داده شد. از آن زمان به بعد به تمام سیستمهای یونیکس و همچنین به محیط ویندوز منتقل شده است. این کلاس همچنین در ماشینهای مجازی جاوا به دو شکل موجود است: کلاس Socket برای برنامههای کلاینت و کلاس ServerSocket برای برنامههای سرور. در اینجا برخی از سازندهها و متدهای کلاس Socket را توضیح میدهیم:
یک اتصال از راه دور به پورت port روی ماشین host باز میکند |
شماره پورت محلی مورد استفاده توسط ساکت را برمیگرداند | |||
شماره پورت راه دوری را که ساکت به آن متصل است بازمیگرداند | |||
آدرس محلی InetAddress را که ساکت به آن متصل است بازمیگرداند | |||
آدرس راه دور InetAddress را که ساکت به آن متصل است بازمیگرداند | |||
یک جریان ورودی برای خواندن دادههای ارسالشده توسط طرف دور بازمیگرداند | |||
یک جریان خروجی برای ارسال داده به شریک راه دور بازمیگرداند | |||
جریان ورودی ساکت را میبندد | |||
جریان خروجی ساکت را میبندد | |||
ساکت و جریانهای ورودی/خروجی آن را میبندد | |||
یک رشته را بازمیگرداند که نمایانگر ساکت است | |||
8.3.6.2. ایجاد اتصال با یک سرور
دیدیم که برای اینکه ماشین A یک اتصال به یک سرویس روی ماشین B باز کند، به دو اطلاعات نیاز دارد:
- آدرس IP یا نام ماشین B
- شماره پورت که سرویس مورد نظر روی آن در حال اجرا است
سازنده
یک سوکت ایجاد میکند و آن را به ماشین host روی پورت port متصل میکند. این سازنده در موارد مختلف استثنا (exception) پرتاب میکند:
- آدرس نادرست
- پورت نادرست
- درخواست رد شد
- …
ما باید این استثنا را مدیریت کنیم:
Socket sClient=null;
try{
sClient=new Socket(host,port);
} catch(Exception e){
//اتصال ناموفق بود – خطا را مدیریت کنید
….
}
اگر درخواست اتصال موفقیتآمیز باشد، به کلاینت یک پورت محلی برای ارتباط با ماشین B اختصاص داده میشود. پس از برقراری اتصال، این پورت را میتوان با استفاده از روش زیر بازیابی کرد:
اگر اتصال موفقیتآمیز باشد، همانطور که دیدیم، در سمت سرور نیز یک فرآیند دیگر روی چیزی که به آن پورت سرویس گفته میشود، در حال اجراست تا سرویس را مدیریت کند. این شماره پورت را میتوان با استفاده از روش زیر به دست آورد:
8.3.6.3. ارسال اطلاعات از طریق شبکه
میتوان با استفاده از روش زیر یک جریان نوشتن به ساکت – و در نتیجه به شبکه – به دست آورد:
هر چیزی که از طریق این استریم ارسال شود، در پورت سرویس ماشین سرور دریافت خواهد شد. بسیاری از برنامهها دارای دیالوگی هستند که از خطوط متنی تشکیل شده و هر خط با یک خطشکن (line break) پایان مییابد. بنابراین، متد println در چنین مواردی بسیار مفید است. سپس جریان خروجی OutputStream به جریان PrintWriter که از روش println استفاده میکند، تبدیل میشود. نوشتن در این جریان ممکن است یک استثنا ایجاد کند.
8.3.6.4. خواندن اطلاعات از شبکه
میتوانید با استفاده از روش زیر یک جریان خواندن از دادههای ورودی به ساکت دریافت کنید:
تمام دادههای خواندهشده از این استریم از پورت سرویس ماشین سرور میآید. برای برنامههایی که دیالوگشان شامل خطوط متنی است که با پایان خط خاتمه مییابند، توصیه میشود از متد readLine استفاده شود. برای این کار، جریان ورودی InputStream به جریان BufferedReader تبدیل میشود که دارای متد readLine() است. خواندن این جریان ممکن است یک استثنا (exception) ایجاد کند.
8.3.6.5. بستن اتصال
این کار با استفاده از متد زیر انجام میشود:
ممکن است این متد یک استثنا پرتاب کند. منابع مورد استفاده، بهویژه پورت شبکه، آزاد میشوند.
8.3.6.6. معماری کلاینت
اکنون اطلاعات لازم برای توصیف معماری پایه یک کلاینت وب را در اختیار داریم:
Socket sClient=null;
try{
// اتصال به سرویس در حال اجرا روی پورت P ماشین M
sClient=new Socket(M,P);
// ایجاد جریانهای ورودی و خروجی ساکت کلاینت
BufferedReader in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
PrintWriter out=new PrintWriter(sClient.getOutputStream(),true);
//حلقه درخواست-پاسخ
boolean fini=false;
String demande;
String réponse;
while (! fini){
// آمادهسازی درخواست
demande=…
//آن را ارسال کنید
out.println(demande);
//پاسخ را میخواند
réponse=in.readLine();
// پردازش پاسخ
…
}
// تمام شد
sClient.close();
} catch(Exception e){
// پردازش استثنا
….
}
ما برای سادهتر کردن مثال، تلاش نکردهایم انواع مختلف استثناءهای تولیدشده توسط سازنده Socket یا متدهای readline، getInputStream، getOutputStream و close را مدیریت کنیم. همه چیز در یک استثناء واحد ادغام شده است.
8.3.7. کلاس ServerSocket
8.3.7.1. Définition
این کلاس برای مدیریت ساکتهای سمت سرور طراحی شده است. در اینجا برخی از سازندهها و متدهای این کلاس را توضیح میدهیم:
ایجاد یک سوکت گوشدهنده روی پورت port | |
همان کار را انجام میدهد، اما اندازه صف را روی count تنظیم میکند، و حداکثر تعداد اتصالات کلاینت که در صورت مشغول بودن سرور هنگام دریافت اتصال کلاینت صفگذاری میشوند را روی c.a.d تنظیم میکند. |
شماره پورت در حال گوش دادن مورد استفاده توسط ساکت را برمیگرداند | |
آدرس محلی InetAddress را که ساکت به آن متصل است بازمیگرداند | |
سرور را در حالت انتظار برای اتصال قرار میدهد (عملیات مسدودکننده). با رسیدن یک اتصال از سوی کلاینت، یک ساکت بازمیگرداند که از طریق آن سرویس به کلاینت ارائه میشود. | |
ساکت و استریمهای ورودی/خروجی آن را میبندد | |
یک رشته را که نمایانگر ساکت است بازمیگرداند | |
ساکت سرویس را میبندد و منابع مرتبط با آن را آزاد میکند |
8.3.7.2. باز کردن سرویس
این کار با استفاده از دو سازنده انجام میشود:
port پورت گوشدادن سرویس است: پورتی که کلاینتها درخواستهای اتصال خود را به آن ارسال میکنند. count حداکثر اندازه صف سرویس است (به طور پیشفرض ۵۰)، که درخواستهای اتصال کلاینتها را که سرور هنوز به آنها پاسخ نداده است ذخیره میکند. وقتی صف پر شود، درخواستهای اتصال ورودی رد میشوند. هر دو سازنده یک استثنا پرتاب میکنند.
8.3.7.3. پذیرفتن درخواست اتصال
وقتی یک کلاینت درخواست اتصال را به پورت گوشدادن سرویس ارسال میکند، سرویس آن را با استفاده از متد زیر میپذیرد:
این متد یک نمونه از Socket را برمیگرداند: این ساکت سرویس است که سرویس از طریق آن ارائه میشود، معمولاً توسط یک وظیفه دیگر. این متد ممکن است یک استثنا پرتاب کند.
8.3.7.4. خواندن/نوشتن از طریق سوکت سرویس
از آنجایی که ساکت سرویس نمونهای از کلاس Socket است، لطفاً به بخشهای قبلی که این موضوع در آنها پوشش داده شده است مراجعه کنید.
8.3.7.5. مشتری را شناسایی کنید
پس از بهدستآوردن سوکت سرویس، میتوان مشتری را با استفاده از روش شناسایی کرد
متد کلاس Socket. این دسترسی را به آدرس IP و نام کلاینت فراهم میکند.
8.3.7.6. پایان دادن به سرویس
این کار با استفاده از متد انجام میشود
متد کلاس ServerSocket. این کار منابع در حال استفاده، بهویژه پورت گوشدادن را آزاد میکند. این متد ممکن است یک استثنا پرتاب کند.
8.3.7.7. معماری پایهٔ سرور
بر اساس آنچه گفته شد، ساختار اساسی یک سرور را میتوان به شرح زیر توصیف کرد:
SocketServer sEcoute=null;
try{
//سرویس باز شده است
int portEcoute=…
int maxConnexions=…
sEcoute=new ServerSocket(portEcoute,maxConnexions);
// پردازش درخواستهای اتصال
boolean fini=false;
Socket sService=null;
while( ! fini){
//در حال انتظار و پذیرش یک درخواست
sService=sEcoute.accept();
// خدمات توسط یک وظیفهٔ دیگر که سوکت سرویس به آن پاس میشود، مدیریت میشود
new Service(sService).start();
// انتظار درخواستهای اتصال را از سر میگیرد
}
// تمام شد – سرویس خاتمه یافت
sEcoute.close();
} catch (Exception e){
// استثناء رسیدگی میشود
…
}
کلاس Service یک thread است که ممکن است به این شکل به نظر برسد:
public class Service extends Thread{
Socket sService; // ساکت سرویس
// سازنده
public Service(Socket S){
sService=S;
}
//اجرا
public void run(){
try{
// ایجاد جریانهای ورودی-خروجی
BufferedReader in=new BufferedReader(new InputStreamReader(sService.getInputStream()));
PrinttWriter out=new PrintWriter(sService.getOutputStream(),true);
//حلقه درخواست-پاسخ
boolean fini=false;
String demande;
String réponse;
while (! fini){
// خواندن درخواست
demande=in.readLine();
// پردازش درخواست
…
//آمادهسازی پاسخ
réponse=…
//ارسال آن
out.println(réponse);
}
// تکمیل شد
sService.close();
} catch(Exception e){
// در حال رسیدگی به استثنا
….
}// تلاش
} //اجرا
8.4. کاربردها
8.4.1. سرور اکو
ما پیشنهاد میکنیم یک سرور اکو بنویسیم که با استفاده از دستور زیر از یک پنجره DOS راهاندازی شود:
سرور روی پورت مشخصشده بهعنوان پارامتر اجرا میشود. این برنامه بهسادگی درخواست ارسالشده توسط کلاینت را همراه با هویت کلاینت (IP+name) بازمیگرداند. این برنامه دو اتصال را در صف خود میپذیرد. این تمام اجزای یک سرور TCP را پوشش میدهد. برنامه به شرح زیر است:
// فراخوانی: serveurEcho پورت
// سرور اکو
//خطی را که کلاینت برای آن ارسال کرده است به کلاینت بازمیگرداند
import java.net.*;
import java.io.*;
public class serveurEcho{
public final static String syntaxe="Syntaxe : serveurEcho port";
public final static int nbConnexions=2;
// برنامهٔ اصلی
public static void main (String arg[]){
//آیا آرگومانی وجود دارد
if(arg.length != 1)
erreur(syntaxe,1);
// این آرگومان باید یک عدد صحیح بزرگتر از 0 باشد
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(arg[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// ساکت گوشدادن ایجاد میشود
ServerSocket ecoute=null;
try{
ecoute=new ServerSocket(port,nbConnexions);
} catch (Exception e){
erreur("Erreur lors de la création de la socket d'écoute ("+e+")",3);
}
// به دنبال
System.out.println("Serveur d'écho lancé sur le port " + port);
//حلقه سرویس
boolean serviceFini=false;
Socket service=null;
while (! serviceFini){
// منتظر یک کلاینت
try{
service=ecoute.accept();
} catch (IOException e){
erreur("Erreur lors de l'acceptation d'une connexion ("+e+")",4);
}
//شناسایی اتصال
try{
System.out.println("Client ["+identifie(service.getInetAddress())+","+
service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
+ "," + service.getLocalPort() + "]");
} catch (Exception e) {
erreur("identification liaison",1);
}
// سرویس توسط وظیفه دیگری انجام میشود
new traiteClientEcho(service).start();
}// پایان حلقه while
}//پایان برنامه اصلی
// نمایش خطاها
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
// شناسایی میکند
private static String identifie(InetAddress Host){
// شناسایی میزبان
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// پایان کلاس
// خدمات را به یک کلاینت سرور اکو ارائه میدهد
class traiteClientEcho extends Thread{
private Socket service; // ساکت سرویس
private BufferedReader in; //جریان ورودی
private PrintWriter out; // جریان خروجی
// سازنده
public traiteClientEcho(Socket service){
this.service=service;
}
// متد اجرا
public void run(){
// ایجاد استریمهای ورودی و خروجی
try{
in=new BufferedReader(new InputStreamReader(service.getInputStream()));
} catch (IOException e){
erreur("Erreur lors de la création du flux déentrée de la socket de service ("+e+")",1);
}//پایان try
try{
out=new PrintWriter(service.getOutputStream(),true);
} catch (IOException e){
erreur("Erreur lors de la création du flux de sortie de la socket de service ("+e+")",1);
}//پایان try
//شناسه اتصال به کلاینت ارسال میشود
try{
out.println("Client ["+identifie(service.getInetAddress())+","+
service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
+ "," + service.getLocalPort() + "]");
} catch (Exception e) {
erreur("identification liaison",1);
}
//حلقه درخواست/پاسخ خواندن/نوشتن
String demande,reponse;
try{
// سرویس زمانی متوقف میشود که کلاینت نشانگر پایان فایل را ارسال کند
while ((demande=in.readLine())!=null){
// اکوی درخواست
reponse="["+demande+"]";
out.println(reponse);
// سرویس زمانی متوقف میشود که کلاینت «end» را ارسال کند
if(demande.trim().toLowerCase().equals("fin")) break;
}//پایان حلقه
} catch (IOException e){
erreur("Erreur lors des échanges client/serveur ("+e+")",3);
}// پایان try
// بستن ساکت
try{
service.close();
} catch (IOException e){
erreur("Erreur lors de la fermeture de la socket de service ("+e+")",2);
}//پایان تلاش
}// پایان اجرا
//نمایش خطاها
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}//پایان خطا
// شناسایی
private String identifie(InetAddress Host){
//شناسایی میزبان
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// پایان کلاس
دو کلاس مورد نیاز برای سرویس در یک فایل منبع واحد ترکیب شدهاند. تنها یکی از آنها، یعنی کلاسی که شامل تابع main است، دارای ویژگی public میباشد. ساختار سرور مطابق با معماری کلی سرورهای TCP است. متدی (identifie) برای شناسایی اتصال بین سرور و کلاینت اضافه شده است. در اینجا برخی از نتایج آورده شده است:
سرور با استفاده از دستور راه اندازی میشود
سپس پیام زیر را در پنجره کنترل نمایش میدهد:
برای آزمایش این سرور، از برنامه telnet استفاده میکنیم که هم روی یونیکس و هم روی ویندوز در دسترس است. تلنت یک کلاینت جهانی TCP است که برای همه سرورهایی که در دیالوگ خود خطوط متنی را که با کاراکتر پایان خط خاتمه مییابند میپذیرند، مناسب است. این مورد در سرور اکو ما صادق است. ما یک کلاینت اولیه telnet را در ویندوز (در این مثال ۲۰۰۰) با تایپ telnet در یک پنجره DOS راهاندازی میکنیم:
DOS>telnet
Microsoft (R) Windows 2000 (TM) version 5.00 (numéro 2195)
Client Telnet Microsoft
Client Telnet numéro 5.00.99203.1
Le caractère d'échappement est 'CTRL+$'
Microsoft Telnet> help
Les commandes peuvent être abrégées. Les commandes prises en charge sont :
close ferme la connexion en cours
display affiche les paramètres d'opération
open ouvre une connexion à un site
quit quitte telnet
set définit les options (entrez 'set ?' pour afficher la liste)
status affiche les informations d'état
unset annule les options (entrez 'unset ?' pour afficher la liste)
? ou help affiche des informations d'aide
Microsoft Telnet> set ?
NTLM Active l'authentification NTLM.
LOCAL_ECHO Active l'écho local.
TERM x (où x est ANSI, VT100, VT52 ou VTNT))
CRLF Envoi de CR et de LF
Microsoft Telnet> set local_echo
Microsoft Telnet> open localhost 187
بهطور پیشفرض، برنامه telnet دستورات تایپشده روی صفحهکلید را بازتاب نمیدهد. برای فعالسازی این بازتاب، دستور زیر را وارد کنید:
برای برقراری ارتباط با سرور، با مشخص کردن پورت سرویس اکو (187) و آدرس ماشینی که روی آن اجرا میشود (localhost)، دستور زیر را وارد کنید:
در پنجره DOS کلاینت، پیام زیر نمایش داده میشود:
در پنجرهٔ سرور، پیام زیر ظاهر میشود:
Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
در اینجا tahe و localhost به یک ماشین واحد اشاره دارند. در پنجرهٔ کلاینت telnet میتوانید خطوط متنی را تایپ کنید. سرور آنها را بازتاب میدهد:
Client [127.0.0.1,tahe,1059] connectÚ au serveur [127.0.0.1,tahe,187]
je suis là
[je suis là]
au revoir
[au revoir]
توجه کنید که پورت کلاینت (1059) به درستی تشخیص داده شده است، اما پورت سرویس (187) با پورت گوشدادن (187) یکسان است که این غیرمنتظره است. در واقع، انتظار میرود پورت سوکت سرویس به جای پورت گوشدادن به دست آید. باید بررسی کنیم که آیا نتایج مشابهی را در یونیکس به دست میآوریم. اکنون، بیایید یک کلاینت دوم را راهاندازی کنیم: telnet. پنجره سرور اکنون نمایش میدهد:
Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
در پنجرهٔ کلاینت دوم، میتوانید خطوط متنی را نیز تایپ کنید:
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
ligne1
[ligne1]
ligne2
[ligne2]
بنابراین میتوانیم ببینیم که سرور اکو میتواند همزمان به چندین مشتری خدمترسانی کند. مشتریان telnet را میتوان با بستن پنجره DOS که در آن در حال اجرا هستند، خاتمه داد.
8.4.2. یک کلاینت جاوا برای سرور اکو
در بخش قبلی، از یک کلاینت telnet برای آزمایش سرویس اکو استفاده کردیم. اکنون کلاینت خود را خواهیم نوشت:
//فراخوانی: clientEcho پورت ماشین
// سرور اکو کلاینت
//خطوط را به سرور ارسال میکند که آنها را بازتاب میدهد
import java.net.*;
import java.io.*;
public class clientEcho{
public final static String syntaxe="Syntaxe : clientEcho machine port";
// برنامهٔ اصلی
public static void main (String arg[]){
//آیا دو آرگومان وجود دارد
if(arg.length != 2)
erreur(syntaxe,1);
// اولین آرگومان باید نام یک ماشین موجود باشد
String machine=arg[0];
InetAddress serveurAddress=null;
try{
serveurAddress=InetAddress.getByName(machine);
} catch (Exception e){
erreur(syntaxe+"\nMachine "+machine+" inaccessible (" + e +")",2);
}
// پورت باید یک عدد صحیح بزرگتر از 0 باشد
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(arg[1]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\nPort incorrect ("+E+")",3);
// اتصال به سرور
Socket sClient=null;
try{
sClient=new Socket(machine,port);
} catch (Exception e){
erreur("Erreur lors de la création de la socket de communication ("+e+")",4);
}
//اتصال شناسایی شد
try{
System.out.println("Client : Client ["+identifie(InetAddress.getLocalHost())+","+
sClient.getLocalPort()+"] connecté au serveur [" + identifie (sClient.getInetAddress())
+ "," + sClient.getPort() + "]");
} catch (Exception e) {
erreur("identification liaison ("+e+")",5);
}
//ایجاد جریان برای خواندن خطوط تایپشده روی صفحهکلید
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
erreur("Création du flux d'entrée clavier ("+e+")",6);
}
//ایجاد جریان ورودی مرتبط با ساکت کلاینت
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
} catch (Exception e){
erreur("Création du flux d'entrée de la socket client("+e+")",7);
}
//ایجاد جریان خروجی مرتبط با ساکت کلاینت
PrintWriter out=null;
try{
out=new PrintWriter(sClient.getOutputStream(),true);
} catch (Exception e){
erreur("Création du flux de sortie de la socket ("+e+")",8);
}
//حلقه درخواست-پاسخ
boolean serviceFini=false;
String demande=null;
String reponse=null;
// خواندن پیامی که بلافاصله پس از اتصال توسط سرور ارسال شده است
try{
reponse=in.readLine();
} catch (IOException e){
erreur("Lecture réponse ("+e+")",4);
}
//نمایش پاسخ
System.out.println("Serveur : " +reponse);
while (! serviceFini){
// خواندن یک خط تایپشده روی صفحهکلید
System.out.print("Client : ");
try{
demande=IN.readLine();
} catch (Exception e){
erreur("Lecture ligne ("+e+")",9);
}
//ارسال درخواست از طریق شبکه
try{
out.println(demande);
} catch (Exception e){
erreur("Envoi demande ("+e+")",10);
}
// انتظار برای/خواندن پاسخ
try{
reponse=in.readLine();
} catch (IOException e){
erreur("Lecture réponse ("+e+")",4);
}
// نمایش پاسخ
System.out.println("Serveur : " +reponse);
// آیا تمام شد؟
if(demande.trim().toLowerCase().equals("fin")) serviceFini=true;
}
// تمام شد
try{
sClient.close();
} catch(Exception e){
erreur("Fermeture socket ("+e+")",11);
}
}// اصلی
// نمایش خطاها
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
// شناسایی
private static String identifie(InetAddress Host){
//شناسه میزبان
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}//پایان کلاس
ساختار این کلاینت مطابق با معماری کلی کلاینتهای tcp است. در اینجا، استثناءهای مختلف احتمالی بهصورت جداگانه مدیریت شدهاند که برنامه را پیچیدهتر میکند. نتایج بهدستآمده از آزمایش این کلاینت به شرح زیر است:
Client : Client [127.0.0.1,tahe,1045] connecté au serveur [127.0.0.1,localhost,187]
Serveur : Client [127.0.0.1,localhost,1045] connectÚ au serveur [127.0.0.1,tahe,187]
Client : 123
Serveur : [123]
Client : abcd
Serveur : [abcd]
Client : je suis là
Serveur : [je suis là]
Client : fin
Serveur : [fin]
خطهایی که با Client شروع میشوند، ارسالشده توسط کلاینت هستند و خطهایی که با Serveur شروع میشوند، بازتابشده توسط سرور هستند.
8.4.3. یک کلاینت عمومی TCP
بسیاری از سرویسهایی که در روزهای اولیه اینترنت ایجاد شدهاند، بر اساس مدل سرور اکو که قبلاً مورد بحث قرار گرفت، عمل میکنند: ارتباط کلاینت-سرور از طریق تبادل خطوط متن انجام میشود. ما قصد داریم یک کلاینت عمومی TCP بنویسیم که به شکل زیر اجرا خواهد شد: java cltTCPgenerique server port
این کلاینت TCP به پورت port روی سرور serveur متصل خواهد شد. پس از انجام این کار، دو نخ ایجاد خواهد کرد:
- یک تِرد مسئول خواندن دستورات تایپشده روی صفحهکلید و ارسال آنها به سرور
- یک تِرد مسئول خواندن پاسخهای سرور و نمایش آنها روی صفحه نمایش
چرا دو تِرد، در حالی که در برنامه قبلی این کار ضروری نبود؟ در آن برنامه، پروتکل گفتگو ثابت بود: کلاینت یک خط میفرستاد و سرور با یک خط پاسخ میداد. هر سرویس پروتکل خاص خود را دارد و ممکن است موقعیتهای زیر نیز پیش بیاید:
- کلاینت باید قبل از دریافت پاسخ، چندین خط متن ارسال کند
- پاسخ یک سرور ممکن است شامل چندین خط متن باشد
بنابراین، حلقه مربوط به ارسال یک خط به سرور و دریافت یک خط از سرور همیشه مناسب نیست. بنابراین ما دو حلقه مجزا ایجاد خواهیم کرد:
- یک حلقه برای خواندن دستورات تایپشده روی صفحهکلید جهت ارسال به سرور. کاربر پایان دستورات را با کلمه کلیدی fin اعلام خواهد کرد.
- یک حلقه برای دریافت و نمایش پاسخهای سرور. این یک حلقه بینهایت خواهد بود که تنها زمانی متوقف میشود که سرور اتصال شبکه را قطع کند یا کاربر فرمان fin را تایپ کند.
برای جدا نگه داشتن این دو حلقه، به دو نخ (thread) مستقل نیاز داریم. بیایید نگاهی به یک مثال اجرایی بیندازیم که در آن کلاینت عمومی TCP ما به سرویسی به نام SMTP (پروتکل انتقال SendMail) متصل میشود. این سرویس مسئول مسیریابی ایمیل به گیرندگان آن است. این سرویس بر روی پورت ۲۵ کار میکند و از یک پروتکل گفتگوی مبتنی بر متن استفاده میکند.
Dos>java clientTCPgenerique 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 هنگام اتصال یک کلاینت به آن، یک پیام خوشآمدگویی ارسال میکند:
- برخی سرویسها دارای یک فرمان help هستند که اطلاعاتی دربارهٔ فرمانهایی که میتوان با سرویس استفاده کرد، ارائه میدهد. در اینجا چنین نیست. فرمانهای SMTP که در مثال استفاده شدهاند به شرح زیر هستند:
- mail from: expéditeur، برای مشخص کردن آدرس ایمیل فرستنده
- rcpt to: destinataire، برای مشخص کردن آدرس ایمیل گیرنده پیام. اگر گیرندگان متعددی وجود داشته باشند، دستور rcpt to: به تعداد لازم برای هر گیرنده تکرار میشود.
- data، که به سرور SMTP سیگنال میدهد که پیام در شرف ارسال است. همانطور که در پاسخ سرور نشان داده شده است، این شامل مجموعهای از خطوط است که با خطی حاوی فقط یک نقطه پایان مییابد. یک پیام ممکن است دارای سربرگهایی باشد که با یک خط خالی از بدنه پیام جدا شدهاند. در مثال ما، با استفاده از کلمه کلیدی Subject: یک موضوع را گنجاندهایم:
- پس از ارسال پیام، میتوانید با استفاده از دستور 'quit' به سرور اطلاع دهید که کارتان تمام شده است. سپس سرور اتصال شبکه را قطع میکند. نخ خواندن (read thread) میتواند این رویداد را تشخیص داده و متوقف شود.
- سپس کاربر برای متوقف کردن نخ خواندن دستورات تایپشده روی صفحهکلید، کلید 'end' را فشار میدهد.
اگر ایمیل دریافتی را بررسی کنیم، موارد زیر را مشاهده میکنیم (آوتلوک):

توجه داشته باشید که سرویس SMTP نمیتواند تشخیص دهد که فرستنده معتبر است یا خیر. بنابراین، فیلد from در یک پیام هرگز قابل اعتماد نیست. در این مورد، فرستنده «machin@univ-angers.fr» وجود نداشت.
این کلاینت عمومی TCP میتواند به ما کمک کند تا پروتکل ارتباطی مورد استفاده توسط سرویسهای اینترنتی را شناسایی کرده و از آنجا، کلاسهای تخصصی برای کلاینتهای این سرویسها بسازیم. بیایید پروتکل ارتباطی سرویس POP (پروتکل دفتر پستی) را بررسی کنیم که به کاربران اجازه میدهد ایمیلهای خود را که در سرور ذخیره شدهاند، بازیابی کنند. این پروتکل بر روی پورت 110 کار میکند.
Dos> java clientTCPgenerique 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]
دستورهای اصلی به شرح زیر هستند:
- کاربر login، جایی که جزئیات ورود خود را برای ماشینی که میزبان ایمیلهای شماست وارد میکنید
- pass password، جایی که رمز عبور مربوط به ورود قبلی را وارد میکنید
- فهرست، برای دریافت فهرستی از پیامها در قالب شماره، اندازه به بایت
- retr i، برای خواندن پیام شمارهٔ i
- quit، برای پایان دادن به جلسه.
اکنون بیایید به پروتکل ارتباطی بین یک کلاینت و یک وبسرور، که معمولاً روی پورت ۸۰ کار میکند، نگاهی بیندازیم:
Dos> java clientTCPgenerique 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]
یک کلاینت وب دستورات خود را بر اساس الگوی زیر به سرور ارسال میکند:
سرور وب تنها پس از دریافت خط خالی پاسخ میدهد. در این مثال، ما فقط از یک دستور استفاده کردهایم:
که از سرور میخواهد 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) را پیدا کرده است، که در آن 200 یک کد پاسخ HTTP است. خطوط
به کلاینت اطلاع میدهد که ۱۱٬۲۵۱ بایت حاوی متن در HTML (زبان نشانهگذاری HyperText) دریافت خواهد کرد و اینکه پس از ارسال دادهها، اتصال بسته خواهد شد.
پس در اینجا یک کلاینت TCP بسیار کاربردی داریم. این کلاینت بیشک کمتر از برنامه telnet که قبلاً استفاده کردیم انجام میدهد، اما نوشتن آن به دست خودمان جالب بود. برنامه کلی کلاینت TCP به شرح زیر است:
//بستههای وارداتی
import java.io.*;
import java.net.*;
public class clientTCPgenerique{
// ویژگیهای یک سرویس را به صورت پارامتر در فرم دریافت میکند
// پورت سرور
// به سرویس متصل میشود
// یک نخ ایجاد میکند تا دستورات تایپشده روی صفحهکلید را بخواند
//اینها به سرور ارسال خواهند شد
// یک نخ ایجاد میکند تا پاسخها را از سرور بخواند
//اینها روی صفحه نمایش داده میشوند
//کل فرآیند زمانی پایان مییابد که دستور «end» تایپ شود
// متغیر نمونه
private static Socket client;
public static void main(String[] args){
// سینتکس
final String syntaxe="pg serveur port";
// تعداد آرگومانها
if(args.length != 2)
erreur(syntaxe,1);
// توجه به نام سرور
String serveur=args[0];
// پورت باید یک عدد صحیح مثبت باشد
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(args[1]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
client=null;
//ممکن است مشکلاتی وجود داشته باشد
try{
// اتصال به سرویس
client=new Socket(serveur,port);
}catch(Exception ex){
// خطا
erreur("Impossible de se connecter au service ("+ serveur
+","+port+"), erreur : "+ex.getMessage(),3);
//پایان
return;
}//catch
// رشتههای خواندن/نوشتن ایجاد میشوند
new ClientSend(client).start();
new ClientReceive(client).start();
//پایان نخ اصلی
return;
}// اصلی
//نمایش خطاها
public static void erreur(String msg, int exitCode){
// نمایش خطا
System.err.println(msg);
//خاتمه با خطا
System.exit(exitCode);
}//خطا
}//کلاس
class ClientSend extends Thread {
// کلاس مسئول خواندن دستورات وارد شده از طریق صفحهکلید
// و ارسال آنها به یک سرور از طریق یک کلاینت TCP که بهعنوان پارامتر ارسال شده است
private Socket client; // کلاینت TCP
// سازنده
public ClientSend(Socket client){
//توجه به کلاینت TCP
this.client=client;
}//
// متد Runِ نخ
public void run(){
// دادههای محلی
PrintWriter OUT=null; //جریان نوشتن شبکه
BufferedReader IN=null; //جریان صفحهکلید
String commande=null; // دستور خوانده شده از صفحهکلید
// پردازش خطا
try{
//ایجاد جریان نوشتن شبکه
OUT=new PrintWriter(client.getOutputStream(),true);
//ایجاد جریان ورودی صفحهکلید
IN=new BufferedReader(new InputStreamReader(System.in));
//حلقه ورودی و ارسال فرمان
System.out.println("Commandes : ");
while(true){
//خواندن دستوری که روی صفحهکلید تایپ شده است
commande=IN.readLine().trim();
// تمام شد؟
if (commande.toLowerCase().equals("fin")) break;
//ارسال فرمان به سرور
OUT.println(commande);
//فرمان بعدی
}//در حین
}catch(Exception ex){
//خطا
System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
}//گرفت
//پایان – بستن استریمها
try{
OUT.close();client.close();
}catch(Exception ex){}
// نشاندهنده پایان نخ
System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
}//اجرا
}//کلاس
class ClientReceive extends Thread{
//کلاسی که مسئول خواندن خطوط متنی است که برای یک
// کلاینت TCP بهعنوان پارامتر ارسال شده است
private Socket client; // کلاینت TCP
// سازنده
public ClientReceive(Socket client){
// توجه شود که کلاینت TCP
this.client=client;
}//
// متد Runِ نخ
public void run(){
// دادههای محلی
BufferedReader IN=null; //جریان خواندن شبکه
String réponse=null; // پاسخ سرور
// مدیریت خطا
try{
// ایجاد جریان خواندن شبکه
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
//حلقه برای خواندن خطوط متن از جریان IN
while(true){
// خواندن جریان شبکه
réponse=IN.readLine();
//آیا جریان بسته است؟
if(réponse==null) break;
//نمایش
System.out.println("<-- "+réponse);
}//در حالی که
}catch(Exception ex){
// خطا
System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
}//گرفتن
//پایان – بستن جریانها
try{
IN.close();client.close();
}catch(Exception ex){}
// نشاندهنده پایان نخ
System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
}//اجرا
}//کلاس
8.4.4. یک سرور عمومی TCP
اکنون به یک سرور نگاه میکنیم
- که دستورات ارسالشده توسط کلاینتهای خود را روی صفحه نمایش میدهد
- و در پاسخ، خطوط متنی را که کاربر از طریق صفحهکلید وارد کرده است، برای آنها ارسال میکند. بنابراین این کاربر است که نقش سرور را ایفا میکند.
برنامه با استفاده از دستور زیر اجرا میشود: java serveurTCPgenerique portEcoute، که در آن portEcoute پورتی است که کلاینتها باید به آن متصل شوند. سرویس کلاینت توسط دو تِرد مدیریت خواهد شد:
- یک تِرد که منحصراً به خواندن خطوط متنی ارسالشده توسط کلاینت اختصاص دارد
- یک تِرد اختصاصی که منحصراً به خواندن پاسخهای تایپشده توسط کاربر میپردازد. این تِرد با استفاده از دستور 'fin' سیگنال پایان اتصال با کلاینت را ارسال میکند.
سرور برای هر کلاینت دو تِرد ایجاد میکند. اگر n کلاینت وجود داشته باشد، در یک زمان 2n تِرد فعال خواهند بود. خودِ سرور هرگز متوقف نمیشود، مگر اینکه کاربر کلید Ctrl-C را روی صفحهکلید فشار دهد. بیایید چند مثال را بررسی کنیم.
سرور روی پورت ۱۰۰ در حال اجرا است و ما از کلاینت عمومی برای ارتباط با آن استفاده میکنیم. پنجره کلاینت به این شکل است:
E:\data\serge\MSNET\c#\network\generic tcp client> java clientTCPgenerique 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> java serveurTCPgenerique 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> java clientTCPgenerique 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> java serveurTCPgenerique 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> java serveurTCPgenerique 88
Serveur générique lancé sur le port 88
حالا یک مرورگر را باز کرده و صفحه http://localhost:88/exemple.html را درخواست میکنیم. مرورگر سپس به پورت 88 روی ماشین localhost متصل شده و صفحه /exemple.html را درخواست میکند:

حال بیایید به پنجره سرور خود نگاه کنیم:
Dos>java serveurTCPgenerique 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 اتصال به کلاینت را قطع میکند. مرورگر سپس میداند که پاسخ سرور کامل است و میتواند آن را نمایش دهد:

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

یعنی دقیقاً همان چیزی که از سرور عمومی ارسال شده بود.
کد سرور عمومی TCP به شرح زیر است:
// پکیجها
import java.io.*;
import java.net.*;
public class serveurTCPgenerique{
// برنامه اصلی
public static void main (String[] args){
// روی پورت برای درخواستهای کلاینت گوش میدهد
// یک نخ ایجاد میکند تا درخواستهای مشتری را بخواند
//اینها روی صفحه نمایش داده خواهند شد
// یک نخ ایجاد میکند تا دستورات تایپشده روی صفحهکلید را بخواند
//اینها بهعنوان پاسخ به کلاینت ارسال خواهند شد
//کل فرآیند با فرمان «end» که از طریق صفحهکلید وارد میشود، پایان مییابد
final String syntaxe="Syntaxe : pg port";
// متغیر نمونه
//آیا آرگومانی وجود دارد
if(args.length != 1)
erreur(syntaxe,1);
// پورت باید یک عدد صحیح بزرگتر از 0 باشد
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(args[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// سرویس شنونده ایجاد میشود
ServerSocket ecoute=null;
int nbClients=0; // تعداد مشتریان پردازششده
try{
// سرویس ایجاد میشود
ecoute=new ServerSocket(port);
// نظارت
System.out.println("Serveur générique lancé sur le port " + port);
//حلقه سرویس مشتری
Socket client=null;
while (true){ //حلقه بینهایت – با Ctrl-C متوقف میشود
// در انتظار مشتری
client=ecoute.accept();
// سرویس توسط نخهای جداگانه اجرا میشود
nbClients++;
// رشتههای خواندن/نوشتن ایجاد میشوند
new ServeurSend(client,nbClients).start();
new ServeurReceive(client,nbClients).start();
// به شنود درخواستها بازمیگردد
}// پایان حلقه while
}catch(Exception ex){
//خطا گزارش میشود
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}// پایان برنامه اصلی
//نمایش خطاها
public static void erreur(String msg, int exitCode){
// نمایش خطا
System.err.println(msg);
//توقف با خطا
System.exit(exitCode);
}//خطا
}//کلاس
class ServeurSend extends Thread{
//کلاس مسئول خواندن ورودی صفحهکلید
// و ارسال آنها به یک کلاینت از طریق یک کلاینت TCP که به سازنده پاس داده شده است
Socket client; // کلاینت TCP
int numClient; // شماره مشتری
// سازنده
public ServeurSend(Socket client, int numClient){
// توجه داشته باشید که مشتری TCP
this.client=client;
// و شماره آن
this.numClient=numClient;
}//سازنده
// متد Runِ نخ
public void run(){
// دادههای محلی
PrintWriter OUT=null; //جریان نوشتن شبکه
String réponse=null; //ورودی صفحهکلید
BufferedReader IN=null; //جریان صفحهکلید
// ردیابی
System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
// پردازش خطا
try{
// ایجاد جریان نوشتن شبکه
OUT=new PrintWriter(client.getOutputStream(),true);
// ایجاد جریان صفحهکلید
IN=new BufferedReader(new InputStreamReader(System.in));
//حلقهٔ ورودی و ارسال فرمان
while(true){
//شناسایی مشتری
System.out.print("--> " + numClient + " : ");
// خواندن پاسخ تایپشده روی صفحهکلید
réponse=IN.readLine().trim();
// تمام شد؟
if (réponse.toLowerCase().equals("fin")) break;
//ارسال پاسخ به سرور
OUT.println(réponse);
// پاسخ بعدی
}//در حالی که
}catch(Exception ex){
//خطا
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// پایان – بستن جریانها
try{
OUT.close();client.close();
}catch(Exception ex){}
// نشاندهنده پایان نخ
System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
}//اجرا
}//کلاس
class ServeurReceive extends Thread{
//کلاس مسئول خواندن خطوط متنی ارسالشده به سرور
// از طریق یک کلاینت TCP که به سازنده پاس داده شده است
Socket client; // کلاینت TCP
int numClient; // شماره مشتری
// سازنده
public ServeurReceive(Socket client, int numClient){
//توجه به مشتری TCP
this.client=client;
// و شماره آن
this.numClient=numClient;
}//سازنده
// متد Run نخ
public void run(){
// دادههای محلی
BufferedReader IN=null; //جریان خواندن شبکه
String réponse=null; // پاسخ سرور
// ردیابی
System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
// مدیریت خطا
try{
// ایجاد جریان خواندن شبکه
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
//حلقه برای خواندن خطوط متن از جریان IN
while(true){
// خواندن جریان شبکه
réponse=IN.readLine();
//آیا جریان بسته است؟
if(réponse==null) break;
//نمایش
System.out.println("<-- "+réponse);
}//در حالی که
}catch(Exception ex){
// خطا
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//گرفتن
//پایان – جریانها بسته شدهاند
try{
IN.close();client.close();
}catch(Exception ex){}
// نشاندهنده پایان نخ
System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
}//اجرا
}//کلاس
8.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 به عنوان پارامتر به آن ارسال شود و محتویات این URL را روی صفحه نمایش دهد. ما فرض میکنیم که وبسروری که برای URL تماس گرفته میشود، از پروتکل HTTP نسخه ۱.۱ پشتیبانی میکند. از میان سربرگهای فهرستشده در بالا، تنها موارد زیر را استفاده خواهیم کرد:
- سربرگ اول مشخص میکند که کدام صفحه را میخواهیم
- سرور دوم مشخص میکند که ما از کدام سرور پرسوجو میکنیم
- سومین هدر نشان میدهد که میخواهیم سرور پس از پاسخ دادن به ما، اتصال را ببندد.
اگر در مثال بالا، GET را با HEAD جایگزین کنیم، سرور فقط سربرگهای HTTP را برای ما ارسال میکند و نه صفحه HTML را.
کلاینت وب ما به شکل زیر فراخوانی خواهد شد: java clientweb URL cmd، که در آن URL استURL مورد نظر و cmd یکی از دو کلمه کلیدی GET یا HEAD است که نشان میدهد آیا ما فقط سربرگها (HEAD) را میخواهیم یا همچنین محتوای صفحه را (GET). بیایید به یک مثال اولیه نگاه کنیم. ما سرور IIS را راهاندازی میکنیم و سپس کلاینت وب را روی همان ماشین اجرا میکنیم:
dos>java 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
پاسخ
به این معنی است که صفحهٔ درخواستشده جابهجا شده است (یعنی از URL). URL جدید، URL، توسط هدر Location: ارائه میشود:
اگر در فراخوانی کلاینت وب به جای HEAD از GET استفاده کنیم:
dos>java 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. برنامه به شرح زیر است:
//بستههای وارداتی
import java.io.*;
import java.net.*;
public class clientweb{
//درخواست یک URL میکند
//محتویات خود را روی صفحه نمایش میدهد
public static void main(String[] args){
// سینتکس
final String syntaxe="pg URI GET/HEAD";
//تعداد آرگومانها
if(args.length != 2)
erreur(syntaxe,1);
// URI درخواستی ثبت شد
String URLString=args[0];
String commande=args[1].toUpperCase();
// اعتبارسنجی URI
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI نادرست است
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
//تأیید سفارش
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// سفارش نادرست
erreur("Le second paramètre doit être GET ou HEAD",3);
}
//استخراج اطلاعات مرتبط از URL
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// میتوانیم ادامه دهیم
Socket client=null; //مشتری
BufferedReader IN=null; //جریان خواندن مشتری
PrintWriter OUT=null; //جریان نوشتن مشتری
String réponse=null; //پاسخ سرور
try{
//اتصال به سرور
client=new Socket(host,port);
// ایجاد جریانهای ورودی-خروجی کلاینت TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
//درخواست URL – ارسال سربرگها HTTP
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
//پاسخ را خواند
while((réponse=IN.readLine())!=null){
// پردازش پاسخ
System.out.println(réponse);
}//در حالی که
//کار تمام شد
client.close();
} catch(Exception e){
// استثنا را مدیریت کنید
erreur(e.getMessage(),4);
}//catch
}//اصلی
//نمایش خطاها
public static void erreur(String msg, int exitCode){
// نمایش خطا
System.err.println(msg);
//خاتمه با خطا
System.exit(exitCode);
}//خطا
}//کلاس
تنها ویژگی جدید در این برنامه، استفاده از کلاس URL است. برنامه یک URL (مکانیاب یکنواخت منابع) یا URI (شناسهی یکنواخت منابع) را به صورت http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... دریافت میکند. کلاس URL به ما امکان میدهد رشته URL را به اجزای مختلف آن تجزیه کنیم. یک شیء URL از رشته URLstring که بهعنوان پارامتر دریافت شده، ساخته میشود:
// URL بررسی اعتبار
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI نادرست
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
اگر رشته URL دریافتشده بهعنوان پارامتر، یک URL معتبر نباشد (فقدان پروتکل، سرور و غیره)، یک استثنا پرتاب میشود. این امکان را به ما میدهد تا اعتبار پارامتر دریافتی را بررسی کنیم. پس از ساخت اشیاء URL، به عناصر مختلف آن دسترسی داریم. بنابراین، اگر شیء url از کد قبلی از رشته ساخته شده باشد
خواهیم داشت:
url.getHost() = serveur
url.getPort()=port یا -1 اگر پورت مشخص نشده باشد
url.getPath()=cheminPageHTML یا یک رشته خالی اگر مسیری وجود نداشته باشد
url.getQuery()=param1=val1;param2=val2;... یا null اگر درخواستی وجود نداشته باشد
uri.getProtocol()=http
8.4.6. پردازش redirectها توسط کلاینت وب
کلاینت وب قبلی هیچگونه تغییر مسیر احتمالی URL را که درخواست کرده بود، مدیریت نمیکند. کلاینت بعدی آن را مدیریت میکند.
- این خط اول هدرهای HTTP ارسالشده توسط سرور را میخواند تا بررسی کند آیا حاوی عبارت «302 Object moved» است که نشاندهنده یک هدایت است
- سپس سربرگهای بعدی را میخواند. اگر هدایت وجود داشته باشد، به دنبال خط «Location: url» میگردد که URL جدید برای صفحهٔ درخواستشده را ارائه میدهد و این URL را یادداشت میکند.
- بقیه پاسخ سرور را نمایش میدهد. اگر هدایت وجود داشته باشد، مراحل ۱ تا ۳ با URL جدید تکرار میشوند. برنامه بیش از یک هدایت را قبول نمیکند. این محدودیت توسط یک ثابت تعریف شده است که قابل تغییر است.
در اینجا یک مثال آورده شده است:
Dos>java 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>
برنامه به شرح زیر است:
//بستههای وارداتی
import java.io.*;
import java.net.*;
import java.util.regex.*;
public class clientweb2{
// درخواست URL
//محتویات خود را روی صفحه نمایش میدهد
public static void main(String[] args){
// دستور زبان
final String syntaxe="pg URL GET/HEAD";
// تعداد آرگومانها
if(args.length != 2)
erreur(syntaxe,1);
// URI درخواستی ثبت شد
String URLString=args[0];
String commande=args[1].toUpperCase();
//اعتبار URI را بررسی میکند
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI نادرست است
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
// تأیید سفارش
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// سفارش نادرست
erreur("Le second paramètre doit être GET ou HEAD",3);
}
// میتوانیم ادامه دهیم
Socket client=null; //مشتری
BufferedReader IN=null; //جریان خواندن مشتری
PrintWriter OUT=null; //جریان نوشتن مشتری
String réponse=null; // پاسخ سرور
final int nbRedirsMax=1; // حداکثر یک هدایت مجاز است
int nbRedirs=0; // تعداد ریدایرکتهای در حال انجام
String premièreLigne; // اولین خط پاسخ
boolean redir=false; // نشان میدهد که آیا ریدایرکت وجود دارد یا خیر
String locationString=""; // رشته URL یک هدایت احتمالی
// بیانگر منظم برای یافتن URL جهت هدایت
Pattern location=Pattern.compile("^Location: (.+?)$");
// مدیریت خطا
try{
//ممکن است چندین رشته URL برای بررسی هدایتها وجود داشته باشد
while(nbRedirs<=nbRedirsMax){
//استخراج اطلاعات مرتبط از URL
String protocol=url.getProtocol();
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// اتصال به سرور
client=new Socket(host,port);
// ایجاد جریانهای ورودی و خروجی کلاینت TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// درخواست URL – ارسال سربرگها HTTP
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
// خط اول پاسخ را میخواند
premièreLigne=IN.readLine();
// اکوی صفحه
System.out.println(premièreLigne);
// بازراستنمایی؟
if(premièreLigne.endsWith("302 Object moved")){
//یک هدایت وجود دارد
redir=true;
nbRedirs++;
}//اگر
// سرور باید سربرگهای زیر HTTP را پردازش کند تا زمانی که یک خط خالی که پایان سربرگها را نشان میدهد، یافت شود
boolean locationFound=false;
while(!(réponse=IN.readLine()).equals("")){
// پاسخ را نمایش دهید
System.out.println(réponse);
// اگر هدایتشده وجود داشته باشد، به دنبال هدر Location بگردید
if(redir && ! locationFound){
// خط را با عبارت رابط `location` مقایسه کنید
Matcher résultat=location.matcher(réponse);
if(résultat.find()){
// اگر تطابق یافت شود، کد هدایت را یادداشت میکنیم URL
locationString=résultat.group(1);
// ما متوجه میشویم که یک تطابق یافت شده است
locationFound=true;
}//اگر
}//اگر
// سربرگ بعدی
}//در حالی که
//خطوط زیر پاسخ
System.out.println(réponse);
while((réponse=IN.readLine())!=null){
// پاسخ نمایش داده میشود
System.out.println(réponse);
}//در حالی که
// اتصال را ببندید
client.close();
//آیا تمام شد؟
if ( ! locationFound || nbRedirs>nbRedirsMax)
break;
//یک هدایت مجدد باید انجام شود – هدایت جدید ساخته میشود URL
URLString=protocol +"://"+host+":"+port+locationString;
url=new URL(URLString);
//ردیابی
System.out.println("\n<--Redirection vers l'URL "+URLString+"-->\n");
}//در حالی که
} catch(Exception e){
// استثنا را مدیریت کنید
erreur(e.getMessage(),4);
}//catch
}//اصلی
// نمایش خطاها
public static void erreur(String msg, int exitCode){
// نمایش خطا
System.err.println(msg);
//خاتمه با خطا
System.exit(exitCode);
}//خطا
}//کلاس
8.4.7. سرور محاسبه مالیات
ما در حال بازنگری تمرین IMPOTS هستیم که پیشتر در اشکال مختلف پوشش داده شده است. بیایید آخرین نسخه را به یاد بیاوریم:
یک کلاس پایه مالیات ایجاد شده است. ویژگیهای آن شامل سه آرایه عددی است:
public class impots{
// دادههای مورد نیاز برای محاسبه مالیات
// از یک منبع خارجی میآید
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// میدان سازنده خالی است
protected impots(){}
// سازنده
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
کلاس **impots** دو سازنده دارد:
- یک سازنده که سه آرایهٔ دادههای مورد نیاز برای محاسبهٔ مالیات به آن پاس میشوند
- یک سازنده بدون پارامتر که فقط توسط کلاسهای فرزند قابل استفاده است
کلاس **impotsJDBC** از این کلاس مشتق شده است؛ این کلاس امکان پر کردن سه آرایه limites، coeffR و coeffN از محتویات یک پایگاه داده را فراهم میکند:
public class impotsJDBC extends impots{
// افزودن یک سازنده برای امکانپذیر ساختن ساخت
// جدولهای 'limites'، 'coeffr' و 'coeffn' از
// جدول مالیاتها در یک پایگاه داده
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: نام DSN پایگاه داده
// userIMPOTS, mdpIMPOTS: نام کاربری/رمز عبور برای دسترسی به پایگاه داده
یک برنامه گرافیکی نوشته شده بود. این برنامه از یک شیء از کلاس impotsJDBC استفاده میکرد. برنامه و این شیء روی یک ماشین واحد قرار داشتند. ما پیشنهاد میکنیم که برنامه تست و شیء impotsJDBC را روی ماشینهای مجزا قرار دهیم. ما یک برنامهٔ کلاینت-سرور خواهیم داشت که در آن شیء راه دور impotsJDBC بهعنوان سرور عمل خواهد کرد. کلاس جدید ServeurImpots نام دارد و از کلاس impotsJDBC مشتق شده است:
//بستههای واردشده
import java.net.*;
import java.io.*;
import java.sql.*;
public class ServeurImpots extends impotsJDBC {
//ویژگیها
int portEcoute; // پورت مورد استفاده برای گوش دادن به درخواستهای مشتری
boolean actif; //وضعیت سرور
// سازنده
public ServeurImpots(int portEcoute,String DSNimpots, String USERimpots, String MDPimpots)
throws IOException, SQLException, ClassNotFoundException {
// کلاس والد
super(DSNimpots, USERimpots, MDPimpots);
// توجه به پورت گوشدادن
this.portEcoute=portEcoute;
// در حال حاضر غیرفعال است
actif=false;
// یک تار (thread) برای خواندن دستورات تایپشده روی صفحهکلید ایجاد و راهاندازی میکند
// سرور با استفاده از این دستورات مدیریت خواهد شد
Thread admin=new Thread(){
public void run(){
try{
admin();
}catch (Exception ignored){}
}
};
admin.start();
}//ServeurImpots
تنها پارامتر جدید در سازنده، پورتی است که برای گوش دادن به درخواستهای مشتری استفاده میشود. سایر پارامترها مستقیماً به کلاس پایه impotsJDBC ارسال میشوند. سرور مالیات توسط دستورات وارد شده از طریق صفحهکلید کنترل میشود. بنابراین، یک نخ برای خواندن این دستورات ایجاد میشود. دو دستور ممکن وجود خواهد داشت: start برای راهاندازی سرویس، و stop برای متوقف کردن دائمی آن. متد admin که این دستورات را مدیریت میکند، به شرح زیر است:
public void admin() throws IOException{
// دستورات مدیریت سرور را که از طریق صفحهکلید وارد میشوند، میخواند
//در یک حلقه بیپایان
String commande=null;
BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
while(true){
// دستورالعملها
System.out.print("Serveur d'impôts>");
// خواندن فرمان
commande=IN.readLine().trim().toLowerCase();
// اجرای فرمان
if(commande.equals("start")){
//فعال؟
if(actif){
//خطا
System.out.println("Le serveur est déjà actif");
//ادامه
continue;
}//اگر
// ایجاد و راهاندازی سرویس گوش دادن
Thread ecoute=new Thread(){
public void run(){
ecoute();
}
};
ecoute.start();
}//اگر
else if(commande.equals("stop")){
//پایان تمام نخهای اجرایی
System.exit(0);
}//اگر
else {
//خطا
System.out.println("Commande incorrecte. Utilisez (start,stop)");
}//اگر
}//در حالی که
}//مدیر
اگر دستوری که از طریق صفحهکلید وارد شود start باشد، یک نخ که به درخواستهای مشتری گوش میدهد راهاندازی میشود. اگر دستوری که وارد شود stop باشد، همه نخها متوقف میشوند. نخ گوشدادن متد ecoute را اجرا میکند:
public void ecoute(){
// رشته برای شنود درخواستهای مشتری
// سرویس شنود ایجاد میشود
ServerSocket ecoute=null;
try{
// سرویس ایجاد میشود
ecoute=new ServerSocket(portEcoute);
//پیگیری
System.out.println("Serveur d'impôts lancé sur le port " + portEcoute);
//حلقه سرویس
Socket liaisonClient=null;
while (true){ //حلقه بینهایت
// در انتظار یک مشتری
liaisonClient=ecoute.accept();
// سرویس توسط یک وظیفهٔ دیگر مدیریت میشود
new traiteClientImpots(liaisonClient,this).start();
//بازگشت به شنود درخواستها
}//پایان حلقه while
}catch(Exception ex){
// گزارش خطا
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}//نخ شنونده
یک سرور استاندارد TCP روی پورت portEcoute در حال گوش دادن است. درخواستهای کلاینت توسط متد run در نخ traiteCientImpots پردازش میشوند که به سازندهاش دو پارامتر ارسال میشود:
- شیء Socket با شناسه liaisonClient که برای دسترسی به کلاینت استفاده خواهد شد
- ابژه impotsJDBC this که دسترسی به روش محاسبه مالیات this.calculer را فراهم میکند.
// -------------------------------------------------------
// به مشتری سرور مالیاتی سرویس ارائه میدهد
class traiteClientImpots extends Thread{
private Socket liaisonClient; // اتصال با مشتری
private BufferedReader IN; //جریان ورودی
private PrintWriter OUT; //جریان خروجی
private impotsJDBC objImpots; // شیء مالیاتی
// سازنده
public traiteClientImpots(Socket liaisonClient,impotsJDBC objImpots){
this.liaisonClient=liaisonClient;
this.objImpots=objImpots;
}//سازنده
روش run به رسیدگی به درخواستهای مشتری میپردازد. اینها خطوط متنی هستند که میتوانند دو شکل داشته باشند:
- وضعیت تأهل (b/n) nbEnfants salaireAnnuel
- fincalculs
فرم ۱ امکان محاسبه مالیات را فراهم میکند؛ فرم ۲ اتصال کلاینت-سرور را قطع میکند.
// روش اجرا
public void run(){
// سرویس را به مشتری ارائه میدهد
try{
//جریان ورودی
IN=new BufferedReader(new InputStreamReader(liaisonClient.getInputStream()));
//جریان خروجی
OUT=new PrintWriter(liaisonClient.getOutputStream(),true);
//ارسال پیام خوشآمدگویی به مشتری
OUT.println("Bienvenue sur le serveur d'impôts");
//حلقه: خواندن درخواست/نوشتن پاسخ
String demande=null;
String[] champs=null; // عناصر درخواست
String commande=null; // دستور مشتری: محاسبه یا پایان محاسبه
while ((demande=IN.readLine())!=null){
// تفکیک درخواست به فیلدها
champs=demande.trim().toLowerCase().split("\\s+");
//دو درخواست پذیرفته شده: محاسبه و محاسبات نهایی
commande=champs[0];
if(! commande.equals("calcul") && ! commande.equals("fincalculs")){
//خطای مشتری
OUT.println("Commande incorrecte. Utilisez (calcul,fincalculs).");
// سفارش بعدی
continue;
}//اگر
if(commande.equals("calcul")) calculerImpôt(champs);
if(commande.equals("fincalculs")){
// پیام خداحافظی به مشتری
OUT.println("Au revoir...");
// رهاسازی منبع
try{ OUT.close();IN.close();liaisonClient.close();}
catch(Exception ex){}
//پایان
return;
}//اگر
//درخواست بعدی
}//در حالی که
}catch (Exception e){
erreur("L'erreur suivante s'est produite ("+e+")",2);
}//پایان تلاش
}//پایان اجرا
مالیات با استفاده از روش calculerImpôt محاسبه میشود که آرایه فیلدهای درخواست مشتری را بهعنوان پارامتر میپذیرد. اعتبار درخواست بررسی شده و در صورت لزوم، مالیات محاسبه و به مشتری بازگردانده میشود.
//محاسبه مالیات
public void calculerImpôt(String[] champs){
// درخواست را پردازش میکند: محاسبه مالیات شخص متأهل nbEnfants salaireAnnuel
// شکسته شده به فیلدها در جدول فیلدها
String marié=null;
int nbEnfants=0;
int salaireAnnuel=0;
// اعتبار آرگومانها
try{
//حداقل ۴ فیلد الزامی است
if(champs.length!=4) throw new Exception();
// متأهل
marié=champs[1];
if (! marié.equals("o") && ! marié.equals("n")) throw new Exception();
// فرزندان
nbEnfants=Integer.parseInt(champs[2]);
// حقوق
salaireAnnuel=Integer.parseInt(champs[3]);
}catch (Exception ignored){
// خطای قالب
OUT.println(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel");
// پایان
return;
}//اگر
// مالیات قابل محاسبه است
long impot=objImpots.calculer(marié.equals("o"),nbEnfants,salaireAnnuel);
//پاسخ را برای مشتری ارسال کنید
OUT.println(""+impot);
}//محاسبه
یک برنامه آزمایشی ممکن است به این شکل باشد:
//تماس: serveurImpots port dsnImpots userImpots mdpImpots
import java.io.*;
public class testServeurImpots{
public static final String syntaxe="Syntaxe : pg port dsnImpots userImpots mdpImpots";
// برنامهٔ اصلی
public static void main (String[] args){
// نیازمند ۴ آرگومان است
if(args.length != 4)
erreur(syntaxe,1);
// پورت باید یک عدد صحیح بزرگتر از 0 باشد
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(args[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// سرور مالیات ایجاد میشود
try{
new ServeurImpots(port,args[1],args[2],args[3]);
}catch(Exception ex){
//خطا
System.out.println("L'erreur suivante s'est produite : "+ex.getMessage());
}//catch
}//اصلی
//نمایش خطاها
public static void erreur(String msg, int exitCode){
// نمایش خطا
System.err.println(msg);
//خاموشی با خطا
System.exit(exitCode);
}//خطا
}// پایان کلاس
ما دادههای لازم برای ساخت یک شیء ServeurImpots را به برنامهٔ تست میدهیم و از آنجا این شیء ایجاد میشود.
بیایید برای اولین بار آن را اجرا کنیم:
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
دستور
یک شیء ServeurImpots ایجاد میکند که هنوز در حال گوش دادن به درخواستهای کلاینت نیست. این فرمان «start» است که از طریق صفحهکلید وارد میشود و این فرآیند گوش دادن را آغاز میکند. فرمان «stop» سرور را متوقف میکند. اکنون بیایید از یک کلاینت استفاده کنیم. ما از کلاینت عمومی که قبلاً ایجاد شده است استفاده خواهیم کرد. سرور راهاندازی میشود:
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
کلاینت عمومی در یک پنجره DOS دیگر اجرا میشود:
میتوانیم ببینیم که کلاینت با موفقیت پیام خوشآمدگویی را از سرور دریافت کرده است. ما چند دستور دیگر میفرستیم:
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>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
8.5. تمرینها
8.5.1. تمرین ۱ – مشتری گرافیکی عمومی TCP
8.5.1.1. مقدمه ای بر برنامه
هدف ایجاد برنامهای است که قادر به برقراری ارتباط از طریق اینترنت با سرویسهای اصلی TCP باشد. ما این را یک کلاینت TCP عمومی مینامیم. وقتی این برنامه را درک کردید، متوجه خواهید شد که همه کلاینتهای TCP شبیه به هم هستند. پنجره برنامه به شرح زیر است:

کنترلهای مختلف به شرح زیر هستند:
خیر. | نام | نوع | نقش |
1 | TxtRemoteHost | JTextField | نام دستگاه ارائهدهنده سرویس مورد نیاز |
2 | TxtPort | JTextField | پورت برای سرویس درخواستی |
3 | TxtSend | JTextField | متن پیامی که باید توسط کلاینت به سرور ارسال شود |
4 | OptRCLF OptLF | JCheckBox | دکمههایی که برای مشخص کردن نحوه پایان خطوط در دیالوگ کلاینت/سرور استفاده میشوند RCLF: بازگشت به خانه (#13) + ارسال خط (#10) LF: خطشکن (#10) |
۵ | LstSuivi | JList | نمایش پیامهای مربوط به وضعیت ارتباط بین کلاینت و سرور |
۶ | LstDialogue | JList | پیامهای مبادله شده توسط کلاینت (->) و سرور (<-) را نمایش میدهد |
7 | CmdAnnuler | JButton | مخفی – واقع در زیر لیست گفتگو – زمانی که اتصال فعال است ظاهر میشود و به شما امکان میدهد در صورتی که سرور پاسخ ندهد، آن را قطع کنید |
گزینههای موجود منو به شرح زیر است:
گزینه | زیرگزینهها | نقش |
اتصال | اتصال | مشتری را به سرور متصل میکند |
قطع اتصال | قطع اتصال | |
خروج | خروج از برنامه | |
پیامها | ارسال | پیام را از کنترل TxtSend به سرور ارسال میکند |
RazSuivi | فهرست LstSuivi را پاک کنید | |
RazDialogue | فهرست LstDialogue را پاک میکند | |
نویسنده | نمایش یک کادر حق نشر |
8.5.1.2. FONCTIONNEMENT DE APPLICATION
هنگامی که صفحهٔ اصلی برنامه بارگذاری میشود، اقدامات زیر انجام میگیرند:
- برگه در مرکز صفحه نمایش قرار میگیرد
- فقط گزینههای منوی Connexion/Quitter و Auteur فعال هستند
- دکمه Annuler پنهان است
- فهرستهای LstSuivi و LstDialogue خالی هستند
این گزینه تنها زمانی در دسترس است که فیلدهای میزبان از راه دور و شماره پورت خالی نباشند و هیچ ارتباط فعالی وجود نداشته باشد. کلیک بر این گزینه، اقدامات زیر را انجام میدهد:
- پورت از نظر اعتبار بررسی میشود: باید یک عدد صحیح بزرگتر از 0 باشد
- یک تِرد برای برقراری ارتباط با سرور راهاندازی میشود
- دکمه Annuler ظاهر میشود که به کاربر امکان قطع اتصال فعلی را میدهد
- تمام گزینههای منو غیرفعال میشوند به جز Quitter و Auteur
ارتباط را میتوان به چند روش قطع کرد:
- کاربر دکمه Annuler را فشار داده است: نخ اتصال متوقف شده و منو به وضعیت اولیه خود بازمیگردد. لاگ نشان میدهد که اتصال توسط کاربر بسته شده است.
- اتصال با خطا پایان مییابد: همان رویهٔ بالا دنبال میشود و علت خطا نیز در لاگ ثبت میگردد.
- اتصال با موفقیت تکمیل میشود: دکمه Annuler حذف میشود؛ لاگ نشان میدهد که اتصال برقرار شده است؛ منوی RazSuivi فعال میشود؛ منوی Connecter غیرفعال میشود؛ منوی Déconnecter فعال میشود
این گزینه فقط زمانی در دسترس است که اتصال به سرور برقرار باشد. با فعالسازی آن، اتصال به سرور بسته شده و منو به وضعیت اولیه خود بازمیگردد. لاگ نشان میدهد که اتصال توسط کلاینت بسته شده است.
این گزینه هر اتصال فعالی به سرور را میبندد و برنامه را خروج میدهد.
این گزینه فقط در صورتی در دسترس است که شرایط زیر برقرار باشد:
-
یک اتصال به سرور برقرار شده باشد
-
پیامی برای ارسال وجود داشته باشد
اگر این شرایط برقرار باشند، متن موجود در فیلد TxtSend (3) به سرور ارسال میشود، و در صورتی که گزینه RCLF تیک خورده باشد، به دنبال آن توالی RCLF، و در غیر این صورت توالی LF ارسال میگردد. هرگونه خطا در حین انتقال در فهرست ردیابی گزارش میشود.
فهرستهای LstSuivi و LstDialogue را بهترتیب پاک کنید. این گزینهها زمانی که فهرستهای مربوطه خالی باشند غیرفعال میشوند.
این دکمه که در پایین فرم قرار دارد، تنها زمانی ظاهر میشود که کلاینت در حال تلاش برای اتصال به سرور است. این اتصال ممکن است به دلیل عدم پاسخدهی سرور یا پاسخ نادرست آن با شکست مواجه شود. دکمه Annuler سپس به کاربر اجازه میدهد تا درخواست اتصال را لغو کند.
فهرست LstSuivi (5) ارتباط را پیگیری میکند. این فهرست مراحل کلیدی ارتباط را نشان میدهد:
-
زمانی که توسط کلاینت باز میشود
-
بستهشدن آن توسط سرور یا کلاینت
-
هرگونه خطایی که ممکن است در حین فعال بودن اتصال رخ دهد
فهرست LstDialogue (6) مکالمه بین کلاینت و سرور را ردیابی میکند. یک نخ (thread) در پسزمینه فعالیتهای ساکت ارتباطی کلاینت را زیر نظر دارد و آن را در فهرست 6 نمایش میدهد.
این منو پنجرهای را که به نام پنجره حق نشر (Copyright) شناخته میشود، باز میکند:

خطاهای اتصال در لیست نظارت 6 گزارش میشوند، در حالی که خطاهای مربوط به گفتگوی کلاینت/سرور در لیست گفتگوی 7 گزارش میگردند. در صورت بروز خطای اتصال، گفتگوی کلاینت/سرور بسته شده و فرم به وضعیت اولیه خود بازگردانده میشود تا برای یک اتصال جدید آماده باشد.
8.5.1.3. TRAVAIL به FAIRE
کار فوق را در دو فرم پیادهسازی کنید:
- برنامهٔ مستقل
- اپلت
8.5.2. تمرین ۲ – یک سرور منبعی
8.5.2.1. INTRODUCTION
یک مؤسسه چندین سرور محاسباتی قدرتمند دارد که از طریق اینترنت قابل دسترسی هستند. هر دستگاهی که بخواهد از این خدمات محاسباتی استفاده کند، یک فایل داده را به پورت ۷۵۶ یکی از سرورها ارسال میکند. این فایل حاوی اطلاعات مختلفی است: نام کاربری، رمز عبور، دستوراتی که نوع محاسبه مورد نیاز را مشخص میکنند و دادههایی که باید بر روی آنها محاسبه انجام شود. اگر فایل داده صحیح باشد، سرور محاسباتی انتخابشده آن را پردازش کرده و نتایج را به شکل یک فایل متنی به کلاینت بازمیگرداند.
این ترتیب مزایای زیادی دارد:
- هر نوع کلاینت (PC، مک، یونیکس و غیره) میتواند از این سرویس استفاده کند
- سرور میتواند در هر نقطهای از اینترنت قرار داشته باشد
- منابع محاسباتی بهینه میشوند: تنها به چند ماشین قدرتمند نیاز است. این بدان معناست که حتی یک سازمان کوچک بدون منابع محاسباتی خود نیز میتواند در ازای مشارکت مالی که بر اساس زمان محاسبات مصرفشده محاسبه میشود، از این سرویس استفاده کند.
با وجود قدرت این ماشینها، گاهی اوقات یک محاسبه میتواند چندین ساعت طول بکشد: در این مدت، سرور برای سایر کلاینتها در دسترس نخواهد بود. این امر کلاینتها را با مشکل یافتن یک سرور محاسباتی در دسترس مواجه میکند. برای حل این مشکل، از یک «مدیر منابع محاسباتی» استفاده میشود که از این پس با عنوان سرور GRC به آن اشاره خواهد شد. این سرویس روی یک ماشین واحد اجرا میشود و در پورت ۸۶۴ به صورت TCP فعالیت میکند. کلاینتی که مایل به دسترسی به یک سرور محاسباتی است، با این سرویس تماس میگیرد. سرور GRC که فهرست کامل سرورهای محاسباتی را در اختیار دارد، با ارسال نام سروری که در حال حاضر غیرفعال است، پاسخ میدهد. سپس کلاینت به سادگی دادههای خود را به سروری که به آن اختصاص داده شده است، ارسال میکند.
ما پیشنهاد میکنیم سرور GRC را بنویسیم.
8.5.2.2. INTERFACE VISUELLE
رابط کاربری به شرح زیر خواهد بود:

رابط دو فهرست از سرورها را نمایش میدهد:
- در سمت چپ، فهرست سرورهای غیرفعال که بنابراین برای محاسبات در دسترس هستند
- در سمت راست، فهرستی از سرورهایی که در حال حاضر توسط محاسبات مشتری اشغال شدهاند.
ساختار منو به شرح زیر است:
منوی اصلی | منوی ثانویه | نقش |
سرویس | شروع | راهاندازی سرویس TCP روی پورت ۸۶۴ |
توقف | متوقف کردن سرویس | |
خروج | خروج از برنامه | |
نویسنده | اطلاعات حق نشر |
ساختار کنترلهای فرم به شرح زیر است:
نام | نوع | نقش |
listLibres | JList | فهرست سرورهای موجود |
listOccupés | JList | فهرست سرورهای اشغالشده |
8.5.2.3. FONCTIONNEMENT DE APPLICATION
هنگامی که برنامه بارگذاری میشود، لیست listLibres با فهرست نام سرورهای محاسباتی که توسط GRC مدیریت میشوند، پر میشود. این سرورها در فایلی به نام «Servers» که بهعنوان پارامتر ارسال میشود، تعریف شدهاند. این فایل شامل فهرستی از نامهای سرور است، بهصورت یک نام در هر خط، و بنابراین برای پر کردن فهرست listLibres استفاده میشود. منوی Start فعال است؛ منوی Stop غیرفعال است.
این گزینه
- سرویس را برای گوش دادن به پورت 864 ماشین راهاندازی میکند
- منوی شروع را غیرفعال میکند
- منوی توقف را فعال میکند
این گزینه سرویس را متوقف میکند:
- فهرست سرورهای در حال استفاده پاک میشود
- فهرست سرورها با محتویات فایل Servers پر میشود
- منوی «شروع» فعال میشود
- گزینه توقف غیرفعال میشود
برنامه بسته میشود.
ارتباط کلاینت/سرور از طریق تبادل خطوط متنی که با دنباله RCLF پایان مییابد، انجام میشود. سرور GRC دو فرمان را تشخیص میدهد: getserver و endservice. نقش این دو فرمان در زیر توضیح داده شده است:
- ۱-getserveur
کلاینت استعلام میگیرد که آیا یک سرور محاسباتی در دسترس او قرار دارد یا خیر.
سپس سرور GRC اولین سرور موجود در فهرست خود را انتخاب کرده و نام آن را به شکل زیر به کلاینت بازمیگرداند:
علاوه بر این، سرور تخصیصیافته به کلاینت را با فرمت زیر به لیست سرورها با وضعیت اشغال اضافه میکند:
همانطور که در مثال زیر نشان داده شده است، جایی که سرور calcul1.istia.univ-angers.fr در حال حاضر به کلاینت با آدرس IP 193.52.43.5 خدمترسانی میکند:

یک کلاینت نمیتواند فرمان **getserver** را ارسال کند اگر یک سرور محاسباتی قبلاً به آن اختصاص داده شده باشد. بنابراین، پیش از پاسخ دادن به کلاینت، سرور GRC بررسی میکند که آدرس کلاینت IP در میان سرورهای درگیر موجود در لیست ثبتشده، وجود نداشته باشد. اگر چنین باشد، سرور GRC پاسخ میدهد:
در نهایت، حالتی وجود دارد که هیچ سرور محاسباتی در دسترس نیست: لیست سرورهای آزاد خالی است. در این حالت، سرور GRC پاسخ میدهد:
در تمام موارد، پس از پاسخ دادن به کلاینت، سرور GRC اتصال را با کلاینت قطع میکند تا بتواند به کلاینتهای دیگر خدمترسانی کند.
- 2-finservice
کلاینت اعلام میکند که دیگر به سرور محاسباتی که از آن استفاده میکرد نیازی ندارد.
سرور GRC ابتدا بررسی میکند که آیا مشتری واقعاً همان کسی است که در حال ارائه سرویس به او بوده است. برای این کار، بررسی میکند که آیا آدرس مشتری، IP، در میان آدرسهای ثبتشده در فهرست سرورهای در حال استفاده وجود دارد یا خیر. اگر چنین نباشد، سرور GRC پاسخ میدهد:
اگر کلاینت شناسایی شود، سرور GRC پاسخ میدهد:
و سرور محاسباتی اختصاصیافته به آن کلاینت را به فهرست سرورهای آزاد منتقل میکند. برای بازگشت به مثال قبلی، اگر کلاینت فرمان finservice را ارسال کند، نمایش روی سرور GRC به شکل زیر درمیآید:

پس از ارسال پاسخ، هرچه باشد، سرور GRC اتصال را قطع میکند.
8.5.2.4. TRAVAIL به FAIRE
برنامه را به صورت یک برنامه مستقل بنویسید که بتوان آن را، برای مثال، با یک کلاینت telnet یا با کلاینت عمومی TCP از تمرین قبلی، آزمایش کرد.
8.5.3. تمرین ۳ – یک کلاینت SMTP
8.5.3.1. INTRODUCTION
در اینجا، ما میخواهیم یک کلاینت برای سرویس SMTP (پروتکل انتقال SendMail) بسازیم که ارسال ایمیل را امکانپذیر میسازد. در یونیکس یا ویندوز، برنامه telnet یک کلاینت است که با پروتکل tcp کار میکند. این برنامه میتواند با هر سرویس tcp که دستورات متنی با پایانیافته با توالی RCLF را میپذیرد، «ارتباط» برقرار کند، یعنی کاراکترهایی با کدهای ASCII 13 و 10. در اینجا مثالی از یک گفتگوی با سرویس smtp برای ارسال ایمیل آورده شده است:
// پاسخ از سرور SMTP
Trying 193.52.43.2...
Connected to istia.univ-angers.fr.
Escape character is '^]'.
220-Istia.Istia.Univ-Angers.fr Sendmail 8.6.10/8.6.9 ready at Tue, 16 Jan 1996 07:53:12 +0100
220 ESMTP spoken here
// توضیحات --------------
برنامه telnet میتواند با استفاده از دستوری هر سرویسی را فراخوانی کند
telnet machine_service port_service
تبادلهای کلاینت/سرور با استفاده از خطوط متنی که با دنباله RCLF پایان مییابند، انجام میشود.
پاسخهای سرویس smtp به شکل زیر هستند:
شماره پیام یا
شماره پیام
سرور SMTP ممکن است چندین خط پاسخ ارسال کند. خط آخر پاسخ با یک عدد و سپس یک فاصله مشخص میشود، در حالی که برای خطوط قبلی پاسخ، عدد با یک خط تیره - دنبال میشود.
عددی بزرگتر یا مساوی 500 نشاندهنده یک پیام خطا است.
// پایان توضیحات
// پاسخ از سرور SMTP
214-Commands:
214- HELO EHLO MAIL RCPT DATA
214- RSET NOOP QUIT HELP VRFY
214- EXPN VERB
214-For more info use "HELP <topic>".
214-To report bugs in the implementation send email to
214- sendmail@CS.Berkeley.EDU.
214-For local information send email to Postmaster at your site.
214 End of HELP info
// توضیحات ---------
دستور mail دارای سینتکس زیر است:
mail from: آدرس ایمیل فرستنده
// پایان توضیحات
// پاسخ از سرور SMTP
// توضیحات
سرور smtp اعتبار آدرس فرستنده را بررسی نمیکند: آن را همانطور که ارائه شده میپذیرد
// پایان توضیحات
// توضیحات ---------
دستور rcpt دارای سینتکس زیر است:
rcpt به: آدرس ایمیل گیرنده پیام
اگر آدرس ایمیل روی دستگاهی که سرور smtp را اجرا میکند وجود داشته باشد، بررسی میکند که آیا معتبر است؛ در غیر این صورت، هیچ تصدیقی انجام نمیدهد. اگر تصدیقی انجام شود و خطایی تشخیص داده شود، این موضوع با عددی ≥ 500 نشان داده خواهد شد.
شما میتوانید هر تعداد دستور rcpt to که میخواهید صادر کنید: این به شما امکان میدهد یک پیام را به چندین نفر ارسال کنید.
// پایان توضیحات
// پاسخ از سمت سرور SMTP
// توضیحات ---------
دستور data دارای سینتکس زیر است:
data
line1
line2
...
.
پس از آن خطوط متنی تشکیلدهنده پیام قرار میگیرند که باید با یک خط حاوی صرفاً کاراکتر «نقطه» پایان یابد.
سپس پیام برای گیرندهٔ مشخصشده توسط دستور rcpt ارسال میشود.
// پایان توضیحات
// پاسخ از سمت سرور SMTP
// متن پیام تایپشده روی صفحهکلید
subject: essai smtp
essai smtp a partir de telnet
.
// توضیحات
در خطوط متنی دستور data میتوانید یک خط «subject:» را برای مشخص کردن موضوع ایمیل اضافه کنید. این خط باید با یک خط خالی دنبال شود.
// پاسخ از سرور SMTP
// توضیحات
دستور «quit» اتصال به سرویس smtp را قطع میکند.
// پایان توضیحات
// پاسخ از سرور SMTP
8.5.3.2. INTERFACE VISUELLE
ما پیشنهاد میکنیم برنامهای با رابط کاربری گرافیکی زیر بسازیم:

کنترلها دارای عملکردهای زیر هستند:
شماره | نوع | نقش |
۱ | JTextField | فهرستی از آدرسهای ایمیل که با ویرگول از هم جدا شدهاند |
2 | JTextField | متن موضوع پیام |
۳ | JTextField | فهرستی از آدرسهای ایمیل که با ویرگول از هم جدا شدهاند |
۴ | JTextField | یک دنباله از آدرسهای ایمیل که با ویرگول از هم جدا شدهاند |
۵ | JTextArea | متن پیام |
۶ | JList | فهرست نظارتی |
۷ | JList | فهرست دیالوگها |
8 | JButton | دکمه «لغو» نمایش داده نمیشود و زمانی ظاهر میشود که مشتری درخواست اتصال به سرور SMTP را میدهد. این دکمه به کاربر اجازه میدهد در صورتی که سرور پاسخی ندهد، این درخواست را لغو کند. |
8.5.3.3. LES MENUS
ساختار منوی برنامه به شرح زیر است:
منوی اصلی | منوی ثانویه | نقش |
پست | ||
ارسال | پیام را از کنترل ۵ ارسال کنید | |
خروج | خروج از برنامه | |
گزینهها | ||
پنهان کردن ردیابی | کنترل ۶ را مخفی میکند | |
بازنشانی ردیابی | پاک کردن فهرست تماشا 6 | |
پنهان کردن کادر گفتگو | فهرست مکالمات را مخفی میکند 7 | |
پاک کردن دیالوگ | پاک کردن فهرست دیالوگها 7 | |
پیکربندی | به کاربر امکان میدهد مشخص کند - آدرس سرور SMTP مورد استفاده توسط برنامه - آدرس ایمیل خود | |
ذخیره... | پیکربندی قبلی را در یک فایل .ini ذخیره میکند | |
نویسنده | اطلاعات حق نشر |
8.5.3.4. FONCTIONNEMENT DE APPLICATION
این منو پنجره زیر را باز میکند:

برای فعال شدن دکمه OK، هر دو فیلد باید تکمیل شوند. هر دو اطلاعات باید در متغیرهای سراسری ذخیره شوند تا برای ماژولهای دیگر در دسترس باشند.
این گزینه فقط در صورتی در دسترس است که شرایط زیر برآورده شوند:
- پیکربندی انجام شده باشد
- پیامی برای ارسال وجود داشته باشد
- یک موضوع وجود دارد
- حداقل یک گیرنده در فیلدهای ۱، ۳ و ۴ وجود دارد
اگر این شرایط برقرار باشند، توالی رویدادها به شرح زیر است:
- فرم به حالتی تنظیم میشود که در آن تمام اقداماتی که میتوانند در مکالمه کلاینت/سرور تداخل ایجاد کنند، غیرفعال هستند
- یک اتصال بر روی پورت ۲۵ سرور مشخصشده در پیکربندی برقرار میشود
- سپس کلاینت مطابق پروتکل توصیفشده در بالا با سرور SMTP ارتباط برقرار میکند
- میدان 'From' از آدرس ایمیل فرستنده مشخصشده در پیکربندی استفاده میکند
- میدان 'rcpt to:' برای هر یک از آدرسهای ایمیل یافتشده در فیلدهای ۱، ۳ و ۴ استفاده میشود
- در خطوط ارسالشده پس از فرمان data، متن زیر ظاهر خواهد شد:
- یک خط Subject: – متن موضوع برای بررسی ۲
- یک خط Cc:: آدرسها برای بررسی ۳
- یک خط Bcc:: آدرسها برای بررسی ۴
- متن پیام برای بررسی ۵
- نقطه پایانی
این دکمه که در پایین فرم قرار دارد، تنها زمانی ظاهر میشود که کلاینت در حال اتصال به سرور smtp باشد. این اتصال ممکن است به دلیل عدم پاسخدهی یا پاسخدهی نادرست سرور smtp ناموفق باشد. سپس دکمه Annuler به کاربر اجازه میدهد درخواست اتصال را لغو کند.
این فهرست (6) ارتباط را ردیابی میکند. مراحل کلیدی ارتباط را نشان میدهد:
- زمانی که توسط کلاینت باز میشود
- بستهشدن آن توسط سرور یا کلاینت
- هرگونه خطای اتصال
فهرست (7) مکالمه (smtp) را که بین کلاینت و سرور رخ میدهد، ردیابی میکند.
این دو فهرست به گزینههای منو متصل هستند:
پنهان کردن فهرست تماشا | فهرست ردیابی ۶ و برچسب بالای آن را پنهان میکند. اگر ارتفاع اشغالشده توسط این دو کنترل H باشد، تمام کنترلهای زیر آنها به اندازه H به بالا جابجا میشوند و اندازه کل فرم به اندازه H کاهش مییابد. علاوه بر این، «پنهان کردن ردیابی» گزینه RazSuivi را در زیر پنهان میکند. |
پاک کردن ردیابی | فهرست ردیابی 6 را پاک میکند |
پنهان کردن دیالوگ | فهرست دیالوگ 7، برچسب بالای آن و گزینه منوی RazDialogue زیر آن را پنهان میکند. مانند «پنهان کردن ردیابی»، موقعیت کنترلهای زیر (احتمالاً دکمه Annuler) مجدداً محاسبه شده و اندازه پنجره کاهش مییابد. |
پاک کردن دیالوگ | فهرست مکالمه 7 را پاک میکند |
این منو پنجرهای را باز میکند که به نام پنجرهٔ حق نشر شناخته میشود:

خطاهای اتصال در فهرست ردیابی 6 گزارش میشوند، در حالی که خطاهای مربوط به ارتباط کلاینت/سرور در فهرست مکالمه 7 گزارش میشوند. هنگامی که خطایی رخ میدهد، کاربر از طریق یک کادر خطا مطلع میشود و اگر فهرست حاوی علت خطا قبلاً پنهان بوده باشد، نمایش داده میشود. علاوه بر این، دیالوگ کلاینت/سرور بسته شده و فرم به وضعیت اولیه خود بازمیگردد.
8.5.3.5. GESTION از UN FICHIER DE CONFIGURATION
ترجیح است که کاربر هر بار که از نرمافزار استفاده میکند، نیازی به پیکربندی مجدد آن نداشته باشد. برای دستیابی به این هدف، اگر گزینه «گزینهها/ذخیره پیکربندی هنگام خروج» تیک خورده باشد، بستن برنامه دو مورد اطلاعات بهدستآمده از طریق گزینه Options/Configurer و همچنین وضعیت دو لیست ردیابی را در فایلی با نام sendmail.ini که در همان پوشه فایل اجرایی (.exe) برنامه قرار دارد، ذخیره میکند. این فایل دارای فرمت زیر است:
SmtpServer=shiva.istia.univ-angers.fr
ReplyAddress=serge.tahe@istia.univ-angers.fr
Suivi=0
Dialogue=1
خطوط SmtpServer و ReplyAddress حاوی دو مورد اطلاعات بازیابیشده از طریق منوی Options/Configure هستند. خطوط Suivi و Dialogue وضعیت لیستهای ردیابی و مکالمه را نشان میدهند: 1 (موجود)، 0 (غایب).
وقتی برنامه بارگذاری میشود، اگر فایل sendmail.ini وجود داشته باشد، خوانده شده و فرم مطابق آن پیکربندی میشود. اگر فایل sendmail.ini وجود نداشته باشد، برنامه طوری رفتار میکند که گویی وجود دارد:
اگر فایل sendmail.ini وجود داشته باشد اما ناقص باشد (خطوط مفقود)، خط مفقود با خط متناظر بالاتر جایگزین میشود. بنابراین، از آنجا که خط Suivi=... مفقود است، ما آن را طوری در نظر میگیریم که گویی Suivi=1 را داریم.
هر خطی که با قالب مطابقت ندارد:
نادیده گرفته میشوند، و همچنین آنهایی که کلمه کلیدی در آنها نامعتبر است. کلمه کلیدی میتواند به صورت حروف بزرگ یا کوچک باشد: این تفاوتی ایجاد نمیکند.
در گزینه Options/Configurer، مقادیر SmtpServer و ReplyAddress که در حال حاضر در استفاده هستند، نمایش داده میشوند. کاربر سپس میتواند در صورت تمایل آنها را ویرایش کند.
8.5.3.6. TRAVAIL تا FAIRE
وظیفهٔ توصیفشده در بالا را انجام دهید. توصیه میشود در آخر به فایل پیکربندی بپردازید.
8.5.4. تمرین ۴ – کلاینت POPPASS
8.5.4.1. Introduction
ما پیشنهاد میکنیم یک کلاینت به نام TCP ایجاد کنیم که قادر به ارتباط با سرور POPPASSD باشد که روی پورت 106 در حال اجرا است. این سرویس به کاربران اجازه میدهد رمز عبور خود را روی ماشین UNIX تغییر دهند. پروتکل ارتباط کلاینت/سرور به شرح زیر است:
1 - ارتباط از طریق تبادل پیامهایی برقرار میشود که با دنباله RCLF پایان مییابد.
2 - کلاینت دستورات را به سرور ارسال میکند
- سرور با پیامهایی که با اعداد سهرقمی شروع میشوند، پاسخ میدهد: XXX. اگر XXX=200 باشد، فرمان به درستی اجرا شده است؛ در غیر این صورت، خطایی رخ داده است.
۳ - توالی مبادلات به شرح زیر است:
- سرور با یک پیام خوشآمدگویی پاسخ میدهد
- اگر ورود تأیید شود، سرور با درخواست رمز عبور پاسخ میدهد؛ در غیر این صورت، خطایی را بازمیگرداند
- اگر رمز عبور پذیرفته شود، سرور با درخواست رمز عبور جدید پاسخ میدهد؛ در غیر این صورت، خطایی را بازمیگرداند
- سرور با تأیید پذیرش رمز عبور جدید پاسخ میدهد؛ در غیر این صورت، خطا برمیگردد
- سرور پیام پایان جلسه را ارسال میکند و اتصال را قطع میکند
8.5.4.2. فرم کلاینت

معنای کنترلهای مختلف به شرح زیر است:
خیر. | نام | نوع | نقش |
1 | txtRemoteHost | JTextField | نام سرور |
2 | txtLogin | JTextField | ورود کاربر |
3 | txtMdp | JTextField | رمز عبور کاربر |
۴ | txtNewMdp | JTextField | رمز عبور کاربر جدید |
۵ | txtConfirmation | JTextField | تأیید رمز عبور جدید |
6 | lstSuivi | JList | پیامهای ردیابی ورود |
7 | lstDialogue | JList | پیامهای دیالوگ کلاینت/سرور |
10 | cmdAnnuler | JButton | نمایش داده نشده – دکمهای که هنگام اتصال به سرور ظاهر میشود. به شما امکان میدهد اتصال را متوقف کنید. |
8.5.4.3. منوها
عنوان | نام کنترل | نقش |
اتصال | منوی ورود | |
ورود | mnuconnect | اتصال به سرور را آغاز میکند |
خروج | mnuQuitter | خروج از برنامه |
پیامها | mnuMessages | |
RazSuivi | mnuRazSuivi | پاک کردن لیست lstSuivi |
RazDialogue | mnuRazDialogue | فهرست را پاک کنید lstDialogue |
نویسنده | mnuAuteur | نمایش جعبهی حق نشر |
8.5.4.4. نحوه کار برنامه
هنگامی که برگه اصلی برنامه بارگذاری میشود، اقدامات زیر انجام میگیرند:
- صفحه روی نمایشگر مرکزدهی میشود
- فقط گزینههای منوی Connexion/Quitter و Auteur فعال هستند
- دکمه Annuler پنهان است
- فهرستهای LstSuivi و LstDialogue خالی هستند
این گزینه تنها پس از تکمیل فیلدهای ۱ تا ۵ در دسترس است. کلیک بر این گزینه، اقدامات زیر را اجرا میکند:
- یک نخ برای برقراری ارتباط با سرور راهاندازی میشود
- دکمه Annuler ظاهر میشود تا کاربر بتواند اتصال در حال انجام را قطع کند
- تمام گزینههای منو غیرفعال میشوند به جز Quitter و Auteur
سپس توالی رویدادها به شرح زیر است:
- کاربر دکمه Annuler را فشار داده است: نخ اتصال متوقف شده و منو به وضعیت اولیه خود بازمیگردد. در لاگ یادداشتی ثبت میشود که اتصال توسط کاربر بسته شده است.
- درخواست اتصال توسط سرور پذیرفته میشود. سپس یک دیالوگ برای تغییر رمز عبور با سرور آغاز میشود. مبادلات این دیالوگ در لیست LstDialogue ثبت میشود. پس از اتمام دیالوگ، اتصال با سرور بسته میشود و منوی فرم به حالت اولیه خود بازمیگردد.
- در حالی که دیالوگ فعال است، دکمه لغو قابل مشاهده باقی میماند تا کاربر در صورت تمایل بتواند اتصال را قطع کند.
- اگر در حین ارتباط خطایی رخ دهد، اتصال قطع شده و علت خطا در لیست ردیابی LstSuivi نمایش داده میشود.
این گزینه هر اتصال فعالی به سرور را میبندد و برنامه را خروج میدهد.
فهرستهای LstSuivi و LstDialogue را به ترتیب پاک کنید. این گزینهها زمانی که فهرستهای مربوطه خالی باشند غیرفعال میشوند.
این دکمه که در پایین فرم قرار دارد، تنها زمانی ظاهر میشود که کلاینت در حال اتصال به سرور است یا قبلاً به آن متصل شده باشد. دکمه Annuler به کاربر امکان قطع ارتباط با سرور را میدهد.
فهرست LstSuivi (5) ارتباط را پیگیری میکند. این فهرست لحظات کلیدی ارتباط را نشان میدهد:
-
زمانی که توسط کلاینت باز میشود
-
بستهشدن آن توسط سرور یا کلاینت
-
هرگونه خطایی که ممکن است در حین فعال بودن اتصال رخ دهد
فهرست LstDialogue (6) مکالمه بین کلاینت و سرور را ردیابی میکند.
این منو پنجرهای به نام پنجرهٔ حق نشر را باز میکند:

خطاهای ارتباطی در فهرست نظارت 6 گزارش میشوند، در حالی که خطاهای مربوط به ارتباط کلاینت/سرور در فهرست دیالوگ 7 گزارش میگردند. در صورت وقوع خطای اتصال، دیالوگ کلاینت/سرور بسته شده و فرم به وضعیت اولیه خود بازمیگردد تا برای اتصال جدید آماده باشد.
8.5.4.5. TRAVAIL یک FAIRE
ابتدا کار فوق را به عنوان یک برنامه مستقل پیادهسازی کرده و سپس به عنوان یک اپلت.