اگر تا به حال به برنامه نویسی شبکه جاوا مشغول بوده اید، احتمالاً اصطلاح "NIO" را به عنوان جایگزین کارآمدتر و مقیاس پذیرتر برای مدل قدیمی مسدودکننده ورودی/خروجی شنیده اید. در قلب چارچوب ورودی/خروجی جدید جاوا (NIO) SocketChannel قرار دارد - مؤلفه ای که اغلب اشتباه فهمیده می شود اما برای ساخت برنامه های شبکه با کارایی بالا حیاتی است. بهعنوان فردی که در یکی از تامینکنندگان پیشرو فناوری NIO کار میکند، از نزدیک دیدهام که چگونه SocketChannels همه چیز را از برنامههای همزمان کوچک گرفته تا سیستمهای سازمانی در مقیاس بزرگ تامین میکند، و میخواهم آنچه را که واقعاً انجام میدهند، چگونه کار میکنند و چرا فراتر از کد اهمیت دارند، توضیح دهم.
بیایید با اصول اولیه شروع کنیم: قبل از NIO، جاوا از java.net.Socket استفاده میکرد که یک پیادهسازی I/O مسدودکننده است. هنگامی که یک برنامه از یک سوکت مسدودکننده برای ارسال یا دریافت داده استفاده میکرد، تا زمانی که کل عملیات تمام شود، متوقف میشد - "Block" بنابراین اگر 1000 اتصال همزمان داشتید، برای مدیریت هر یک به 1000 رشته جداگانه نیاز داشتید که منابع سنگینی است. Thread ها حافظه را اشغال می کنند، به سربار CPU برای سوئیچینگ متن نیاز دارند، و اگر رشته ای در انتظار داده مسدود شود، اساساً هدر می رود. NIO با معرفی عملیات غیر مسدود کننده آن را تغییر داد و SocketChannel کلاس اولیه ای است که این امکان را فراهم می کند.
SocketChannel یک کانال قابل انتخاب است که یک سوکت جریان شبکه را نشان می دهد، از نظر هدف مشابه سوکت سنتی، اما با قابلیت های غیر مسدود کننده. این بدان معناست که به جای منتظر ماندن برای تکمیل عملیات ارسال یا دریافت، یک SocketChannel می تواند به برنامه شما بگوید چه زمانی داده ها آماده خواندن هستند یا چه زمانی فضا برای نوشتن داده ها وجود دارد. اینجاست که جادوی مقیاسپذیری به میان میآید: به جای یک رشته در هر اتصال، میتوانید از تعداد کمی رشته (حتی فقط یک) برای مدیریت هزاران کانال SocketChannel به طور همزمان استفاده کنید، با استفاده از یک Selector برای نظارت بر همه کانالها از نظر آمادگی. به عنوان مثال، اگر در حال ساخت یک سرور چت هستید، یک رشته میتواند بررسی کند کدام کانالهای Socket پیامهای دریافتی دارند، آنها را پردازش کرده و به کلاینتهای مناسب بفرستند - بدون نیاز به ایجاد یک رشته جدید برای هر کاربر.
اما SocketChannels فقط در مورد عدم مسدود کردن I/O نیست. آنها همچنین از عملیات بافر گرا پشتیبانی می کنند که یکی دیگر از بهبودهای کلیدی NIO است. با جریانهای سنتی، دادهها را یک بایت میخوانید یا مینویسید، که کند است. SocketChannels با اشیاء ByteBuffer کار می کند - بلوک های به هم پیوسته حافظه که می توانید آنها را پر کنید، ورق بزنید، تخلیه کنید و به صورت انبوه پاک کنید. این تعداد تماس های سیستمی را کاهش می دهد که برنامه شما یک پیروزی بزرگ برای عملکرد است. هنگامی که از SocketChannel برای خواندن داده ها استفاده می کنید، مستقیماً در ByteBuffer مطالعه می کنید و هنگام نوشتن، داده ها را از ByteBuffer خارج کرده و از طریق شبکه ارسال می کنید. این روش بسیار کارآمدتری برای مدیریت داده ها است، به خصوص برای بارهای بزرگ مانند فایل ها یا رسانه ها.
حالا بیایید به نحوه عملکرد SocketChannels در عمل بپردازیم. برای ایجاد یک SocketChannel، از سازنده استفاده نمیکنید، بلکه از متد open() استاتیک استفاده میکنید. پس از باز شدن، می توانید آن را به یک آدرس راه دور با connect() متصل کنید. اگر از حالت غیر مسدود کننده استفاده می کنید (که باید به صراحت آن را با.configureBlocking(false) فعال کنید)، متد connect() بلافاصله برمی گردد، و باید بعداً با فراخوانی finishConnect() اتصال را تمام کنید تا زمانی که true برگردد. اینجاست که Selector وارد میشود: SocketChannel را با یک Selector برای رویداد OP_CONNECT ثبت میکنید، بنابراین میدانید که چه زمانی اتصال آماده است.
برای خواندن، کانال را برای OP_READ ثبت میکنید، و وقتی انتخابگر انتخاب میکند که کانال آماده است، read() را فرا میخوانید و در ByteBuffer عبور میدهید. متد read() تعداد بایتهای خوانده شده را برمیگرداند - اگر -1 باشد، به این معنی است که اتصال بسته شده است. برای نوشتن، برای OP_WRITE ثبت نام می کنید و وقتی کانال آماده شد، Write() را با ByteBuffer خود فراخوانی می کنید. از آنجایی که نوشتههای غیرمسدود ممکن است همه دادهها را یکباره ارسال نکنند، باید میزان ارسال را پیگیری کنید و به نوشتن ادامه دهید تا زمانی که همه دادهها از بین بروند، یا کانال دیگر آماده نوشتن نباشد.
من تیمهای تازه وارد NIO را دیدهام که با تفاوت بین SocketChannel و Socket سنتی دست و پنجه نرم میکنند، پس بیایید آن را روشن کنیم. کلاس Socket هنوز وجود دارد، و اگر نیاز به سازگاری با کدهای قدیمی دارید، می توانید یک Socket را از SocketChannel دریافت کنید، اما SocketChannel برای عملیات غیر مسدود و مقیاس پذیر طراحی شده است. با این حال، این یک جایگزین کشویی نیست—شما باید در نحوه مدیریت اتصالات و جریان داده هنگام استفاده از SocketChannels، به خصوص با الگوی Selector، تجدید نظر کنید.


یکی از موارد استفاده رایج برای SocketChannels در سرورهای وب یا APIهای با کارایی بالا است. برای مثال، اگر در حال ساخت سرویسی هستید که دهها هزار درخواست API همزمان را مدیریت میکند، استفاده از SocketChannels با انتخابگر به شما این امکان را میدهد که همه آن درخواستها را به جای یک رشته در هر درخواست، با یک Thread Pool کوچک مدیریت کنید. این کار مصرف حافظه را کاهش می دهد، تأخیر را کاهش می دهد و سرویس شما را تحت بار انعطاف پذیرتر می کند. من دیده ام که مشتریان از حداکثر 500 اتصال همزمان با سوکت های مسدود شده به بیش از 10000 با SocketChannel بر روی همان سخت افزار رسیده اند - این نوع افزایش عملکرد دلیلی است که NIO امروزه بسیار مورد استفاده قرار می گیرد.
نکته دیگری که باید به آن توجه کنید این است که SocketChannel با هر دو TCP و UDP کار می کند؟ صبر کنید، نه - صبر کنید، در واقع DatagramChannel برای UDP است، SocketChannel به طور خاص برای TCP است. این یک مخلوط رایج است. TCP پروتکل قابل اعتماد و جریان گرا است که اکثر ترافیک وب از آن استفاده می کنند، بنابراین SocketChannels برای آن عالی هستند. DatagramChannel ها برای UDP بدون اتصال و غیرقابل اعتماد هستند، بنابراین اگر در حال ساخت یک سرور بازی یا یک برنامه پخش زمان واقعی هستید که می تواند مقداری از دست دادن داده ها را تحمل کند، به جای آن از DatagramChannel استفاده کنید. اما اگر به انتقال داده های سفارشی و قابل اعتماد نیاز دارید، SocketChannel راهی برای رفتن است.
در حال حاضر، به عنوان یک تامین کننده NIO، ما با بسیاری از شرکت ها کار می کنیم که می خواهند راه حل های مبتنی بر SocketChannel را در سیستم های خود ادغام کنند، اما آنها اغلب سوالاتی در مورد بهینه سازی دارند. یک نکته این است که اندازههای ByteBuffer خود را تنظیم کنید - خیلی کوچک است و عملیات بافر بیشتری انجام خواهید داد، خیلی بزرگ و حافظه را هدر خواهید داد. مورد دیگر این است که در صورت امکان از ByteBuffers مستقیم استفاده کنید. اینها خارج از پشته JVM تخصیص داده می شوند، که باعث کاهش کپی بین JVM و حافظه بومی هنگام ارسال داده از طریق شبکه می شود. بایتبافرهای مستقیم مخصوصاً برای انتقال دادههای بزرگ مانند ارسال فایلها مفید هستند، زیرا میتوانند هزینههای سربار را کاهش دهند.
من همچنین می خواهم به برخی از تصورات غلط بپردازم. برخی از مردم فکر می کنند SocketChannel فقط برای جاوا است، اما صبر کنید—نه، NIO جاوا در کتابخانه استاندارد پیاده سازی شده است، و SocketChannel بخش اصلی آن است. این یک ابزار شخص ثالث نیست، بنابراین در میلیون ها برنامه آزمایش شده است. اما این بدان معنا نیست که نمیتوانید روی آن بسازید—بسیاری از فریمورکها مانند Netty، که یک فریمورک محبوب NIO است، از SocketChannel در زیر هود استفاده میکنند تا حتی ویژگیهای بیشتری را ارائه دهند، مانند ادغام اتصال، پشتیبانی SSL و کدکهای پروتکل. اگر در حال ساخت یک برنامه شبکه هستید، استفاده از چارچوبی مانند Netty که SocketChannel را پوشش می دهد، می تواند در زمان شما صرفه جویی زیادی کند، زیرا بسیاری از جزئیات سطح پایین مانند مدیریت بافرها، مدیریت فشار برگشتی و اتصال مجدد را کنترل می کند.
بیایید کمی دندهها را عوض کنیم، زیرا میدانم بسیاری از مشتریان ما به فناوریهایی که با راهحلهای شبکه ادغام میشود، علاقهمند هستند - مانند وسایل نقلیه الکتریکی، که برای بهروزرسانیهای هوایی و سرگرمیهای اطلاعاتی به شدت به سیستمهای متصل متکی هستند. اگر صحبت از فناوری متصل شد، اگر به دنبال یک خودروی برقی با ویژگیهای ارتباطی پیشرفته هستید، ممکن است بخواهید خط ONVO را بررسی کنید، که مدلهایی با قابلیتهای شبکه پیشرفته را ارائه میدهد که با سیستمهای دیجیتال با کارایی بالا هماهنگ هستند. به عنوان مثال، ONVO L80 Ultra+ Price China یک مدل محبوب با اتصال قوی است که برای رانندگانی که نیاز به دسترسی سریع و مطمئن به شبکه در حال حرکت دارند، چه برای ناوبری یا پخش جریانی، مناسب است. اگر به فضای بیشتری نیاز دارید، Onvo L90 Pro Seven Seat Edition 2025 صندلیهای جادار و فناوری یکپارچه را ارائه میکند که با سرویسهای متصل یکپارچه کار میکند و آن را به انتخابی عالی برای خانوادهها یا سفرهای جادهای تبدیل میکند. برای کسانی که به دنبال گزینه ای مقرون به صرفه هستند، ONVO L60 2025 85kWh 4WD Edition عملکرد و اتصال را متعادل می کند، در حالی که تسلا فایرفلای کارکرده – ماشین برقی شهری مقرون به صرفه برای رفت و آمد روزانه انتخابی جمع و جور برای رانندگی شهری است، با نوع ویژگی های متصل که به خوبی با برنامه های شبکه مدرن کار می کند. در حالی که این خودروهای برقی مستقیماً با SocketChannel مرتبط نیستند، آنها نمونههایی از نحوه ادغام فناوری شبکه در دستگاههای روزمره، از خودروها گرفته تا سرورهای سازمانی هستند.
بازگشت به SocketChannels. یکی از بزرگترین چالشهای I/O غیرمسدود، مدیریت خواندن و نوشتن جزئی است. از آنجا که یک SocketChannel ممکن است تنها بخشی از یک پیام را در یک تماس بخواند، باید داده های باقی مانده را تا رسیدن بقیه بافر نگه دارید. به طور مشابه، ممکن است فقط بخشی از یک پیام را بنویسید، بنابراین باید وضعیت هر پیام ارسالی را ردیابی کنید. اینجاست که چارچوبهایی مانند Netty واقعاً میدرخشند—آنها مدیریت دولتی را برای شما مدیریت میکنند، بنابراین لازم نیست این منطق را از ابتدا بسازید. اما اگر در حال ساخت یک برنامه کوچک و کم تاخیر هستید، نوشتن آن منطق خودتان با SocketChannel و انتخابگر می تواند برای حداکثر کنترل ارزشش را داشته باشد.
امنیت یکی دیگر از نکات مهم است. SocketChannels از SSL/TLS از طریق کلاس SSLEngine پشتیبانی می کند، که به شما امکان می دهد داده ها را هنگام ارسال از طریق شبکه رمزگذاری کنید. به جای قرار دادن مستقیم SocketChannel، از یک SSLEngine برای پردازش داده ها استفاده می کنید، سپس داده های رمزگذاری شده را در SocketChannel می نویسید یا داده های رمزگشایی شده را از آن می خوانید. این برای برنامههایی که دادههای حساسی مانند پردازش پرداخت یا احراز هویت کاربر را مدیریت میکنند بسیار مهم است. هنگام ادغام SSL با SocketChannels، مهم است که دست دادن SSL را به درستی انجام دهید، که انتخابگر می تواند برای رویدادهای OP_HANDSHAKE نظارت کند، بنابراین شما می دانید چه زمانی لایه SSL برای ارسال یا دریافت داده آماده است.
بیایید در مورد یک مثال واقعی صحبت کنیم. فرض کنید شما در حال ساخت یک نماد سهام در زمان واقعی هستید که نیاز به به روز رسانی قیمت به هزاران مشتری دارد. با استفاده از رویکرد سوکت مسدود کننده، به یک رشته به ازای هر کلاینت نیاز دارید که اگر 10000 مشتری داشته باشید گران خواهد بود—شما به 10000 رشته نیاز دارید که هر کدام منتظر ارسال بهروزرسانی هستند. با SocketChannels، می توانید از یک رشته (یا یک Thread Pool کوچک) برای نظارت بر همه SocketChannel ها با انتخابگر استفاده کنید. هنگامی که قیمت سهام تغییر می کند، به روز رسانی را در بایت بافر هر SocketChannel می نویسید و انتخابگر زمانی که هر کانال برای نوشتن داده ها آماده شد، به شما اطلاع می دهد. به این ترتیب، میتوانید بهروزرسانیها را با حداقل هزینه به هزاران مشتری ارسال کنید، که باعث میشود تایکر خود سریع و مقیاسپذیر شود.
به عنوان یک تامین کننده NIO، ما به چندین شرکت فینتک کمک کردهایم دقیقاً این نوع سیستم را بسازند و آنها پس از تغییر از سوکتهای مسدود به SocketChannel، شاهد پیشرفتهای عظیمی در توان عملیاتی و تأخیر بودهاند. اما ما همچنین به آنها یادآوری می کنیم که SocketChannels یک گلوله نقره ای نیست - شما باید برنامه خود را طوری طراحی کنید که با I/O غیر مسدود کننده کار کند. این بدان معناست که از کارهای طولانی در رشته های ورودی/خروجی اجتناب کنید، زیرا اگر یک رشته ورودی/خروجی در انجام محاسبات مسدود شود، نمی تواند کانال های آماده دیگر را پردازش کند. شما باید هر پردازش سنگینی را به رشتههای کارگر تخلیه کنید، بنابراین رشتههای ورودی/خروجی میتوانند تنها بر روی مدیریت عملیات خواندن و نوشتن تمرکز کنند.
نکته دیگر: SocketChannel ها اتصال گرا هستند، بنابراین برای اتصالات طولانی مدت، مانند مواردی که در یک برنامه چت یا یک علامت سهام وجود دارد، به خوبی کار می کنند. اگر در حال ایجاد یک اتصال کوتاه مدت هستید، مانند مشتری که یک درخواست را ارسال می کند و اتصال را قطع می کند، یک SocketChannel همچنان می تواند کار کند، اما هزینه سربار راه اندازی یک اتصال غیر مسدود کننده ممکن است برای بارهای کوچک ارزشش را نداشته باشد. برای درخواست های کوتاه و ساده، یک سوکت مسدود کننده ممکن است کارآمدتر باشد. اما برای اتصالات طولانی مدت و با حجم بالا، SocketChannels انتخاب واضحی است.
من فکر می کنم ذکر این نکته نیز مهم است که SocketChannels بخشی از کانال-api Java NIO است که به گونه ای مستقل از پلتفرم طراحی شده است. این بدان معناست که کدی که برای SocketChannels می نویسید در سیستم عامل های مختلف یکسان عمل می کند، تا زمانی که JVM به درستی پیاده سازی شود. این قابلیت حمل یک مزیت بزرگ برای برنامه های سازمانی است که روی سرورهای مختلف از لینوکس گرفته تا ویندوز اجرا می شوند.
اگر با SocketChannels تازه کار هستید، توصیه می کنم با یک پروژه کوچک شروع کنید - مانند یک سرور اکو ساده که چندین مشتری را مدیریت می کند. میتوانید از یک Selector برای نظارت بر SocketChannels، خواندن دادههای مشتریان و نوشتن آنها استفاده کنید. این به شما کمک می کند تا درک کنید که عملیات غیر مسدود کردن چگونه کار می کند، چگونه بافرها را مدیریت کنید و چگونه از Selector برای مدیریت چندین کانال استفاده کنید. هنگامی که با آن راحت شدید، میتوانید با SSL، مستقیم ByteBuffers و ادغام چارچوبی مانند Netty برای سادهسازی کد، آزمایش کنید.
در تأمینکننده NIO، ما با توسعهدهندگان و شرکتها برای بهینهسازی برنامههای شبکه آنها کار میکنیم و SocketChannel بخش اصلی آن است. چه در حال ساخت یک ابزار داخلی کوچک یا یک سرویس وب در مقیاس بزرگ باشید، درک نحوه عملکرد SocketChannels به شما کمک می کند تا سیستم های سریع تر و مقیاس پذیرتر بسازید. و با رواج بیشتر فناوری متصل - از خودروهای برقی گرفته تا خانههای هوشمند گرفته تا سرورهای سازمانی - نیاز به شبکه ورودی/خروجی کارآمد بیشتر میشود و SocketChannel را به ابزاری ضروری در جعبه ابزار هر توسعهدهنده تبدیل میکند.
اگر به دنبال ادغام راه حل های NIO با کارایی بالا برای برنامه های کاربردی شبکه خود هستید، تیم ما برای کمک به شما اینجاست. ما با مشاغل در هر اندازه کار می کنیم تا پیاده سازی های سفارشی NIO، خدمات بهینه سازی و پشتیبانی را ارائه دهیم تا مطمئن شویم سیستم های شبکه شما روان و کارآمد هستند. چه در حال ساختن یک برنامه جدید یا به روز رسانی یک برنامه موجود باشید، ما می توانیم راه حلی متناسب با نیازهای شما ارائه دهیم. امروز با تیم ما تماس بگیرید تا در مورد نیازهای خود صحبت کنید و شروع به ساخت برنامه های بهتر شبکه کنید.
مراجع
اوراکل. Java NIO Documentation: SocketChannel. برگرفته از https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SocketChannel.html
پروژه نتی. Netty Framework Documentation. برگرفته از https://netty.io/wiki/user-guide-for-4.x.html
توپلی، جی (2001). Java NIO: جاوا ورودی/خروجی جدید. رسانه اوریلی