การเชื่อมต่อทีพีพี
พื้นฐานของ TCP คือ Socket ในการเชื่อมต่อ TCP เราจะใช้ ServerSocket และ Socket หลังจากที่ไคลเอนต์และเซิร์ฟเวอร์สร้างการเชื่อมต่อแล้ว ส่วนที่เหลือจะเป็นการควบคุมของ I/O
ก่อนอื่น มาดูการสื่อสาร TCP แบบง่าย ๆ ซึ่งแบ่งออกเป็นไคลเอนต์และเซิร์ฟเวอร์
รหัสลูกค้าเป็นดังนี้:
โมฆะคงที่สาธารณะ main (String [] args) พ่น IOException
-
ซ็อกเก็ตซ็อกเก็ต = null;
BufferedReader br = null;
PrintWriter pw = null;
BufferedReader brTemp = null;
พยายาม
-
ซ็อกเก็ต = ซ็อกเก็ตใหม่ (InetAddress.getLocalHost(), 5678);
br = ใหม่ BufferedReader (InputStreamReader ใหม่ (socket.getInputStream ()));
pw = PrintWriter ใหม่ (socket.getOutputStream());
brTemp = BufferedReader ใหม่ (InputStreamReader ใหม่ (System.in));
ในขณะที่(จริง)
-
เส้นสตริง = brTemp.readLine();
pw.println(บรรทัด);
pw.ฟลัช();
ถ้า (line.equals("end")) แตก;
System.out.println(br.readLine());
-
-
จับ (ข้อยกเว้นเช่น)
-
System.err.println(เช่นgetMessage());
-
ในที่สุด
-
ถ้า (ซ็อกเก็ต != null) socket.close();
ถ้า (br != null) br.close();
ถ้า (brTemp != null) brTemp.close();
ถ้า (pw != null) pw.close();
-
-
-
โมฆะคงที่สาธารณะ main (String [] args) พ่น IOException
-
เซิร์ฟเวอร์ ServerSocket = null;
ไคลเอนต์ซ็อกเก็ต = null;
BufferedReader br = null;
PrintWriter pw = null;
พยายาม
-
เซิร์ฟเวอร์ = ServerSocket ใหม่ (5678);
ลูกค้า = เซิร์ฟเวอร์ยอมรับ ();
br = ใหม่ BufferedReader (InputStreamReader ใหม่ (client.getInputStream ()));
pw = PrintWriter ใหม่ (client.getOutputStream());
ในขณะที่(จริง)
-
เส้นสตริง = br.readLine();
pw.println("การตอบสนอง:" + บรรทัด);
pw.ฟลัช();
ถ้า (line.equals("end")) แตก;
-
-
จับ (ข้อยกเว้นเช่น)
-
System.err.println(เช่นgetMessage());
-
ในที่สุด
-
ถ้า (เซิร์ฟเวอร์ != null) server.close();
ถ้า (ไคลเอนต์ != null) client.close();
ถ้า (br != null) br.close();
ถ้า (pw != null) pw.close();
-
-
-
โค้ดด้านบนนี้สรุปเฟรมเวิร์กหลักของไคลเอ็นต์และเซิร์ฟเวอร์ในระหว่างกระบวนการสื่อสาร TCP เราพบว่าในโค้ดด้านบนนี้ เซิร์ฟเวอร์สามารถประมวลผลคำขอเดียวจากไคลเอ็นต์ได้ตลอดเวลา นั่นคือการประมวลผลแบบอนุกรม ไม่สามารถขนานได้ นี้ไม่เหมือนกับวิธีการประมวลผลเซิร์ฟเวอร์ในการแสดงผลของเรา เราสามารถเพิ่มหลายเธรดไปยังเซิร์ฟเวอร์ได้ เมื่อมีคำขอของลูกค้าเข้ามา เราจะสร้างเธรดเพื่อประมวลผลคำขอที่เกี่ยวข้อง
รหัสฝั่งเซิร์ฟเวอร์ที่ได้รับการปรับปรุงจะเป็นดังนี้:
คลาส ServerThread ขยายเธรด
-
ซ็อกเก็ตซ็อกเก็ตส่วนตัว = null;
ServerThread สาธารณะ (ซ็อกเก็ตซ็อกเก็ต)
-
this.socket = ซ็อกเก็ต;
-
โมฆะสาธารณะวิ่ง () {
BufferedReader br = null;
PrintWriter pw = null;
พยายาม
-
br = ใหม่ BufferedReader (InputStreamReader ใหม่ (socket.getInputStream ()));
pw = PrintWriter ใหม่ (socket.getOutputStream());
ในขณะที่(จริง)
-
เส้นสตริง = br.readLine();
pw.println("การตอบสนอง:" + บรรทัด);
pw.ฟลัช();
ถ้า (line.equals("end")) แตก;
-
-
จับ (ข้อยกเว้นเช่น)
-
System.err.println(เช่นgetMessage());
-
ในที่สุด
-
ถ้า (ซ็อกเก็ต != null)
พยายาม {
ซ็อกเก็ต.ปิด();
} จับ (IOException e1) {
e1.printStackTrace();
-
ถ้า (br != null)
พยายาม {
br.ปิด();
} จับ (IOException จ) {
e.printStackTrace();
-
ถ้า (pw != null) pw.close();
-
-
-
ในกระบวนการเขียนโปรแกรม เรามีแนวคิดเรื่อง "ทรัพยากร" ตัวอย่างเช่น การเชื่อมต่อฐานข้อมูลเป็นทรัพยากรทั่วไป เพื่อปรับปรุงประสิทธิภาพ เรามักจะไม่ทำลายการเชื่อมต่อฐานข้อมูลโดยตรง แต่ใช้พูลการเชื่อมต่อฐานข้อมูลเพื่อจัดการหลายรายการ ฐานข้อมูล การเชื่อมต่อได้รับการจัดการและนำกลับมาใช้ใหม่ สำหรับการเชื่อมต่อซ็อกเก็ต มันก็เป็นทรัพยากรเช่นกัน เมื่อโปรแกรมของเราต้องการการเชื่อมต่อซ็อกเก็ตจำนวนมาก มันจะเป็นวิธีที่ไม่มีประสิทธิภาพมากหากจำเป็นต้องสร้างการเชื่อมต่อแต่ละครั้งใหม่
เช่นเดียวกับพูลการเชื่อมต่อฐานข้อมูล เรายังสามารถออกแบบพูลการเชื่อมต่อ TCP ได้ด้วย แนวคิดนี้คือ เราใช้อาร์เรย์เพื่อรักษาการเชื่อมต่อซ็อกเก็ตหลายรายการ และอาร์เรย์สถานะอื่นเพื่ออธิบายว่ามีการใช้งานการเชื่อมต่อซ็อกเก็ตแต่ละรายการหรือไม่ การเชื่อมต่อซ็อกเก็ต เราจะสำรวจอาร์เรย์สถานะและนำการเชื่อมต่อซ็อกเก็ตที่ไม่ได้ใช้ครั้งแรกออก หากมีการใช้งานการเชื่อมต่อทั้งหมด จะมีข้อยกเว้นเกิดขึ้น นี่เป็น "กลยุทธ์การกำหนดเวลา" ที่ใช้งานง่ายและเรียบง่าย ในกรอบงานโอเพ่นซอร์สหรือเชิงพาณิชย์จำนวนมาก (Apache/Tomcat) จะมี "กลุ่มทรัพยากร" ที่คล้ายกัน
รหัสสำหรับพูลการเชื่อมต่อ TCP จะเป็นดังนี้:
ที่อยู่ InetAddress ส่วนตัว = null;
พอร์ต int ส่วนตัว
ซ็อกเก็ตส่วนตัว [] arrSockets = null;
บูลีนส่วนตัว [] arrStatus = null;
จำนวน int ส่วนตัว
TcpConnectionPool สาธารณะ (ที่อยู่ InetAddress, พอร์ต int, จำนวน int)
-
this.address = ที่อยู่;
this.port = พอร์ต;
นี้ .count = นับ;
arrSockets = ซ็อกเก็ตใหม่ [นับ];
arrStatus = บูลีนใหม่ [นับ];
เริ่มต้น();
-
โมฆะส่วนตัว init()
-
พยายาม
-
สำหรับ (int i = 0; i < นับ; i++)
-
arrSockets[i] = ซ็อกเก็ตใหม่ (address.getHostAddress(), พอร์ต);
arrStatus[i] = เท็จ;
-
-
จับ (ข้อยกเว้นเช่น)
-
System.err.println(เช่นgetMessage());
-
-
สาธารณะซ็อกเก็ต getConnection ()
-
ถ้า (arrSockets == null) init();
อินท์ i = 0;
สำหรับ(i = 0; i <นับ; i++)
-
ถ้า (arrStatus[i] == เท็จ)
-
arrStatus[i] = จริง;
หยุดพัก;
-
-
ถ้า (i == นับ) โยน RuntimeException ใหม่ ("ไม่มีการเชื่อมต่อในขณะนี้");
กลับ arrSockets [i];
-
การเชื่อมต่อการปล่อยโมฆะสาธารณะ (ซ็อกเก็ตซ็อกเก็ต)
-
ถ้า (arrSockets == null) init();
สำหรับ (int i = 0; i < นับ; i++)
-
ถ้า (arrSockets[i] == ซ็อกเก็ต)
-
arrStatus[i] = เท็จ;
หยุดพัก;
-
-
-
โมฆะสาธารณะ reBuild()
-
เริ่มต้น();
-
โมฆะสาธารณะทำลาย ()
-
ถ้า (arrSockets == null) กลับมา;
สำหรับ (int i = 0; i < นับ; i ++)
-
พยายาม
-
arrSockets[i].ปิด();
-
จับ (ข้อยกเว้นเช่น)
-
System.err.println(เช่นgetMessage());
ดำเนินการต่อ;
-
-
-
-
UDP เป็นวิธีการเชื่อมต่อที่แตกต่างจาก TCP โดยปกติจะใช้ในสถานการณ์ที่ต้องการประสิทธิภาพแบบเรียลไทม์สูงและความต้องการความแม่นยำต่ำ เช่น วิดีโอออนไลน์ UDP จะมี "การสูญเสียแพ็กเก็ต" ใน TCP หากเซิร์ฟเวอร์ไม่เริ่มทำงาน จะมีการรายงานข้อยกเว้นเมื่อไคลเอนต์ส่งข้อความ แต่สำหรับ UDP จะไม่มีการสร้างข้อยกเว้น
สองคลาสที่ใช้สำหรับการสื่อสาร UDP คือ DatagramSocket และ DatagramPacket ซึ่งส่วนหลังเก็บเนื้อหาของการสื่อสาร
ต่อไปนี้เป็นตัวอย่างการสื่อสาร UDP อย่างง่าย เช่นเดียวกับ TCP มันถูกแบ่งออกเป็นสองส่วน: ไคลเอนต์และเซิร์ฟเวอร์ รหัสไคลเอนต์มีดังนี้:
โมฆะสาธารณะคง main (String [] args)
-
พยายาม
-
โฮสต์ InetAddress = InetAddress.getLocalHost();
พอร์ต int = 5678;
BufferedReader br = BufferedReader ใหม่ (InputStreamReader ใหม่ (System.in));
ในขณะที่(จริง)
-
เส้นสตริง = br.readLine();
ข้อความไบต์ [] = line.getBytes();
DatagramPacket packet = DatagramPacket ใหม่ (ข้อความ, message.length, โฮสต์, พอร์ต);
ซ็อกเก็ต DatagramSocket = DatagramSocket ใหม่ ();
socket.send (แพ็คเก็ต);
ซ็อกเก็ต.ปิด();
ถ้า (line.equals("end")) แตก;
-
br.ปิด();
-
จับ (ข้อยกเว้นเช่น)
-
System.err.println(เช่นgetMessage());
-
-
-
โมฆะสาธารณะคง main (String [] args)
-
พยายาม
-
พอร์ต int = 5678;
DatagramSocket dsSocket = DatagramSocket ใหม่ (พอร์ต);
ไบต์ [] บัฟเฟอร์ = ไบต์ใหม่ [1024];
แพ็กเก็ต DatagramPacket = DatagramPacket ใหม่ (บัฟเฟอร์, buffer.length);
ในขณะที่(จริง)
-
dsSocket.receive (แพ็กเก็ต);
ข้อความสตริง = สตริงใหม่ (บัฟเฟอร์, 0, packet.getLength ());
System.out.println(packet.getAddress().getHostName() + /// + ข้อความ);
ถ้า (message.equals("end")) พัง;
packet.setLength(บัฟเฟอร์.ความยาว);
-
dsSocket.ปิด();
-
จับ (ข้อยกเว้นเช่น)
-
System.err.println(เช่นgetMessage());
-
-
-
Multicast ใช้วิธีการเดียวกันกับ UDP โดยจะใช้ที่อยู่ IP คลาส D และหมายเลขพอร์ต UDP มาตรฐาน อ้างอิงถึงที่อยู่ระหว่าง 224.0.0.0 ถึง 239.255.255.255 ไม่รวม 224.0.0.0
คลาสที่ใช้ในมัลติคาสต์คือ MulticastSocket ซึ่งมีสองวิธีที่ต้องใส่ใจ: joinGroup และ leaveGroup
ต่อไปนี้เป็นตัวอย่างของมัลติคาสต์รหัสไคลเอ็นต์จะเป็นดังนี้:
โมฆะสาธารณะคง main (String [] args)
-
BufferedReader br = BufferedReader ใหม่ (InputStreamReader ใหม่ (System.in));
พยายาม
-
ที่อยู่ InetAddress = InetAddress.getByName("230.0.0.1");
พอร์ต int = 5678;
ในขณะที่(จริง)
-
เส้นสตริง = br.readLine();
ข้อความไบต์ [] = line.getBytes();
DatagramPacket packet = DatagramPacket ใหม่ (ข้อความ, message.length, ที่อยู่, พอร์ต);
MulticastSocket multicastSocket = MulticastSocket ใหม่ ();
multicastSocket.send (แพ็กเก็ต);
ถ้า (line.equals("end")) แตก;
-
br.ปิด();
-
จับ (ข้อยกเว้นเช่น)
-
System.err.println(เช่นgetMessage());
-
-
-
โมฆะสาธารณะคง main (String [] args)
-
พอร์ต int = 5678;
พยายาม
-
MulticastSocket multicastSocket = MulticastSocket ใหม่ (พอร์ต);
ที่อยู่ InetAddress = InetAddress.getByName("230.0.0.1");
multicastSocket.joinGroup (ที่อยู่);
ไบต์ [] บัฟเฟอร์ = ไบต์ใหม่ [1024];
แพ็กเก็ต DatagramPacket = DatagramPacket ใหม่ (บัฟเฟอร์, buffer.length);
ในขณะที่(จริง)
-
multicastSocket.receive (แพ็กเก็ต);
ข้อความสตริง = สตริงใหม่ (บัฟเฟอร์, packet.getLength());
System.out.println(packet.getAddress().getHostName() + /// + ข้อความ);
ถ้า (message.equals("end")) พัง;
packet.setLength(บัฟเฟอร์.ความยาว);
-
multicastSocket.close();
-
จับ (ข้อยกเว้นเช่น)
-
System.err.println(เช่นgetMessage());
-
-
-
NIO คือชุดใหม่ของ IO API ที่เปิดตัวใน JDK1.4 โดยมีการออกแบบใหม่ในการจัดการบัฟเฟอร์ การสื่อสารเครือข่าย การเข้าถึงไฟล์ และการดำเนินการชุดอักขระ สำหรับการสื่อสารผ่านเครือข่าย NIO ใช้แนวคิดเรื่องบัฟเฟอร์และช่องสัญญาณ
ต่อไปนี้เป็นตัวอย่างของ NIO ซึ่งแตกต่างจากรูปแบบการเขียนโค้ดที่เรากล่าวถึงข้างต้นอย่างมาก
โมฆะสาธารณะคง main (String [] args)
-
โฮสต์สตริง = "127.0.0.1";
พอร์ต int = 5678;
ช่อง SocketChannel = null;
พยายาม
-
ที่อยู่ InetSocketAddress = InetSocketAddress ใหม่ (โฮสต์, พอร์ต);
ชุดอักขระ ชุดอักขระ = Charset.forName("UTF-8");
ตัวถอดรหัส CharsetDecoder = charset.newDecoder();
ตัวเข้ารหัส CharsetEncoder = charset.newEncoder();
ByteBuffer buffer = ByteBuffer.allocate (1024);
CharBuffer charBuffer = CharBuffer.จัดสรร (1024);
ช่อง = SocketChannel.open();
ช่องเชื่อมต่อ(ที่อยู่);
คำขอสตริง = "GET / /r/n/r/n";
channel.write(encoder.encode(CharBuffer.wrap(คำขอ)));
ในขณะที่ ((channel.read(buffer)) != -1)
-
buffer.พลิก();
decoder.decode (บัฟเฟอร์, charBuffer, เท็จ);
charBuffer.flip();
System.out.println(charBuffer);
บัฟเฟอร์.ชัดเจน();
charBuffer.clear();
-
-
จับ (ข้อยกเว้นเช่น)
-
System.err.println(เช่นgetMessage());
-
ในที่สุด
-
ถ้า (ช่อง != null)
พยายาม {
ช่อง.ปิด();
} จับ (IOException จ) {
// TODO บล็อก catch ที่สร้างขึ้นอัตโนมัติ
e.printStackTrace();
-
-
-
-