ساخت چت روم با سی شارپ: قسمت دوم


ساخت چت روم با سی شارپ: قسمت دوم


در قسمت قبل آموزش ما یک سرور را در حالت listening قرار دادیم و همچنین بسته های دریافتی را تفکیک کردیم و آنها را به تابعی با نام handle_packet ارسال کردیم. در این بخش از آموزش ما بسته های مورد استفاده را تعریف میکنیم. ما به دو بسته نیاز داریم که به شکل زیر آنها را تعریف میکنیم.

بسته با شناسه 1

نوع بسته: کلاینت به سرور

مقادیر بازگشتی: ندارد

مقادیر ارسالی: متن چت همراه با نام ارسال کننده (از نوع رشته)

وظیفه: ارسال چت های کاربر به سرور

بسته با شناسه 2

نوع بسته: سرور به کلاینت

مقادیر بازگشتی: ندارد

مقادیر ارسالی: متن چت های سایر کاربران(از نوع رشته)

وظیفه : ارسال چت های سایر کاربران به کاربر

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

در ادامه اگر بسته ای که به سرور ارسال می شود با همان شناسه باید داده ای را برگرداند میگوییم که بسته مقدار بازگشتی دارد، به عنوان مثال بسته مربوط به ورود(لوگین) با شناسه 5 ، هنگامی که داده ها به سرور ارسال می شوند باید نتیجه آن با همان شناسه 5 بازگردد، البته در بسته هایی که ما در اینجا تعریف کرده ایم هیچکدام مقدار بازگشتی ندارند.

مقادیر ارسالی داده هایی هستند که بسته باید به مقصد برساند تعریف نوع و ترتیب داده ها بسیار مهم است به عنوان مثال برای نام از String و برای امتیاز از int استفاده می کنیم. و در نهایت وظیفه بسته که شامل تعیین دقیق کاری که بسته باید انجام دهد است.

حالا وقت آن است تا کدهای مربوط به دریافت چت و ارسال آن برای سایر کاربران را بنویسیم

 

try

            {

               

                using (BinaryReader reader = new BinaryReader(new MemoryStream(data)))

                {

                    int packet_id = reader.ReadUInt16();

                   

                    switch (packet_id)

                    {

                        case 1:

 

                            UInt16 chat_len = reader.ReadUInt16();

                            string chat_data = Encoding.UTF8.GetString(reader.ReadBytes(chat_len));

                           

                            using (BinaryWriter writer = new BinaryWriter(new MemoryStream()))

                            {

                                //packet data

                                writer.Write((ushort)0);

                                writer.Write((ushort)2);

                                writer.Write((ushort)chat_data.Length);

                                writer.Write(chat_data);

 

                                // write packet len at begining

                                writer.Seek(0, SeekOrigin.Begin);

                                writer.Write((ushort)writer.BaseStream.Length);

 

 

                                byte[] raw_data = ((MemoryStream)writer.BaseStream).ToArray();

 

                                for (int i = 0; i < list_users.Count-1; i++)

                                {

                                    if (list_users[i] != client) { send_packet(raw_data, list_users[i]); }

                                }

                            }

                            break;

                    }

 

                }

            }

            catch (Exception)

            {

 

               

          }

کد های بالا در تابع handle_packet قرار دارند همانطور که میدانید این تابع دو پارامتر دارد اولی داده های تفکیک شده و دوم کلاینتی که داده ها را ارسال کرده است، داخل این تابع ابتدا از try catch استفاده کرده ایم تا در صورت بروز هر گونه خطا در هنگام خواندن داده ها و یا ارسال آنها برنامه سرور ما بسته نشود. در ادامه از binaryreader برای خواندن داده ها استفاده کرده ایم، داخل بلاک binaryreader ابتدا شناسه پکت را که از نوع uint16  وارد کرده بودیم را میخوانیم سپس با استفاده از یک switch وظیفه بسته آمده را انجام میدهیم. در کد های بالا پیامی که از سمت کاربر به سرور ارسال می شود با شناسه یک است که باید برای سایر کلاینت ها ارسال شود، بنابراین ما در کیس 1 ، ابتدا طول چت کاربر و سپس متن چت را استخراج میکنیم(ما قبلا طول چت را در سمت کلاینت وارد کرده ایم که در جلسه بعد کامل شرح داده می شود) برای تبدیل بایت ها به رشته از کلاس encoding استفاده کرده ایم که بسته به نوع encoding (در اینجا utf8) داده ها را استخراج کرده ایم .

حالا باید داده های دریافت شده را به سایر کاربران ارسال کنیم پس یک binarywirter ایجاد میکنیم. در ابتدا چون طول بسته را نمیدانیم چقدر است یک مقدار 0 از نوع Ushort (با Uint16 برابر است) وارد میکنیم سپس شناسه بسته که 2 است و در ادامه طول پیام و در نهایت متن پیام، برای این که کلاینت بتواند بسته ها را از یک دیگر تفکیک کند باید طول بسته را در ابتدای بسته قرار دهیم ، از انجایی که ما در ابتدا طول بسته را 0 قرار دادیم باید آن را دوباره مقدار دهی کنیم(به دلیل اینکه ما در ابتدا نمیدانستیم طول بسته چقدر است اما حالا میدانیم). برای تغییر مقداری که در ابتدای بسته قرار داده ایم باید موقعیت فعلی رایتر را به ابتدای بسته تغییر دهیم برای اینکار از کد wirter.seek استفاده کرده ایم در پارامتر اول این تابع افست و در پارامتر دوم محل افست تعریف شده است که چون ما در اینجا قصد داریم به ابتدای بسته برویم افست ان را 0 و محل آن را از ابتدا در نظر گرفته ایم(اگر seekorigin را end قرار دهیم افست را از اخر بسته در نظر میگیرد)، در خط بعد ما طول بسته را باز نویسی کرده ایم.

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

نکته: این ابتدایی ترین حالت ارسال و دریافت است و ما در اینجا مسائل امنیتی و چند گره ای را در نظر نگرفته ایم.

نکته 2: برای درک بهتر binaryreader و  binarywriter توصیه میشود حتما رفرنس های مایکروسافت را مطالعه کنید.

نکته3: از انجایی که نوع داده ای مانند int16،int32،float و مانند آن طول ثابت دارند ما نیازی به وارد کردن طول آنها قبل از مقدار آنها نداریم، در کد های بالا ما برای ارسال و دریافت رشته (که هیچ طول ثابتی ندارد) ابتدا طول رشته را در یک نوع  ushort قرارداده ایم و سپس متن رشته را (در ارسال رشته بین سی شارپ و گیم میکر باید از این راه استفاده شود)، همچنین باید به خاطر داشته باشید ما در اینجا متن های انگلیسی را جابه جا میکنیم که طول هر کاراکتر آن 1 بایت است اما متن های فارسی و سایر زبان ها طولشان 2 بایت است که باید از encoding از نوع  utf8 استفاده شود.

در قسمت بعد نوشتن کلاینت را توضیح خواهیم داد.

 

 


    نظرات


    نام: cloner

    4 آذر 1397
    salam khaste nabashid key in client ro minevisid?? mn vaghean lazemesh daram541
    نام: hadi eb

    4 آذر 1397
    با سلام خوشحالم که آموزش ها براتون مفید بوده انشالله تا اخر این ماه آماده میشه بلکه هر چه زودتر






ارسال نظر




رفتن به بالا