يوفر النظام التالي مقدمة متعمقة لآلية وظيفة Java Nio واستخدامها من خلال المبادئ والعمليات ، وما إلى ذلك ، دعنا نتعلم ذلك.
مقدمة
يشرح هذا المقال أساسًا آلية IO في Java
مقسمة إلى قطعتين:
الجزء الأول يفسر آلية IO تحت القراءة المتعددة. يشرح الجزء الثاني كيفية تحسين إهدار موارد وحدة المعالجة المركزية في ظل آلية IO (New IO)
صدى خادم
لا أحتاج إلى تقديم آلية المقبس تحت موضوع واحد. إذا كنت لا تعرف ، فيمكنك التحقق من المعلومات ضمن العديد من المواضيع. ماذا لو كنت تستخدم مآخذ؟
نحن نستخدم أبسط خادم صدى لمساعدة الجميع على الفهم
أولاً ، دعونا نلقي نظرة على مخطط سير العمل للخادم والعميل تحت الرأي المتعدد:
يمكنك أن ترى أن العديد من العملاء يرسلون طلبات إلى الخادم في نفس الوقت
قام الخادم بإجراء تدبير لتمكين مؤشرات ترابط متعددة لمطابقة العميل المقابل.
ويكمل كل مؤشر ترابط طلبات عملائهم وحدها
بعد انتهاء المبدأ ، دعونا نرى كيف يتم تنفيذه
هنا كتبت خادمًا بسيطًا
استخدم تقنية تجمع مؤشرات الترابط لإنشاء مؤشرات الترابط (لقد علقت على وظيفة الكود المحددة):
الفئة العامة myserver {private static eventorservice evectororservice = evectors.newcachedthreadpool () ؛ // قم بإنشاء مجموعة من فئة STATIC STATIC PRIVATE PRASELSG تنفذ Runnable {// بمجرد وجود طلب عميل جديد ، قم بإنشاء هذا الموضوع لمعالجة عميل المقبس ؛ // قم بإنشاء عميل عام HandleMsg (عميل Socket) {// إنشاء معلمة ربط this.client = client ؛ } Override public void run () {bufferedReader BufferedReader = null ؛ // إنشاء ذاكرة التخزين المؤقت للحرف تدفق printWriter printWriter = null ؛ // إنشاء دفق كتابة الأحرف جرب {bufferedReader = جديد bufferedReader (جديد inputStreamReader (client.getInputStream ())) ؛ // الحصول على دفق إدخال العميل printWriter = new printWriter (client.getOutputStream () ، true) ؛ // الحصول على دفق إخراج العميل ، صحيح هو تحديث inputline string = null ؛ long a = system.currentTimeMillis () ؛ بينما ((inputLine = bufferedReader.ReadLine ())! = null) {printWriter.println (inputLine) ؛ } long b = system.currentTimeMillis () ؛ System.out.println ("استغرق هذا الموضوع:"+(BA)+"Seconds!") ؛ } catch (ioException e) {E.PrintStackTrace () ؛ } أخيرًا {try {bufferedReader.Close () ؛ printWriter.close () ؛ client.close () ؛ } catch (ioException e) {E.PrintStackTrace () ؛ }}}} static void main (string [] args) يلقي ioException {// يتم استخدام مؤشر الترابط الرئيسي للخادم للاستماع إلى طلبات العميل في خادم ServersoCTOCTACT (Serversity) (8686) ؛ // إنشاء خادم مع Port 8686 Socket Client = NULL ؛ بينما (صواب) {// loop الاستماع client = server.accept () ؛ // يستمع الخادم إلى نظام طلب العميل. ExecutorService.Submit (New Handlemsg (Client)) ؛ // ضع طلب العميل في مؤشر ترابط معاملات من خلال تجمع مؤشرات الترابط للمعالجة}}}في الكود أعلاه ، نستخدم فئة لكتابة خادم صدى بسيط لتمكين الاستماع إلى المنفذ في الموضوع الرئيسي باستخدام حلقة ميتة.
عميل بسيط
مع خادم ، يمكننا الوصول إليه ، وإرسال بعض بيانات السلسلة. تتمثل وظيفة الخادم في إرجاع هذه السلاسل وطباعة خيط أخذ الوقت.
دعنا نكتب عميلًا بسيطًا للرد على الخادم:
الطبقة العامة myClient {public static void main (string [] args) يلقي ioException {socket client = null ؛ printWriter printWriter = null ؛ BufferedReader BufferedReader = null ؛ حاول {client = new Socket () ؛ client.connect (new inetsocketaddress ("LocalHost" ، 8686)) ؛ printWriter = new printWriter (client.getOutputStream () ، true) ؛ printWriter.println ("Hello") ؛ printWriter.flush () ؛ BufferedReader = جديد BufferedReader (New InputStreamReader (client.getInputStream ())) ؛ // اقرأ المعلومات التي يتم إرجاعها بواسطة Server and Output System.out.println ("المعلومات من الخادم هي:"+BufferedReader.ReadLine ()) ؛ } catch (ioException e) {E.PrintStackTrace () ؛ } أخيرًا {printWriter.close () ؛ BufferedReader.Close () ؛ client.close () ؛ }}}في الكود ، نستخدم دفق الأحرف لإرسال سلسلة Hello. إذا كان الرمز على ما يرام ، فسيقوم الخادم بإرجاع بيانات Hello وطباعة معلومات السجل التي حددناها.
عرض نتائج خادم الصدى
لنركض:
1. افتح الخادم وتمكين الاستماع إلى الحلقة:
2. افتح عميل:
يمكنك أن ترى أن العميل يطبع نتيجة الإرجاع
3. تحقق من سجل الخادم:
جيد جدا ، يتم تنفيذ برمجة مقبس متعددة المؤشرات البسيطة
لكن فكر في الأمر:
إذا طلب العميل ، أضف النوم أثناء كتابة IO إلى الخادم ،
اجعل كل طلب يشغل مؤشر ترابط الخادم لمدة 10 ثوانٍ
ثم هناك الكثير من طلبات العميل ، كل منها يأخذ هذا الوقت الطويل
ثم سيتم تقليل قدرات توحيد الخادم إلى حد كبير
هذا ليس لأن الخادم لديه العديد من المهام الثقيلة ، ولكن لمجرد أن مؤشر ترابط الخدمة ينتظر IO (لأن القبول والقراءة والكتابة كلها ممنوعة)
من غير المدفوع جدًا أن تترك وحدات المعالجة المركزية عالية السرعة تنتظر شبكتها غير الفعالة IO
ماذا علي أن أفعل في هذا الوقت؟
نيو
نجحت IO الجديدة في حل المشكلة أعلاه. كيف حلها؟
الحد الأدنى للوحدة لـ IO لمعالجة طلبات العميل هو الموضوع
يستخدم NIO وحدة أصغر مستوى واحد من مؤشر ترابط: القناة (القناة)
يمكن القول أن هناك حاجة إلى موضوع واحد فقط في NIO لإكمال جميع الاستقبال والقراءة والكتابة وغيرها من العمليات.
لتعلم NIO ، يجب عليك أولاً فهم نقاطها الأساسية الثلاث
المحدد ، المحدد
المخزن المؤقت ، المخزن المؤقت
قناة ، قناة
المدون ليس موهوبًا ، لذا فقد رسم صورة قبيحة لتعميق انطباعه^. ^
أعطني مخطط سير عمل آخر NIO تحت TCP (من الصعب للغاية رسم الخطوط ...)
فقط افهم الأمر تقريبًا ، دعنا نذهب خطوة بخطوة
المخزن المؤقت
بادئ ذي بدء ، تحتاج إلى معرفة ما هو المخزن المؤقت
لم يعد تفاعل البيانات يستخدم تدفقات مثل آليات IO
بدلاً من ذلك ، استخدم المخزن المؤقت (المخزن المؤقت)
يعتقد المدون أن الصور هي الأسهل في الفهم
لذا...
يمكنك أن ترى أين يوجد المخزن المؤقت في سير العمل بأكمله
دعونا نلقي نظرة على تلك الفعلية. الرمز المحدد في الشكل أعلاه هو كما يلي:
1. قم أولاً بتخصيص مساحة للمخزن المؤقت
bytebuffer bytebuffer = bytebuffer.allocate (1024) ؛
قم بإنشاء كائن Bytebuffer وحدد حجم الذاكرة
2. اكتب البيانات إلى المخزن المؤقت:
1). بيانات من القناة إلى المخزن المؤقت: Channel.Read (Bytebuffer) ؛ 2). بيانات من العميل إلى المخزن المؤقت: bytebuffer.put (...) ؛
3. اقرأ البيانات من المخزن المؤقت:
1). بيانات من المخزن المؤقت إلى القناة: channel.write (bytebuffer) ؛ 2). بيانات من المخزن المؤقت إلى الخادم: bytebuffer.get (...) ؛
المحدد
المحدد هو جوهر NIO ، وهو مسؤول القناة
من خلال تنفيذ طريقة حظر SELECT () ، استمع إلى ما إذا كانت القناة جاهزة
بمجرد قراءة البيانات ، تكون قيمة إرجاع هذه الطريقة هي عدد مفاتيح التحديد
لذلك عادةً ما يقوم الخادم بتنفيذ حلقة SELECT () Method Dead حتى يصبح القناة جاهزة ثم يبدأ العمل
ستربط كل قناة حدثًا للمحدد ، ثم إنشاء كائن مفتاح تحديد.
ما تجدر الإشارة إليه هو:
عندما تكون القناة والمحدد ملزمين ، يجب أن تكون القناة في وضع عدم الحظر.
لا يمكن لـ Filechannel التبديل إلى وضع عدم الحظر لأنه ليس قناة مقبس ، لذلك لا يمكن لـ Filechannel ربط الأحداث مع المحدد
هناك أربعة أنواع من الأحداث في NIO:
1.SelectionKey.op_connect: حدث اتصال
2.selectionKey.op_accept: تلقي الأحداث
3.selectionkey.op_read: اقرأ الأحداث
4.SelectionKey.op_write: اكتب الأحداث
قناة
هناك أربع قنوات في المجموع:
Filechannel: أعمال على دفق ملف IO
DatagramChannel: إنه يعمل على بروتوكول UDP
Socketchannel: إنه يعمل على بروتوكول TCP
Serversocketchannel: يعمل على بروتوكول TCP
تشرح هذه المقالة NIO من خلال بروتوكول TCP شائع الاستخدام
دعنا نأخذ serversocketchannel كمثال:
افتح قناة Serversocketchannel
serversocketchannel serversocketchannel = serversocketchannel.open () ؛
أغلق قناة Serversocketchannel:
serversocketchannel.close () ؛
حلقة Socketchannel:
بينما (صواب) {socketchannel socketchannel = serversocketchannel.accept () ؛ clientChannel.ConfigureBlocking (false) ؛} clientChannel.configureBlocking(false); البيان هو تعيين هذه القناة على عدم الحظر ، أي ، هو السيطرة الحرة غير المتزامنة على الحظر أو عدم الحظر هي واحدة من خصائص NIO
مفتاح الاختيار
Selectekeke هو المكون الأساسي للتفاعل بين القنوات والمحددات
على سبيل المثال ، قم بربط محدد على Socketchannel وقم بتسجيله كحدث اتصال:
socketchannel clientChannel = socketchannel.open () ؛ clientChannel.ConfigureBlocking (false) ؛ clientChannel.connect (new inetsocketaddress (port)) ؛ clientChannel.register (selector ، selectekey.op_connect) ؛
النواة في طريقة التسجيل () ، والتي تُرجع كائن مفتاح التحديد
للكشف عن أحداث القناة هو نوع الحدث الذي يمكنك استخدامه للطريقة التالية:
selectekey.isacceptable () ؛ selectekey.isconnectable () ؛ selectekey.isreadable () ؛ selectekey.iswritable () ؛
ينفذ الخادم العمليات المقابلة في الاقتراع من خلال هذه الطرق
بالطبع ، يمكن أيضًا الحصول على المفاتيح المرتبطة بالقناة والمحدد بدورها.
Channel Channel = selecteKey.Channel () ؛ Selector Selector = SelecteKey.Selector () ؛
عند تسجيل الأحداث على القناة ، يمكننا أيضًا ربط المخزن المؤقت:
clientChannel.register (key.selector () ، selectekey.op_read ، bytebuffer.allocatedirect (1024)) ؛
أو ربط كائن:
selectekey.attach (object) ؛ object anthorobj = selectekey.attachment () ؛
خادم TCP من NIO
بعد التحدث كثيرًا ، سوف نلقي نظرة على أبسط الكود والأكثر الأساسية (ليس من الأنيق إضافة العديد من التعليقات ، ولكن من المريح أن يفهم الجميع):
package cn.blog.test.niotest ؛ استيراد java.io.ioException ؛ استيراد java.net.inetsocketaddress ؛ استيراد java.nio.bytebuffer ؛ import java.nio.channels.*؛ محدد // إنشاء منفذ int نهائي خاص محدد = 8686 ؛ int static static int buf_size = 10240 ؛ private void initserver () يلقي ioException {// إنشاء كائن Manager this.selector = celector.open () ؛ // إنشاء قناة قناة كائن قناة serversocketchannel = serversocketchannel.open () ؛ Channel.ConfigureBlocking (false) ؛ // قم بتعيين القناة على قناة غير محظورة. socket (). // ربط القناة على المنفذ 8686 // ربط مدير القناة والقناة أعلاه ، وقم بتسجيل حدث OP_ACCEPT للقناة // بعد تسجيل الحدث ، سيعود CONCTORCTOR.Select () (مفتاح). إذا لم يصل الحدث إلى celector.select () ، فسيحظر SelecteKeykey = channel.register (Selector ، SelecteKey.op_accept) ؛ بينما (صواب) {// complict.select () ؛ // هذه طريقة حظر ، انتظر حتى يتم قراءة البيانات ، وقيمة الإرجاع هي عدد المفاتيح (يمكن أن يكون هناك متعددة) مفاتيح تعيين = celector.selectedkeys () ؛ // إذا كانت القناة تحتوي على بيانات ، فقم بالوصول إلى المفاتيح التي تم إنشاؤها إلى ITerator Collection Collection = keys.iterator () ؛ // احصل على ITerator من مجموعة المفاتيح هذه بينما (iterator.hasnext ()) {// استخدم ITerator لاجتياز مفتاح Selection Collection = (SelecteKey) iterator.next () ؛ // احصل على مثيل رئيسي في المجموعة iterator.remove () ؛ // تذكر حذف هذا العنصر في التكرار بعد الحصول على مثيل المفتاح الحالي ، وهو أمر مهم للغاية ، وإلا فإن الخطأ سيحدث إذا (key.isacceptable ()) {// just ما إذا كانت القناة ممثلة بالمفتاح الحالي في الحالة المقبولة ، وإذا كان الأمر كذلك ، doaccept (مفتاح) ؛ } آخر if (key.isReadable ()) {doread (key) ؛ } if if (key.iswritable () && key.isvalid ()) {dowrite (key) ؛ } آخر if (key.isconnectable ()) {system.out.println ("connectable!") ؛ }}}} public void doAccept (مفتاح SelecteKey) يلقي ioException {serversocketchannel serverChannel = (serversocketchannel) key.channel () ؛ System.out.println ("خادمات Conversocketchannel يستمع في حلقة") ؛ socketchannel clientChannel = serverChannel.accept () ؛ clientChannel.ConfigureBlocking (false) ؛ clientChannel.register (key.selector () ، selectekey.op_read) ؛ } public void doread (SelecteKey Key) يلقي ioException {SocketchAnly ClientChannel = (SocketchAnnel) key.channel () ؛ bytebuffer bytebuffer = bytebuffer.allocate (buf_size) ؛ bytesread = clientChannel.Read (bytebuffer) ؛ بينما (bytesRead> 0) {bytebuffer.flip () ؛ byte [] data = bytebuffer.array () ؛ معلومات السلسلة = سلسلة جديدة (بيانات) .trim () ؛ System.out.println ("الرسالة المرسلة من العميل هي:"+معلومات) ؛ bytebuffer.clear () ؛ bytesRead = clientChannel.Read (bytebuffer) ؛ } if (bytesRead ==-1) {clientChannel.Close () ؛ }} public void dowrite (مفتاح SelecteKey) يلقي ioException {bytebuffer bytebuffer = bytebuffer.allocate (buf_size) ؛ bytebuffer.flip () ؛ socketchannel clientChannel = (socketchannel) key.channel () ؛ بينما (bytebuffer.hasremaining ()) {clientChannel.write (bytebuffer) ؛ } bytebuffer.compact () ؛ } public static void main (string [] args) يلقي ioException {mynioserver mynioServer = new mynioserver () ؛ mynioserver.initserver () ؛ }} لقد طبعت قناة الشاشة وأخبرتك عندما بدأت الخادمات في الركض
إذا تعاونت مع تصحيح عميل NIO ، فيمكنك العثور عليه بوضوح. قبل إدخال استطلاع SELECT ()
على الرغم من وجود مفتاح بالفعل لحدث قبول ، لن يتم استدعاء SELECT () افتراضيًا
بدلاً من ذلك ، يجب عليك الانتظار حتى يتم التقاط أحداث أخرى مثيرة للاهتمام بواسطة Select () قبل استدعاء مفتاح Selection's Conclude
عندها فقط بدأ خوادم soxerockechannel في إجراء مراقبة دائرية
وبعبارة أخرى ، في المحدد ، يتم الحفاظ على الخوادم الخاطئة دائمًا.
و serverChannel.accept(); غير متزامن حقًا (channel.configureblocking (false) ؛
إذا لم يتم قبول الاتصال ، فسيتم إعادة فارغ
إذا تم توصيل Socketchannel بنجاح ، فإن هذا Socketchannel يسجل حدث كتابة (قراءة)
وتعيين غير متزامن
عميل TCP الخاص بـ NIO
يجب أن يكون هناك عميل إذا كان هناك خادم
في الواقع ، إذا كنت تستطيع فهم الخادم تمامًا
رمز العميل مشابه
package cn.blog.test.niotest ؛ استيراد java.io.ioException ؛ استيراد java.net.inetsocketaddress ؛ استيراد java.nio.bytebuffer ؛ import java.nio.channels. Mynioclient {محدد المحدد الخاص ؛ // إنشاء منفذ int نهائي خاص محدد = 8686 ؛ int static static int buf_size = 10240 ؛ private static bytebuffer bytebuffer = bytebuffer.allocate (buf_size) ؛ private void initClient () يلقي ioException {this.selector = celector.open () ؛ socketchannel clientChannel = socketchannel.open () ؛ clientChannel.ConfigureBlocking (false) ؛ ClientChannel.connect (New InetSocketaddress (PORT)) ؛ ClientChannel.register (Selector ، SelecteKey.OP_Connect) ؛ بينما (صواب) {celector.select () ؛ ITerator <SelecteKey> iterator = celector.SelectedKeys (). iterator () ؛ بينما (iterator.hasnext ()) {selectekey key = iterator.next () ؛ iterator.remove () ؛ if (key.isconnectable ()) {doconnect (key) ؛ } آخر if (key.isReadable ()) {doread (key) ؛ }}} doconnect public void (مفتاح SelecteKey) يلقي ioException {socketchannel clientChannel = (socketchannel) key.channel () ؛ if (clientChannel.isconnectionPending ()) {clientChannel.FinishConnect () ؛ } clientChannel.ConfigureBlocking (false) ؛ string info = "Hello Server !!!" ؛ bytebuffer.clear () ؛ bytebuffer.put (info.getBytes ("utf-8")) ؛ bytebuffer.flip () ؛ clientChannel.write (bytebuffer) ؛ //clientchannel.register(key.selector()،SelectionKey.op_read) ؛ clientChannel.Close () ؛ } public void doread (SelecteKey Key) يلقي ioException {SocketchAnly ClientChannel = (SocketchAnnel) key.channel () ؛ ClientChannel.Read (Bytebuffer) ؛ byte [] data = bytebuffer.array () ؛ String msg = new string (data) .trim () ؛ system.out.println ("Server يرسل رسالة:"+msg) ؛ clientChannel.Close () ؛ key.selector (). close () ؛ } static void main (string [] args) يلقي ioException {mynioclient mynioclient = new mynioclient () ؛ mynioclient.initclient () ؛ }}نتيجة الإخراج
هنا أفتح خادمًا وعميلًا:
بعد ذلك ، يمكنك محاولة فتح ألف عميل في نفس الوقت. طالما أن وحدة المعالجة المركزية قوية بما فيه الكفاية ، فلن يتمكن الخادم من تقليل الأداء بسبب انسداد.
ما سبق هو شرح مفصل لجافا نيو. إذا كان لديك أي أسئلة ، فيمكنك مناقشتها في منطقة الرسالة أدناه.