در این قسمت از آموزش برنامه نویسی شبکه در C# یک برنامه Client و یک برنامه Server تحت Consol خواهیم نوشت که این دوبرنامه برای برقراری ارتباط با هم از پروتکل TCP استفاده می کنند. در واقع این دو برنامه از طریق یک سوکت TCP با هم ارتباط برقرار خواهند کرد و برنامه Client یک رشته متنی Hello Server! را به Server ارسال کرده و Server بعد از دریافت این رشته، دوباره همین رشته را به Client برمی گرداند.
توجه: برای اجرای برنامه ابتدا برنامه Server و سپس برنامه Client را اجرا کنید.
وقتی دو برنامه تحت شبکه از طریق پروتکل TCP با هم ارتباط برقرار می کنند مثل این است که کانالی بین آنها بوجود می آید. این کانال ارتباطی تا وقتی وجود خواهد داشت که یکی از دو برنامه ارتباط را قطع کند. ارسال اطلاعات بین دو برنامه از طریق همین کانال صورت می گیرد. برنامه ارسال کننده اطلاعات لازم نیست در هر بار ارسال اطلاعات آدرس مقصد را به همرا ه بسته ارسال کند، زیرا این کانال ارتباطی به طور منطقی به مقصد متصل است. علاوه بر آن این کانال یک پل ارتباطی قابل اعتمادی را ایجاد می کند به طوری که توالی بایت های دریافت شده در مقصد همان توالی بایت هایی است که فرستنده در کانال قرار داده است. به همین دلیل پروتکل TCP را پروتکل اتصال گرا (Connection Oriented) می نامند.
تشریح فضاهای نام و کلاس های مورد نیاز
محیط کاری .NET دو کلاس مخصوص TCP فراهم می کند که عبارت اند از TcpClient و TcpListener. نمونه ای و یا objectی از هر یک از این کلاس ها، یک طرف اتصال TCP را مشخص می کند. در اتصال TCP سمت مقابل (برنامه ای بر روی کامپیوتر مقابل) توسط یک آدرس IP و یک شماره پورت مشخص می شود. برقراری ارتباط به این صورت است که TCP سرویس گیرنده یک تقاضای اتصال به TCP سرویس دهنده ارسال می کند و در این سمت objectی از کلاس TcpListener به تقاضا های اتصال TCP گوش فرا می دهد و با ورود اولین درخواست، یک سوکت TCP بین خودش و سرویس گیرنده ایجاد می کند.
تشریح برنامه سرویس گیرنده (TCPEchoClient)
در برنامه سرویس گیرنده از فضای نام System.IO برای IOExceptionها و از فضای نام System.Net.Sockets برای استفاده از دو کلاس TCPClient و NetworkStream و همچنین مدیریت استثنا های SocketException استفاده شده است.
توسط objectی از کلاس TCPClient می توان یک سوکت در سمت Client برای برقراری ارتباط با Server ایجاد کرد. در هنگام ساختن objectی از این کلاس می توان نام Server و شماره پورتی که سرور مشغول گوش دادن به آن است را مشخص کرد. کلاس بعدی کلاس NetworkStream می باشد که توسط objectی از این کلاس و همچنین توسط متد GetStream() از کلاس TCPClient می توان به کانال ارتباطی که بعد از برقرای اتصال بین سرویس گیرنده و سرویس دهنده برقرار می شود دسترسی داشت. از این پس می توان با استفاده از متد های Write(…) و Read(..) مربوط به کلاس NetworkStream اطلاعات را در کانال نوشت و یا از آن خواند. اطلاعاتی که قرار است در کانال به منظور ارسال به سمت مقابل قرار گیرد باید به صورت یک آرایه بایتی باشد.
تشریح کدهای برنامه سرویس گیرنده
همانطور که گفته شد این برنامه یک برنامه Consol Application می باشد و در ذیل کدهای تابع Main() برنامه آورده شده است که به تشریح این کدها خواهیم پرداخت.
کد:
1 static void Main(string[] args)
2 {
3 TcpClient client = null;
4 NetworkStream netStream = null;
5 try
6 {
7 // create socket that is connected to server on specified port
8 client = new TcpClient("127.0.0.1", 8000);
9 netStream = client.GetStream();
10 byte[] byteBuffer = Encoding.ASCII.GetBytes("Hello Server!");
11 // send the encoded string to the server
12 netStream.Write(byteBuffer, 0, byteBuffer.Length);
13 Console.WriteLine(byteBuffer.Length + " bytes sent to server.");
14 int totalBytesRcvd = 0; // total bytes received so far
15 int byteRcvd = 0; // bytes received in last read
16 // receive the same string back from the server
17 while (totalBytesRcvd < byteBuffer.Length)
18 {
19 if ((byteRcvd = netStream.Read(byteBuffer,totalBytesRcvd,
20 byteBuffer.Length - totalBytesRcvd))== 0)
21 {
22 Console.WriteLine("Connection closed.");
23 break;
24 } // end if
25 totalBytesRcvd += byteRcvd;
26 } // end while
27 Console.WriteLine(byteBuffer.Length +
28 " bytes received from server >>> " +
29 Encoding.ASCII.GetString(byteBuffer,0,byteBuffer.Length));
30 } // end try
31 catch (Exception err)
32 {
33 Console.WriteLine(err.ToString());
34 } // end catch
35 finally
36 {
37 netStream.Close();
38 client.Close();
39 Console.ReadKey();
40 } // end finally
41 } // end method main
در خط شماره 3 یک شی به نام client از کلاس TcpClient می سازیم که از این شی برای برقراری ارتباط با سروری که شماره IP و شماره پورت آن را می دانیم استفاده می کنیم. همانطور که گفتیم، وقتی که دو کامپیوتر از طریق TCP با هم اتباط برقرار می کنند، بین آنها یک کانال ارتباطی برقرار می شود که این کانال تا زمانی که یکی از دو کامپیوتر ارتباط را قطع نکرده باشند برقرار است. در خط شماره 4 یک شی به نام netStream از کلاس NetworkStream ساخته می شود تا بعد از برقراری اتصال با سرور بتوانیم توسط این شی از کانال ارتباطی استفاده کنیم.
در خط شماره 8 شی client را توسط سازنده کلاس TcpClient که دو پارامتر دریافت می کند مقدار دهی می کنیم. پارامتر اول سازنده، نام کامپیوتر مقصد (Server) و پارامتر دوم، شماره پورت در کامپیوتر مقصد است که برنامه Server در حال گوش دادن به آن پورت می باشد. در واقع با این دستور یک سوکت بین Client و Server ساخته می شود. در خط شماره 9 توسط متد GetStream() شی client همان کانال ارتباطی که از آن صحبت کردیم را در دست گرفته و به شی netStream اختصاص می دهیم. حال می توان توسط متد های Write(…) و Read(…) شی netStream در این کانال اطلاعات را بنویسیم و یا از آن بخوانیم. برای ارسال اطلاعات در NetworkStream باید اطلاعات در قالب یک آرایه ای از نوع byte باشد، به همین منظور در خط شماره 10 یک آرایه از نوع byte تعریف کرده و توسط دستور Encoding.ASCII.GetBytes("Hello Server!") رشته Hello Server! را به صورت آرایه ای از بایت ها تبدیل می کنیم.
در خط شماره 12 توسط متد Write(…) شی netStream اطلاعات را در کانال می نویسیم و در سمت مقابل برنامه Server می تواند توسط متد Read(…) اطلاعات را دریافت کند. پارامتر اول دستور Write(…) آرایه بایتی را مشخص می کند که قرار است اطلاعات آن ارسال شود، پارامتر دوم تعیین می کند که از کدام محل از آرایه شروع به ارسال اطلاعات کند، و پارامتر سوم تعیین کننده مقدار اطلاعاتی است که باید ارسال شود.
همانطور که گفتیم برنامه Server متن ارسالی توسط Client را دریافت کرده و همان متن را دوباره به سمت Client ارسال می کند. در این برنامه برای دریافت متن ارسالی از سمت Server، در خطوط شماره 17 الی 26 از یک حلقه while استفاده می کنیم. زمانی که Server فرایند ارسال خود را به پایان رساند، مقدار بازگشتی از متد Read(…) در شرط داخل حلقه، مقدار صفر خواهد بود و مقدار برگشتی صفر به معنی اتمام فرایند ارسال از سمت Server می باشد. و در نهایت در خط شماره 29 اطلاعات دریافتی را از حالت کد شده به فرمت متنی تبدیل کرده و در خروجی نمایش می دهیم.
در اتمام کار هر اتصال و ارتباط شبکه ای بایستی کلیه منابعی را که برای برقراری ارتباط اشغال کرده ایم آزاد کنیم و این کار توسط دستورات بلاک finally از برنامه صورت می گیرد.
تشریح برنامه سرویس دهنده (TCPEchoServer)
در نسخه Server از این برنامه از فضای نام System.Net.Sockets به منظور استفاده از کلاس های TcpListener، TcpClient و NetworkStream و همچنین به منظور مدیریت استثناء های SocketException استفاده شده است.
توسط objectی از کلاس TcpListener برنامه Server می تواند به درخواست های اتصال ورودی TCP به Server گوش فرادهد. در هنگام تعریف شی از این کلاس می توان یک واسط محلی (یک کارت شبکه بر روی کامپیوتر Server) و یک شماره پورت را جهت گوش دادن Server به اتصال های ورودی مشخص کرده و توسط متد Start() از این کلاس می توان فرایند گوش دادن را آغاز کرد. بعد از این، توسط متد AcceptTcpClient() از کلاس TcpListener که خروجی آن از نوع کلاس TcpClient() می باشد می توان اتصال های ورودی از سمت سرویس گیرنده ها را دریافت کرد و در ادامه با استفاده از متد GetSteam() می توان به کانال ارتباطی که بین سرویس گیرنده و سرویس دهنده ایجاد شده است دسترسی داشت و به ارسال و دریافت اطلاعات پرداخت.
تشریح کدهای برنامه سرویس دهنده
برنامه Server نیز همانطور که گفته شد یک Consol Application می باشد که در زیر به تحلیل تابع
Main() برنامه می پردازیم
کد:
1 static void Main(string[] args)
2 {
3 byte[] byteBuffer = new byte[32]; // receive buffer
4 TcpListener listener = null;
5 try
6 {
7 // create a TcpListener to accept client connections
8 listener = new TcpListener(IPAddress.Any, 8000);
9 listener.Start();
10 }
11 // exit program, if server can not use port number 8000
12 catch (Exception err)
13 {
14 Console.WriteLine(err.ToString());
15 Environment.Exit(0);
16 }
17 int byteRcvd = 0; // received byte count
18 // run forever, accepting and servicing connections
19 for (; ; )
20 {
21 TcpClient client = null; // for each client connection
22 NetworkStream netStream = null; // for each connection stream
23 Console.WriteLine("Waiting for connections...");
24 try
25 {
26 client = listener.AcceptTcpClient(); // get client connection
27 netStream = client.GetStream(); // get connection stream
28 Console.WriteLine("Handling client...");
29 // receive until client closes connection,
30 // indicated by 0 return value
31 int totalByteRcvd = 0;
32 while ((byteRcvd = netStream.Read(byteBuffer, totalByteRcvd,
33 byteBuffer.Length - totalByteRcvd)) > 0)
34 {
35 netStream.Write(byteBuffer, 0, byteRcvd);
36 totalByteRcvd += byteRcvd;
37 } // end while
38 Console.WriteLine(totalByteRcvd +
39 " bytes received from client >>> " +
40 Encoding.ASCII.GetString(byteBuffer) +
41 "\r\n");
42 // close the stream and socket. we are done with this client!
43 netStream.Close();
44 client.Close();
45 } // end try
46 catch (Exception err)
47 {
48 Console.WriteLine(err.ToString());
49 netStream.Close();
50 } // end catch
51 } // end for
52 } // end main method
در خط شماره 3 یک آرایه از نوع بایت و با اندازه 32 تعریف می شود. برنامه Server از این آرایه برای ذخیره داده هایی که از Client دریافت می شود استفاده می کند. در خط شماره 4 تعریف اولیه شی listener از نوع کلاس TcpListener صورت می گیرد و در ادامه در خط شماره 8 از کد برنامه تعریف کامل این object انجام می شود. همانطور که مشاهده می شود سازنده کلاس TcpListener دو پارامتر دریافت می کند که پارامتر اول مشخص می کند که کامپیوتر Server به کدام کارت شبکه روی خودش گوش دهد. به عنوان مثال اگر دو عدد کارت شبکه با IPهای مختلف روی کامپیوتر Server وجود داشته باشد، با وارد کردن IP همان کارت شبکه مشخص می کنید که برنامه Server باید به درخواست های اتصال از کدام کارت شبکه توجه کند. و اما پارامتر دوم، این پارامتر مشخص می کند که برنامه Server به کدام پورت گوش دهد. این شماره پورت همان شماره پورتی است که برنامه Client باید شماره آنرا بداند تا بتواند به برنامه Server متصل شده و ارسال و دریافت Data انجام دهد. توجه داشته باشید که با این دستور ما در واقع یک سوکت در سمت Server ایجاد کرده ایم. در ادامه و در خط شماره 9 توسط متد start() از کلاس TcpListener فرایند گوش دادن به سوکت ایجاد شده آغاز می شود.
با مشاهده به کد برنامه ملاحضه می کنید که کل فرایند ساختن سوکت در سمت Server در خطوط 5 الی 16 از کد قرار دارد و توسط بلاک های try و catch خطاهای آن مدیریت می شود. سازنده TcpListener در صورت عدم موفقیت در ساخت سوکت استثناء های ArgumentNullException و ArgumentOutOfRangeException را صادر می کند. در واقع برنامه Server اگر نتواند سوکت خود را ایجاد کند دیگر نمی تواند به عنوان یک Server برای Clientها وظیفه خود را انجام دهد، به همین خاطر در بلوک catch و در صورت عدم موفقیت Server در ساختن سوکت توسط دستور Environment.Exit(0) به اجرای برنامه خاتمه داده می شود.
همانطور که مشاهده می شود کلیه کدهای خطوط 19 الی 51 درون یک حلقه بی پایان قرار دارند. توسط این دستورات متن ارسالی از Client دریافت شده و همان متن دوباره برای Client ارسال می گردد. این دستورات از این جهت در یک حلقه بینهایت قرار گرفته اند که برنامه Server بتواند تقاضاهای اتصال از چندین Client را مدیریت کند. یعنی تا وقتی که برنامه Server در حال اجرا است، با خاتمه یک برنامه Client می توانید باز هم برنامه Client دیگری را اجرا کنید. دقت داشته باشید که این برنامه در یک لحضه فقط می تواند به یک Client سرویس دهد.
در خطوط 21 و 22 یک شی از نوع TcpClient برای مدیریت هر اتصال دریافتی و یک شی NetworkStream برای در دست گرفتن Stream و یا همان کانال ارتباطی بین Client و Server تعریف شده است. در صورتی که تقاضایی از سمت Clientی برای اتصال به Server وجود نداشته باشد اجرای برنامه Server با رسیدن به خط شماره 26 و در واقع با رسیدن به دستور AcceptTcpClient() متوقف می شود، که به این عمل بلوکه شدن گویند. متد AcceptTcpClient() با دریافت اولین تقاضای اتصال از سمت Client از حالت بلوکه خارج شده و در خط شماره 27 توسط متد GetStream() از شی client کانال ارتباطی بین Server و Client برقرار می گردد و به شی netStream تخصیص داده می شود.
از این پس می توان کلیه اعمال ارسال و دریافت را توسط شی netStream انجام داد. در خطوط 32 الی 37 توسط یک حلقه while متن ارسالی از سمت Client توسط متد Read(…) از شی netStream دریافت شده و توسط متد Write(…) دوباره به Client ارسال می گردد. در خطوط 43 و 44 توسط متد Close() شی های client و netStream ، ارتباط و اتصال بین Server و Client قطع می گردد و دوباره دستورات حلقه for(; ; ) از ابتدا اجرا می شوند. یعنی در این حالت چنانچه Client دیگری منتظر اتصال بوده باشد، به همان ترتیب به آن پاسخ داده می شود و اگر تقاضایی نباشد دوباره Server به حالت بلوکه می رود.
نويسنده :
سیامک محمدی نژاد
کارشناس کامپیوتر، نرم افزار
علاقه مندی ها (Bookmarks)