ระบบต่อไปนี้ให้การแนะนำเชิงลึกเกี่ยวกับกลไกการทำงานของ Java Nio และการใช้งานผ่านหลักการกระบวนการ ฯลฯ เรามาเรียนรู้กันเถอะ
คำนำ
บทความนี้ส่วนใหญ่อธิบายกลไก IO ใน Java
แบ่งออกเป็นสองชิ้น:
ส่วนแรกอธิบายกลไก IO ภายใต้มัลติเธรด ส่วนที่สองอธิบายถึงวิธีการเพิ่มประสิทธิภาพการสูญเสียทรัพยากร CPU ภายใต้กลไก IO (ใหม่ IO)
Echo Server
ฉันไม่จำเป็นต้องแนะนำกลไกซ็อกเก็ตภายใต้เธรดเดียว หากคุณไม่ทราบคุณสามารถตรวจสอบข้อมูลภายใต้เธรดจำนวนมาก ถ้าคุณใช้ซ็อกเก็ตล่ะ?
เราใช้เซิร์ฟเวอร์ Echo ที่ง่ายที่สุดเพื่อช่วยให้ทุกคนเข้าใจ
ก่อนอื่นมาดูแผนภาพเวิร์กโฟลว์ของเซิร์ฟเวอร์และไคลเอนต์ภายใต้มัลติเธรด:
คุณจะเห็นว่าลูกค้าหลายรายส่งคำขอไปยังเซิร์ฟเวอร์ในเวลาเดียวกัน
เซิร์ฟเวอร์ได้ทำการวัดเพื่อเปิดใช้งานหลายเธรดเพื่อให้ตรงกับไคลเอนต์ที่เกี่ยวข้อง
และแต่ละเธรดจะทำตามคำขอของลูกค้าเพียงอย่างเดียว
หลังจากหลักการเสร็จสิ้นมาดูกันว่ามันถูกนำไปใช้อย่างไร
ที่นี่ฉันเขียนเซิร์ฟเวอร์ง่ายๆ
ใช้เทคโนโลยีเธรดพูลเพื่อสร้างเธรด (ฉันได้แสดงความคิดเห็นเกี่ยวกับฟังก์ชั่นรหัสเฉพาะ):
คลาสสาธารณะ MyServer {Private ExecutorService ExecutorService = Executors.newCachedThreadPool (); // สร้างพูลพูลพูลคลาสส่วนตัวแบบคงที่ HandleMSG ใช้งาน Runnable {// เมื่อมีคำขอไคลเอนต์ใหม่ให้สร้างเธรดนี้เพื่อประมวลผลไคลเอนต์ซ็อกเก็ต // สร้างไคลเอ็นต์ handlemsg (ซ็อกเก็ตไคลเอนต์) {// สร้างพารามิเตอร์ที่มีผลผูกพัน this.client = ไคลเอนต์; } @Override โมฆะสาธารณะ Run () {bufferederer bufferedReader = null; // สร้างแคชอักขระอินพุตสตรีม PrintWriter PrintWriter = NULL; // สร้างสตรีมการเขียนอักขระลอง {bufferedReader = ใหม่ bufferedReader (ใหม่ inputStreamReader (client.getInputStream ())); // รับอินพุตสตรีมของไคลเอนต์ printWriter = ใหม่ printWriter (client.getOutputStream (), จริง); // รับกระแสเอาต์พุตของไคลเอนต์จริงคือการรีเฟรชสตริงอินพุต = null; ยาว a = system.currenttimeMillis (); ในขณะที่ ((inputline = bufferedreader.readline ())! = null) {printwriter.println (inputline); } long b = system.currentTimeMillis (); System.out.println ("เธรดนี้ใช้:"+(ba)+"วินาที!"); } catch (ioexception e) {e.printstacktrace (); } ในที่สุด {ลอง {bufferedreader.close (); printwriter.close (); client.close (); } catch (ioexception e) {e.printstacktrace (); }}}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น IOException {// เธรดหลักของเซิร์ฟเวอร์ใช้เพื่อฟังคำขอไคลเอนต์ในเซิร์ฟเวอร์เซิร์ฟเวอร์เซิร์ฟเวอร์ = เซิร์ฟเวอร์ใหม่ (8686); // สร้างเซิร์ฟเวอร์ด้วยพอร์ต 8686 ซ็อกเก็ตไคลเอนต์ = null; ในขณะที่ (จริง) {// loop listen client = server.accept (); // เซิร์ฟเวอร์รับฟังการร้องขอไคลเอนต์ System.out.println (client.getRemotesocketAddress ()+"การเชื่อมต่อไคลเอนต์สำเร็จ!"); ExecutorService.submit (ใหม่ handlemsg (ไคลเอนต์)); // ใส่คำขอไคลเอนต์ลงในเธรด handlmsg ผ่านพูลเธรดสำหรับการประมวลผล}}}ในรหัสข้างต้นเราใช้คลาสเพื่อเขียนเซิร์ฟเวอร์ Echo อย่างง่ายเพื่อเปิดใช้งานการฟังพอร์ตในเธรดหลักโดยใช้ลูปตาย
ลูกค้าง่าย
ด้วยเซิร์ฟเวอร์เราสามารถเข้าถึงได้และส่งข้อมูลสตริง ฟังก์ชั่นของเซิร์ฟเวอร์คือการส่งคืนสตริงเหล่านี้และพิมพ์เธรดที่ใช้เวลา
มาเขียนไคลเอนต์ง่าย ๆ เพื่อตอบกลับเซิร์ฟเวอร์:
คลาสสาธารณะ myClient {โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น IOException {ซ็อกเก็ตไคลเอนต์ = null; PrintWriter PrintWriter = NULL; bufferedReader bufferedReader = null; ลอง {client = socket ใหม่ (); client.connect (ใหม่ inetSocketAddress ("localhost", 8686)); PrintWriter = ใหม่ PrintWriter (client.getOutputStream (), จริง); printwriter.println ("สวัสดี"); printwriter.flush (); bufferedReader = ใหม่ bufferedReader (ใหม่ inputStreamReader (client.getInputStream ())); // อ่านข้อมูลที่ส่งคืนโดยเซิร์ฟเวอร์และ System.out.out.println ("ข้อมูลจากเซิร์ฟเวอร์คือ:"+bufferedreader.readline ()); } catch (ioexception e) {e.printstacktrace (); } ในที่สุด {printwriter.close (); bufferedReader.close (); client.close (); -ในรหัสเราใช้สตรีมอักขระเพื่อส่งสตริงสวัสดี หากรหัสไม่เป็นไรเซิร์ฟเวอร์จะส่งคืนข้อมูลสวัสดีและพิมพ์ข้อมูลบันทึกที่เราตั้งไว้
ผลการสะท้อนของเซิร์ฟเวอร์แสดงผล
วิ่งกันเถอะ:
1. เปิดเซิร์ฟเวอร์และเปิดใช้งานการฟังลูป:
2. เปิดลูกค้า:
คุณจะเห็นว่าไคลเอนต์พิมพ์ผลการส่งคืน
3. ตรวจสอบบันทึกเซิร์ฟเวอร์:
ดีมากการเขียนโปรแกรมซ็อกเก็ตแบบมัลติเธรดแบบง่าย ๆ ถูกนำไปใช้
แต่คิดเกี่ยวกับมัน:
หากลูกค้าร้องขอให้เพิ่มการนอนหลับระหว่างการเขียน IO ไปยังเซิร์ฟเวอร์
ทำให้แต่ละคำขอครอบครองเธรดเซิร์ฟเวอร์เป็นเวลา 10 วินาที
จากนั้นมีคำขอของลูกค้าจำนวนมากแต่ละครั้งใช้เวลานานขนาดนั้น
จากนั้นความสามารถในการรวมเซิร์ฟเวอร์จะลดลงอย่างมาก
นี่ไม่ใช่เพราะเซิร์ฟเวอร์มีงานหนักมากมาย แต่เพียงเพราะเธรดบริการกำลังรอ IO (เพราะยอมรับอ่านและเขียนล้วนเป็นการบล็อกทั้งหมด)
มันไม่ได้รับค่าตอบแทนมากที่จะปล่อยให้ซีพียูความเร็วสูงรอให้เครือข่ายที่ไม่มีประสิทธิภาพของพวกเขา IO
ฉันควรทำอย่างไรในเวลานี้?
NIO
ใหม่ 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 () ให้ฟังว่าช่องนั้นพร้อมหรือไม่
เมื่อข้อมูลสามารถอ่านได้แล้วค่าส่งคืนของวิธีนี้คือจำนวนการเลือก
ดังนั้นเซิร์ฟเวอร์มักจะเรียกใช้เมธอด () เมธอดตายไปจนกว่าช่องสัญญาณจะพร้อมแล้วเริ่มทำงาน
แต่ละช่องจะผูกเหตุการณ์กับตัวเลือกจากนั้นสร้างวัตถุ SelectionKey
สิ่งที่ควรสังเกตคือ:
เมื่อช่องและตัวเลือกถูกผูกไว้ช่องจะต้องอยู่ในโหมดที่ไม่ปิดกั้น
FileChannel ไม่สามารถเปลี่ยนไปใช้โหมดที่ไม่ปิดกั้นได้เนื่องจากไม่ใช่ช่องซ็อกเก็ตดังนั้น FileChannel จึงไม่สามารถผูกเหตุการณ์ด้วยตัวเลือกได้
NIO มีสี่ประเภทใน 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 (เท็จ);} clientChannel.configureBlocking(false); คำสั่งคือการตั้งค่าช่องทางนี้ให้เป็นแบบไม่ปิดกั้นนั่นคือการควบคุมแบบอะซิงโครนัสฟรีของการบล็อกหรือการไม่ปิดกั้นเป็นหนึ่งในคุณสมบัติของ NIO
การเลือก
SelectionKey เป็นองค์ประกอบหลักของการโต้ตอบระหว่างช่องและตัวเลือก
ตัวอย่างเช่นผูกตัวเลือกบน Socketchannel และลงทะเบียนเป็นเหตุการณ์การเชื่อมต่อ:
SocketChannel ClientChannel = SocketChannel.Open (); clientChannel.ConfigureBlocking (เท็จ); clientChannel.Connect (ใหม่ inetSocketAddress (พอร์ต)); clientChannel.REGISTER (ตัวเลือก, selectionKey.op_connect);
แกนอยู่ในเมธอด register () ซึ่งส่งคืนวัตถุ SelectionKey
ในการตรวจจับเหตุการณ์ช่องเป็นกิจกรรมประเภทใดที่คุณสามารถใช้วิธีการต่อไปนี้:
SelectionKey.iscceptable (); SelectionKey.isconnectable (); SelectionKey.isreadable (); SelectionKey.iswitable ();
เซิร์ฟเวอร์ดำเนินการที่สอดคล้องกันในการสำรวจผ่านวิธีการเหล่านี้
แน่นอนว่าคีย์ที่ถูกผูกไว้กับช่องและตัวเลือกสามารถรับได้ในทางกลับกัน
ช่องช่อง = selectionKey.channel (); ตัวเลือกตัวเลือก = selectionKey.Selector ();
เมื่อลงทะเบียนกิจกรรมในช่องเรายังสามารถผูกบัฟเฟอร์:
clientChannel.Register (key.Selector (), selectionKey.op_read, bytebuffer.allocatedirect (1024));
หรือผูกวัตถุ:
SelectionKey.attach (Object); Object Anthorobj = SelectionKey.attachment ();
เซิร์ฟเวอร์ TCP ของ NIO
หลังจากพูดคุยกันมากเราจะดูรหัสหลักที่ง่ายที่สุดและมากที่สุด (มันไม่หรูหราที่จะเพิ่มความคิดเห็นมากมาย แต่มันสะดวกสำหรับทุกคนที่จะเข้าใจ):
แพ็คเกจ cn.blog.test.niotest; นำเข้า java.io.ioexception; นำเข้า java.net.inetsocketaddress; นำเข้า java.nio.bytebuffer; นำเข้า Java.nio.channels. ตัวเลือก; // สร้างพอร์ต int คงสุดท้ายของตัวเลือกส่วนตัว = 8686; INT คงที่ครั้งสุดท้ายส่วนตัว int buf_size = 10240; โมฆะส่วนตัว initserver () พ่น IOException {// สร้างตัวเลือกวัตถุตัวจัดการช่องสัญญาณนี้ selector = selector.open (); // สร้างช่องสัญญาณช่องสัญญาณช่องสัญญาณ serversocketChannel = serversocketChannel.open (); channel.configureblocking (เท็จ); // ตั้งค่าช่องเป็น channel non-blocking.socket (). bind (ใหม่ inetSocketAddress (พอร์ต)); // ผูกช่องทางบนพอร์ต 8686 // ผูกตัวจัดการช่องสัญญาณและช่องด้านบนและลงทะเบียนเหตุการณ์ op_accept สำหรับช่อง // หลังจากลงทะเบียนเหตุการณ์ selector.select () จะกลับมา (คีย์) หากเหตุการณ์ไม่ถึง selector.select () มันจะบล็อกการเลือก key selectionKey = channel.register (ตัวเลือก, selectionKey.op_accept); ในขณะที่ (จริง) {// poll selector.select (); // นี่คือวิธีการปิดกั้นรอจนกว่าข้อมูลจะสามารถอ่านได้และค่าที่ส่งคืนคือจำนวนคีย์ (สามารถมีได้หลายตัว) set keys = selector.selectedKeys (); // หากช่องมีข้อมูลให้เข้าถึงคีย์ที่สร้างขึ้นในคีย์คอลเลกชันตัววนซ้ำ iterator = keys.iterator (); // รับตัววนซ้ำของคอลเลกชันคีย์นี้ในขณะที่ (iterator.hasnext ()) {// ใช้ตัววนซ้ำเพื่อสำรวจคีย์การเลือกคอลเลกชัน Key = (selectionKey) iterator.next (); // รับอินสแตนซ์สำคัญในคอลเลกชัน iterator.remove (); // อย่าลืมลบองค์ประกอบนี้ในตัววนซ้ำหลังจากได้รับอินสแตนซ์คีย์ปัจจุบันซึ่งมีความสำคัญมากมิฉะนั้นข้อผิดพลาดจะเกิดขึ้นหาก (key.iscceptable ()) {// ตัดสินว่าช่องที่แสดงโดยคีย์ปัจจุบันอยู่ในสถานะที่ยอมรับได้ } อื่นถ้า (key.isreadable ()) {doread (คีย์); } อื่นถ้า (key.iswitable () && key.isvalid ()) {dowrite (คีย์); } อื่นถ้า (key.isconnectable ()) {system.out.println ("เชื่อมต่อ!"); }}}} โมฆะสาธารณะ Doaccept (SelectionKey Key) พ่น IOException {ServersocketChannel ServerChannel = (ServersocketChannel) key.channel (); System.out.println ("ServersocketChannel กำลังฟังอยู่ในวง"); SocketChannel ClientChannel = ServerChannel.Accept (); clientChannel.ConfigureBlocking (เท็จ); clientChannel.Register (key.Selector (), selectionKey.op_read); } โมฆะสาธารณะ DOREAD (SelectionKey Key) พ่น IOException {SocketChannel clientChannel = (SocketChannel) key.channel (); byteBuffer bytebuffer = bytebuffer.allocate (buf_size); long bytesRead = clientChannel.read (bytebuffer); ในขณะที่ (bytesread> 0) {byteBuffer.flip (); ไบต์ [] data = byteBuffer.Array (); สตริงข้อมูล = สตริงใหม่ (ข้อมูล) .trim (); System.out.println ("ข้อความที่ส่งจากไคลเอนต์คือ:"+ข้อมูล); bytebuffer.clear (); bytesRead = clientChannel.read (bytebuffer); } if (bytesRead ==-1) {clientChannel.close (); }} โมฆะสาธารณะ DowRite (SelectionKey Key) พ่น IOException {ByTebuffer bytebuffer = bytebuffer.allocate (buf_size); bytebuffer.flip (); SocketChannel clientChannel = (SocketChannel) key.channel (); ในขณะที่ (bytebuffer.hasremaining ()) {clientchannel.write (bytebuffer); } byteBuffer.Compact (); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น ioexception {mynioserver mynioserver = new mynioserver (); mynioserver.initserver (); - ฉันพิมพ์ช่องจอมอนิเตอร์และบอกคุณเมื่อ SeversocketChannel เริ่มทำงาน
หากคุณร่วมมือกับการดีบักของลูกค้า NIO คุณสามารถค้นหาได้อย่างชัดเจน ก่อนเข้าสู่การสำรวจ () แบบสำรวจ
แม้ว่าจะมีคีย์สำหรับเหตุการณ์ที่ยอมรับอยู่แล้ว select () จะไม่ถูกเรียกโดยค่าเริ่มต้น
คุณต้องรอจนกว่าเหตุการณ์ที่น่าสนใจอื่น ๆ จะถูกจับโดยเลือก () ก่อนที่จะโทรหา SelectionKey ของ Acept
จากนั้น ServersocketChannel จะเริ่มทำการตรวจสอบแบบวงกลม
กล่าวอีกนัยหนึ่งในตัวเลือก ServersocketChannel จะได้รับการดูแลรักษาอยู่เสมอ
และ serverChannel.accept(); เป็นแบบอะซิงโครนัสอย่างแท้จริง (channel.configureblocking (เท็จ); ในวิธีการ initserver)
หากไม่ยอมรับการเชื่อมต่อจะส่งคืนค่าว่าง
หากมีการเชื่อมต่อ Socketchannel สำเร็จ SocketChannel นี้จะลงทะเบียนเหตุการณ์การเขียน (อ่าน)
และตั้งค่าอะซิงโครนัส
ไคลเอนต์ TCP ของ Nio
จะต้องมีไคลเอนต์หากมีเซิร์ฟเวอร์
อันที่จริงถ้าคุณสามารถเข้าใจเซิร์ฟเวอร์ได้อย่างสมบูรณ์
รหัสของลูกค้าคล้ายกัน
แพ็คเกจ cn.blog.test.niotest; นำเข้า java.io.ioexception; นำเข้า java.net.inetsocketaddress; นำเข้า java.nio.bytebuffer; นำเข้า Java.nio.channels.SelectionKey; Mynioclient {ตัวเลือกส่วนตัว; // สร้างพอร์ต int คงสุดท้ายของตัวเลือกส่วนตัว = 8686; INT คงที่ครั้งสุดท้ายส่วนตัว int buf_size = 10240; ByteBuffer ส่วนตัวคงที่ ByteBuffer = ByTebuffer.Allocate (buf_size); เป็นโมฆะส่วนตัว initclient () พ่น IOException {this.Selector = selector.open (); SocketChannel ClientChannel = SocketChannel.Open (); clientChannel.ConfigureBlocking (เท็จ); clientChannel.Connect (ใหม่ inetSocketAddress (พอร์ต)); clientChannel.Register (ตัวเลือก, selectionKey.op_connect); ในขณะที่ (จริง) {selector.select (); Iterator <SelectionKey> iterator = selector.selectedKeys (). iterator (); ในขณะที่ (iterator.hasnext ()) {selectionKey key = iterator.next (); iterator.remove (); if (key.isconnectable ()) {doconnect (คีย์); } อื่นถ้า (key.isreadable ()) {doread (คีย์); }}} โมฆะสาธารณะ doconnect (SelectionKey Key) พ่น IOException {SocketChannel clientChannel = (SocketChannel) key.channel (); if (clientChannel.isconnectionPending ()) {clientChannel.finishConnect (); } clientChannel.ConfigureBlocking (เท็จ); 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 (); } โมฆะสาธารณะ DOREAD (SelectionKey Key) พ่น IOException {SocketChannel clientChannel = (SocketChannel) key.channel (); clientChannel.Read (bytebuffer); ไบต์ [] data = byteBuffer.Array (); String msg = สตริงใหม่ (data) .trim (); System.out.println ("เซิร์ฟเวอร์ส่งข้อความ:"+msg); clientChannel.close (); key.Selector (). Close (); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น ioexception {mynioclient mynioclient = mynioclient ใหม่ (); mynioclient.initClient (); -ผลลัพธ์ผลลัพธ์
ที่นี่ฉันเปิดเซิร์ฟเวอร์และไคลเอนต์สองตัว:
ต่อไปคุณสามารถลองเปิดลูกค้าหนึ่งพันรายในเวลาเดียวกัน ตราบใดที่ CPU ของคุณมีประสิทธิภาพเพียงพอเซิร์ฟเวอร์จะไม่สามารถลดประสิทธิภาพได้เนื่องจากการอุดตัน
ข้างต้นเป็นคำอธิบายโดยละเอียดของ Java Nio หากคุณมีคำถามใด ๆ คุณสามารถพูดคุยในพื้นที่ข้อความด้านล่าง