# برنامه نویسی با محصولات مایکروسافت > برنامه نویسی مبتنی بر Microsoft .Net Framework > Silverlight > آموزش: نوشتن یک چت روم  در سیلورلایت (مقدمه ای بر WCF Duplex Communication)

## mahdi7s

سلام خدمت دوستان گرامی

 در طول این آموزش ما یک چت روم ساده در سیلورلایت خواهیم ساخت که بستر ارتباطاتش WCF خواهد بود

* - Duplex Communication (ارتباط دو طرفه) :*
 اغلب ارتباطات بین یک سرور و کلاینت با یک درخواست از طرف کلاینت آغاز و با پاسخ سرور به اتمام می رسد
این   در حالی است که برخی از مواقع سرور می خواهد اطلاعاتی را به کلاینت(ها)   ارسال کند که این می شود در خواستی از طرف سرور برای ارسال اطلاعات به   کلاینت. و ارتباط دو طرفه یعنی زمانی که هم سرور و هم کلاینت هر دو بتوانند   از یک دیگر درخواست و به یک دیگر پاسخ دهند.
اما   ارتباط دو طرفه تحت پروتکل HTTP غیر ممکن است : زیرا پروتکل HTTP بر پایه   یک در خواست از طرف کلاینت و سپس پاسخی از طرف سرور است بنابراین در این   پروتکل اگر کلاینت درخواستی ندهد سرور نمی تواند پاسخی بدهد.

پس چگونه یک ارتباط دو طرفه تحت HTTP داشته باشیم؟

*- Polling Duplex Binding در WCF :*
 این نوع Binding در WCF معرفی شد تا بتواند یک ارتباط دو طرفه را شبیه سازی کند(البته پروتکل Polling Duplex مخصوص WCF نیست).  نحوه ی کار این پروتکل اینگونه است که کلاینت مستمرا" درخواست هایی به  سرور می فرستد(Poll) تا از موجود بودن یا نبودن اطلاعات جدید بر روی سرور  برای خودش(کلاینت) آگاهی پیدا کند و اگر اطلاعات موجود بود با درخواست  کلاینت آن اطلاعات توسط سرور به کلاینت ارسال می شوند.

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

 MS Visual Studio 2010
 VS Silverlight 4 Tools (دانلود)

 - شروع کنیم؟!

 VS  را باز کنید بر روی New Project کلیک کنید و در پنجره باز شده در قسمت  Installed Templates بر روی Silverlight کلیک کنید و در قسمت راست بر روی  Silverlight Application کلیک و یک نام برای پروژه بنویسید(من گذاشتم  SLSampleChat) و OK را بزنید سپس در قسمت Options پنجره New Silverlight  Application که باز می شود Silverlight 4 را انتخاب و Enable WCF RIA  Services را تیک بزنید و OK کنید.

 وقتی برای اولین بار  پروژه باز می شود تب مربوط به طراحی MainPage باز می شود(این همان صفحه  اصلی ای هست که کاربر در مروگرش می بیند).
 یک TextBlock (برای نمایش  گفتگوها) و یک TextBox (برای تایپ متن) و یک دکمه (برای ارسال متن) به  ترتیب با نام های txtLocalMessages و txtMessage و btnSend بر روی MainPage  تان قرار دهید یا می توانید تگ Grid را به شکل زیر ویرایش کنید:

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TextBlock x:Name="txtLocalMessages" Grid.ColumnSpan="2" />
        <TextBox x:Name="txtMessage" Grid.Row="1" />
        <Button x:Name="btnSend" Grid.Column="1" Grid.Row="1" Content="Send" />
    </Grid> 
 در  Solution Explorer بر روی پروژه SLSampleChat.Web راست کلیک کرده و Add  -> New Item را انتخاب کنید. در پنجره باز شده در سمت راست(Installed  Templates) بر روی Silverlight کلیک کنید و در سمت چپ آیتم  Silverlight-enabled WCF Service را انتخاب و نام آن را ChatService.svc  بگذارید و بر روی دکمه Add کلیک کنید.

ChatService.svc را باز کنید و صفت زیر را بالای کلاس ChatService قرار دهید(زیر دو صفت دیگر):
 
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]   صفت فوق تعیین کننده ی رفتار و چگونگی اجرای سرویسی (در اینجا ChatService) است که با آن مزین شده.
 مقدار  Single که به خصیصه InstanceContextMode اختصاص داده شده بیانگر این است  که سرویس ما فقط یکبار ساخته می شود و کلاینت(ها) از آن استفاده می کنند.
 و مقدار Multiple که به خصیصه ConcurrencyMode اختصاص داده شده باعث می شود سرویس ما MultiThreading (چند نخی) رو پشتیبانی کنه. 

 یک رابط (Interface) با نام IChatNotifies به پروژه SLSampleChat.Web اضافه کنید و آن را به شکل زیر ویرایش کنید:


    [ServiceContract]  
   public interface IChatNotifies  
   {  
     [OperationContract(IsOneWay = true)]  
     void OnMessage(string message);  
   }  

 در  ادامه شما خواهید دید که ما برای با خبر ساختن کلاینت ها از پیامهای  دریافتی در سرور از رابط فوق استفاده خواهیم کرد و در اصل کلاس فوق نقش یک  CallBack (همان Event های امروزی) را اجرا می کند.

به کلاس ChatService برگردید و صفت ServiceContract را به شکل زیر ویرایش کنید:
 
این  باعث میشه سرویس ما رابطی که CallBack های ما را معرفی کرده بشناسد و در  اصل قابلیت Duplex Communication (ارتباط دو طرفه) را به سرویس اضافه کند و  کلاینت ها را هم قادر به گوش دادن به این CallBack ها می سازد.


 [ServiceContract(Namespace = "", CallbackContract = typeof(IChatNotifies))]  
سپس فیلد زیر را به سرویس تان اضافه کنید:

 
private SynchronizedCollection<IChatNotifies> m_Clients = new SynchronizedCollection<IChatNotifies>();   
این  فیلد کلاینت هایی که به سرور معرفی می شوند را نگهداری می کند.(از نوع  SynchronizedCollection استفاده شده چون Thread Safe است و کد را پیچیده  نمی کند)

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


     [OperationContract(IsOneWay = true)]  
     public void Subscribe()  
     {  
       var newClient = OperationContext.Current.GetCallbackChannel<IChatN  otifies>();  
       m_Clients.Add(newClient);  
     }  
 
متد  GetCallbackChannel  یک کانال (از نوع IChatNotifies) مربوط به کلاینتی که  متد Subscribe را صدا زده بدست می آورد تا ما به وسیله ی آن اطلاعات را از  سرور به کلاینت ارسال کنیم.

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

         [OperationContract(IsOneWay = true)]
        public void SendMessage(string message)
        {
            foreach (var client in m_Clients)
            {
                client.OnMessage(message);
            }
        } 
متد فوق هم پیام دریافتی را مستقیما به تمام کلاینت ها ارسال می کند.

بر روی پروژه SLSampleChat.Web راست کلیک کرده و Add Reference را انتخاب کنید سپس در پنجره باز شده

روی تب Browse کلیک کنید و به آدرس

C:\Program  Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Server بروید (این آدرس  می تواند در سیستم شما کمی متفاوت باشد) حالا فایل  System.ServiceModel.PollingDuplex.dll را انتخاب کنید و بر روی OK کلیک  کنید.

فایل پیکربندی Web.config را باز کنید و تگ system.serviceModel را به شکل زیر ویرایش کنید:


  <system.serviceModel>  
   <extensions>  
    <bindingExtensions>  
     <add name="pollingDuplex"  type="System.ServiceModel.Configuration.PollingDup  lexHttpBindingCollectionElement,  System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral,  PublicKeyToken=31bf3856ad364e35" />  
    </bindingExtensions>  
   </extensions>  
   <bindings>  
    <pollingDuplex>  
     <binding name="myPollingDuplex"  duplexMode="MultipleMessagesPerPoll" inactivityTimeout="00:05:00"  receiveTimeout="00:05:00" serverPollTimeout="00:05:00" />  
    </pollingDuplex>  
   </bindings>  
   <services>  
    <service name="SLSampleChat.Web.ChatService">  
     <endpoint address="" binding="pollingDuplex"  bindingConfiguration="myPollingDuplex"  contract="SLSampleChat.Web.ChatService" />  
     <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />  
    </service>  
   </services>  
   <behaviors>  
    <serviceBehaviors>  
     <behavior name="">  
      <serviceMetadata httpGetEnabled="true" />  
      <serviceDebug includeExceptionDetailInFaults="false" />  
     </behavior>  
    </serviceBehaviors>  
   </behaviors>  
   <serviceHostingEnvironment aspNetCompatibilityEnabled="true"  
    multipleSiteBindingsEnabled="true" />  
  </system.serviceModel>  
 
پیکربندی  فوق در ابتدا Polling Duplex Binding رو تعریف می کند(چون این نوع Binding  به صورت پیش فرض تعریف نشده است) سپس یک PollingDuplexBinding ایجاد و آن  را به Binding سرویس مان (ChatService) اختصاص می دهد.

(Binding خصوصیات ارتباط بین سرور و کلاینت یک سرویس را مشخص می کند(از جمله نوع پروتکل استفاده شده برای ارتباط))

از  منوی Build گزینه ی Build Solution را انتخاب کنید و پس از Build شدن (اگر  در این قسمت به خطایی برخورد کردید یکی از مراحل فوق را اشتباه انجام داده  اید و ابتدا باید آن خطا را برطرف کنید) بر روی پروژه ی SLSampleChat راست  کلیک کرده و Add Service Reference را بزنید. در پنجره باز شده بر روی  دکمه Discover کلیک کنید و سپس بر روی OK کلیک کنید.

به محیط طراحی MainPage بروید و با کلیک بر روی دکمه Send رویدا کلیک آن را هندل کنید.

در بالای کلاس MainPage فضای نام SLSampleChat.ServiceReference1 را using کنید و سپس فیلد زیر را به این کلاس اضافه کنید:


 private ChatServiceClient m_ServiceContext = new  ChatServiceClient(new  PollingDuplexHttpBinding(PollingDuplexMode.Multipl  eMessagesPerPoll), new  EndpointAddress("../ChatService.svc"));
بوسیله ی این فیلد ما می توانیم سرویسی که در سرور ساخته ایم را صدا بزنیم.

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

public MainPage()  
     {  
       InitializeComponent();  
       m_ServiceContext.OnMessageReceived += OnMessageReceived;  
       m_ServiceContext.SubscribeAsync();  
     }  

 
کد فوق رویداد دریافت پیام از سرور را هندل و کلاینت را به سرور معرفی می کند.

سپس متد زیر را به کلاس اضافه کنید:


     private void OnMessageReceived(object s, OnMessageReceivedEventArgs e)  
     {  
       var msg = e.Error == null ? e.message : e.Error.Message;  
       txtLocalMessages.Text += msg + "\r\n";  
     }  
 
کد بالا چک می کند اگر خطایی هنگام دریافت پیام رخ داده باشد آن را نمایش دهد و در غیر اینصورت پیام را نمایش می دهد.

و نهایتا در متد کلیک دکمه Send کد زیر را وارد کنید:

if (!string.IsNullOrEmpty(txtMessage.Text))  
         m_ServiceContext.SendMessageAsync(txtMessage.Text)  ;  
 
کد فوق هم در صورت خالی نبودن پیام آن را به سرور ارسال می کند تا سرور آن را به کلاینت ها ارسال کند.

چت  ما ساخته شد! برای اجرا بر روی دکمه Debug کلیک کنید سپس آدرس صفحه باز  شده در مرورگرتان را کپی کنید و در تب یا مرورگری دیگر به آن آدرس بروید و  خودتان با خودتان چت کنید!

   دوستان سوالات و مشکلات و تجربیات و ... خود را در این زمینه می توانند همینجا یا به صورت کامنت در وبلاگ من مطرح کنند.

***هرگونه استفاده از این آموزش بدون ذکر منبع (وبلاگ من) ممنوع می باشد.
-------------------------------------
دانلود سورس برنامه
------------------------------------------------
بیشترین منابع استفاده شده:
فیلم آموزشی در مورد Duplex Communication
کتاب SL Data & Services
و ...
------------------------------------------------------------------
موفق باشید

----------


## taghvajou

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

سوال اول: واسه چی از اینترفیس استفاده کردین؟ چرا از یه کلاس معمولی استفاده نکردین؟

----------


## mahdi7s

سلام خدمت دوست عزیز آقای تقواجو




> واسه چی از اینترفیس استفاده کردین؟ چرا از یه کلاس معمولی استفاده نکردین؟


فکر کنم منظورتان رابط IChatNotifies باشد. این رابط نقش Callback Contract را در سرویس دو طرفه ما دارد و هر متدیی که داخل این رابط معرفی شود در اصل یک رویداد در کلاینت خواهد بود(برای مثال متد OnMessage در این رابط می شود همان رویداد OnMessageReceived که ما در کلاینت آن را هندل کردیم) و چون ما نمی تونیم یک رویداد را در یک سرویس WCF ایجاد کنیم بنابراین برای با خبر کردن کلاینت ها از پیام جدید در سرور از یک Callback استفاده می کنیم اما چرا از یک کلاس استفاده نکردم؟ معلوم است چون شما فقط دارید رویدادی را تعریف می کنید تا کلاینت بتواند آن را هندل کند(اگر این کار را در WPF یا ویندوز فرم انجام دهید خواهید دید که شما باید این رابط را در کلاینت پیاده سازی کنید اما SL اینکارو خودش انجام داده و همین باعث سادکی کار شده) و وقتی متد ما(callback) کدی ندارد و کلاس آن باید در کلاینت پیاده سازی شود مایکروسافت دلیلی نمی بیند که آن را یک class تعریف کنیم

موفق باشید :چشمک:

----------


## taghvajou

سلام به همه 
و سلام مخصوص به مهدی جان
بنده تقوی جو هستم مثل عیسی و موسی نه تقواجو! :گریه: 

ممنون که به سوال دوم هم پیش پیش پاسخ دادین: کال بک چیست و چه کاربردی داره؟

ولی سوال سوم:
انواع دوپلکس کامیونیکیشن و مقایسه اونها؟

----------


## mahdi7s

یک بار نوشتم پرید :خیلی عصبانی: 

نمیشه گفت چند نوع Duplex Communication داریم چون این Duplex Communication به معنای یک ارتباط دو طرفه بین دو یا چند رایانه هست و شما هر ارتباط دو طرفه ای می بینید یک Duplex Communication است.

یک ارتباط دو طرفه می تواند تحت پروتکل های متفاوتی انجام شود. مثلا پروتکل Polling Duplex یا Tcp (با استفاده از سوکت ها) یا استفاده از مقید net.Tcp (که در SL 4 اضافه شده) که در این آموزش من Polling Duplex را انتخاب کردم.

برای مقایسه هم عکس ضمیمه به صورت حالت های متفاوت Polling Duplex را با net.Tcp Binding در SL مقایسه کرده.

موفق باشید جناب تقو*ی* جو

----------


## xamfia

ممنون بابت مطلب مفیدتان امیدوارم تعداد کم تشکرها باعث نشه شما این تاپیک رو ادامه ندین!  :چشمک:

----------


## mahdi7s

خیلی ممنون

واقیتش قرار بود کمی بیشتر در این مورد بنویسم اما واقعا زمان بسیار محدودی دارم.
ولی در حال نوشتن مطلبی به عنوان ادامه برای همین مطلب هستم که معلوم نیست کی و کجا و با چه عنوانی به پایان برسد(یا نرسد!)

موفق باشید.

----------


## vvorojakk

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

*Parser Error Message:* It is an error to use a section registered as  *allowDefinition='MachineToApplication'* beyond application level.  This error can  be caused by a virtual directory not being configured as an application in  IIS.


*Source Error:* 

Line 40:     </behaviors>
Line 41: 
Line 42:     <serviceHostingEnvironment aspNetCompatibilityEnabled="false"
Line 43:       multipleSiteBindingsEnabled="true" />
Line 44: 

لطفا راهنمایی کنید چطوری برطرفش کنم؟

----------


## vvorojakk

اقای mehdi7s شما علت ایجاد این خطا رو نمی دونید؟

----------


## mahdi7s

سلام

اگر برنامه را بر روی IIS هاست کردید ... جواب نمی دهد چون پیکربندی لازم را ندارد

اگر در VS اجرایش کردید احتمالا زمان inactivityTimeout به پایان رسیده

----------


## hamidhws

سلام



> اگر برنامه را بر روی IIS هاست کردید ... جواب نمی دهد چون پیکربندی لازم را ندارد


ممنون از شما دوست عزیز
میشه لطفا پیکربندی لازم برای IIS 7 رو یه توضیح بدید؟ ممنون میشم




> اگر در VS اجرایش کردید احتمالا زمان inactivityTimeout به پایان رسیده


یعنی اگه برنامه رو چند دقیقه رها کنیم دیگه عمل نمیکنه؟ چطوری میشه کاری کرد که این محدودیت ها رفع بشه؟
چطوری میشه کاری کرد که بدون مشکل اجرا بشه و از رسیدن اطلاعات به بقیه کلاینت ها اطمینان حاصل کرد؟



از دوستانی که اطلاعی در مورد عملکرد دوطرفه دارن خواهش میکنم راهنمایی کنن و جواب سوال های بالای منو بدید تورو خدا کارم گیره

----------


## davoodrm666_666

در IIS اجرا نمي شود البته با تغييرات لازم براي IIS

----------


## hamidhws

> در IIS اجرا نمي شود البته با تغييرات لازم براي IIS


ببخشید میشه بیشتر توضیح بدید؟ یعنی کلا نمیشه از این روش تحت وب استفاده کرد؟

من آخر نفهمیدم میشه یا نمیشه!
آخه یجا میفرمایید اجرا نمیشود 
پشتش میفرمایید با تغییرات لازم ...


حالا آخرش میشه یا نمیشه ؟

اگه میشه و احتیاج به تغییرات داره که منم همین تغییراتو میخوام ! چه تغییراتی باید بدم؟ 
تورو خدا یکی یه کمکی کنه بگه چیکار باید کرد
من تا حالا با IIS کار نکردم

----------


## hamidhws

از دوستان خواهش میکنم کمک کنن

----------


## hamidhws

راستی من قبلا یه برنامه نصب کردم که یادم نیست از کجا گرفتمش

به اسم iis express 7.5 

فکر کنم از بعد از نصب اون توی vs  توی قسمت packege/publish setting یه گزینه هایی اضافه شد

نمیدونم اینا قبلا نصب این برنامه هم بوده یا نه ؟
Untitled.jpg

----------


## mahdi7s

سلام



> میشه لطفا پیکربندی لازم برای IIS 7 رو یه توضیح بدید؟ ممنون میشم


مشکل شما چیه؟ خطایی؟ عددی؟ کاملتر توضیح بدید، بگید چه جوری هاست کردید و ... 




> یعنی اگه برنامه رو چند دقیقه رها کنیم دیگه عمل نمیکنه؟ چطوری میشه کاری کرد که این محدودیت ها رفع بشه؟


یک راه حل ساده فرستادن یک پیام الکی! هر چند دقیقه یکبار (قبل از این که innactivityTimeout به پایان تموم بشه) به سرور هست!
البته این راه حل هر چند برای خودم هست و تا مدتی هم تایید شد ولی راه حلی نیست که مشکلات long polling را کامل برطرف کند و هیچ چیزی را تضمین نمی کند (راه حل منطقی و بهتر را در پیام خصوصی تا حدودی برایتان توضیح دادم و بیشتر از اون را حتما" تاپیک یا پست جدیدی در وبلاگم خواهم زد)

در ضمن IIS 7.5 برروی ویندوز 7 موچوده و نیازی به نصب نسخه express نیست

موفق باشید

----------


## hamidhws

سلام



> مشکل شما چیه؟ خطایی؟ عددی؟ کاملتر توضیح بدید، بگید چه جوری هاست کردید و ...


اول تشکر میکنم از کمک های شما دوست عزیز
راستش هنوز روی iis نبردم (یجورایی میخوام از مشکل هایی که بعد ممکنه پیش بیاد پیشگیری کنم) تا حالا هم با iis کار نکردم آموزشی هم از هاست کردن سیلورلایت روی  iis  توی اینترنت پیدا نکردم (هرچی پیدا کردم در مورد mime type بود).. اگه توی این زمینه هم کمکم کنید ممنون میشم ... قبلا هم توی سایت این سوالو مطرح کردم اما به جواب نرسید! اصلا نمیدونم ربطی به publish داره یا چجوریه! در واقع من برنامه نویس تحت ویندوزم و این اول تجربه تحت وب منه و اولین باره که با iis کار میکنم




> البته این راه حل هر چند برای خودم هست و تا مدتی هم تایید شد ولی راه حلی نیست که مشکلات long polling را کامل برطرف کند و هیچ چیزی را تضمین نمی کند (راه حل منطقی و بهتر را در پیام خصوصی تا حدودی برایتان توضیح دادم و بیشتر از اون را حتما" تاپیک یا پست جدیدی در وبلاگم خواهم زد)


تورو خدا این آموزشو قرار بدید--- جالبه بدونید غیر از آموزش های مشابه شما در سایت های مختلف نتونستم آموزش فارسی دیگه ای در مورد wcf duplex پیدا کنم ! 




> در ضمن IIS 7.5 برروی ویندوز 7 موچوده و نیازی به نصب نسخه express نیست


ممنون دوست عزیز
یعنی اون عکسایی که از محیط vs  گذاشتم توی بقیه vs های دیگه هم هست؟
چون وقتی با این حالت برنامه رو اجرا میکنم یه شبیه ساز iis اجرا میشه
اگه کمی در این مورد توضیح بدید ممنون میشم

----------


## mahdi7s

> راستش هنوز روی iis نبردم (یجورایی میخوام از مشکل هایی که بعد ممکنه پیش  بیاد پیشگیری کنم) تا حالا هم با iis کار نکردم آموزشی هم از هاست کردن  سیلورلایت روی  iis  توی اینترنت پیدا نکردم (هرچی پیدا کردم در مورد mime  type بود).. اگه توی این زمینه هم کمکم کنید ممنون میشم ... قبلا هم توی  سایت این سوالو مطرح کردم اما به جواب نرسید! اصلا نمیدونم ربطی به publish  داره یا چجوریه! در واقع من برنامه نویس تحت ویندوزم و این اول تجربه تحت  وب منه و اولین باره که با iis کار میکنم


هاست کردن سیلورلایت می شود همان هاست کردن پروژه سرور(وب سایت مرتبط داخل solution) که اینجا کامل آموزش داده.




> یعنی اون عکسایی که از محیط vs  گذاشتم توی بقیه vs های دیگه هم هست؟
> چون وقتی با این حالت برنامه رو اجرا میکنم یه شبیه ساز iis اجرا میشه
> اگه کمی در این مورد توضیح بدید ممنون میشم


نه نیست، IIS express همانطور که گفتید IIS اصلی رو شبیه سازی می کنه تا شما بتونید هنگام *دیباگ* کردنپروژه از قابلیت های IIS که در Cassini موجود نیست(مثل SSL) استفاده کنید ولی ربطی به هاست کردن پروژه داخل IIS اصلی داخل خود ویندوز ندارد.

موفق باشید

----------


## mehrsa_fr

سورس برنامه وجود ندارد

----------

