1. Trang chủ
  2. » Công Nghệ Thông Tin

Giáo trình Lập trình mạng: Phần 1 - Trường ĐH Tây Bắc

76 13 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Giáo Trình Lập Trình Mạng
Tác giả Nguyễn Duy Hiếu, Mai Văn Tám
Trường học Trường Đại Học Tây Bắc
Chuyên ngành Lập Trình Mạng
Thể loại giáo trình
Năm xuất bản 2019
Thành phố Sơn La
Định dạng
Số trang 76
Dung lượng 1,89 MB

Cấu trúc

  • CHƯƠNG 1. CÁC KHÁI NIỆM CƠ BẢN VỀ MẠNG MÁY TÍNH (11)
    • 1.1 Mạng máy tính (11)
    • 1.2. Các lớp của một mạng (12)
      • 1.2.1 Lớp máy tính-mạng (14)
      • 1.2.2 Lớp Internet (14)
      • 1.2.3 Lớp giao vận (15)
      • 1.2.4 Lớp ứng dụng (16)
    • 1.3 Giao thức IP, TCP và UDP (16)
      • 1.3.1 Khái quát về giao thức IP, TCP và UDP (16)
      • 1.3.2 Địa chỉ IP và tên miền (17)
      • 1.3.3 Các cổng (19)
    • 1.4 Mạng Internet (20)
      • 1.4.1 Các khối địa chỉ Internet (20)
      • 1.4.2 Dịch địa chỉ mạng (21)
      • 1.4.3 Tường lửa (21)
      • 1.4.4 Máy chủ proxy (21)
      • 1.4.5 Mô hình Client/Server (23)
  • CHƯƠNG 2. CÁC DÒNG VÀO-RA (STREAM) (25)
    • 2.1 Các dòng ra (output stream) (25)
    • 2.2 Các dòng vào (input stream) (29)
    • 2.3 Các dòng filter stream (33)
      • 2.3.1 Gắn kết các filter stream (35)
      • 2.3.2 Các lớp BufferedInputStream và BufferedOutputStream (35)
      • 2.3.3 Lớp PrintStream (36)
      • 2.3.4 Các lớp DataInputStream và DataOutputStream (37)
    • 2.4 Các lớp Reader and Writer (39)
      • 2.4.1 Lớp Writer (39)
      • 2.4.3 Lớp Reader (40)
      • 2.4.4 Các lớp Filter Reader và Filter Writer (41)
      • 2.4.5 Lớp Scanner (42)
      • 2.4.6 Lớp PrintWriter (43)
  • CHƯƠNG 3. LẬP TRÌNH ĐA LUỒNG TRONG JAVA (45)
    • 3.1 Giới thiệu về luồng (thread) (45)
      • 3.1.1 Thread là gì? Multi-thread là gì? (45)
      • 3.1.2 Đa nhiệm (multitasking) (45)
      • 3.1.3 Ưu điểm và nhược của đa luồng (46)
    • 3.2 Vòng đời của một luồng trong Java (46)
    • 3.3 Cách tạo luồng trong Java (47)
      • 3.3.1 Tạo luồng bằng cách kế thừa từ lớp Thread (48)
      • 3.3.2 Tạo luồng bằng cách hiện thực từ giao diện Runnable (48)
    • 3.4 Ví dụ minh họa sử dụng đa luồng (49)
    • 3.5 Các phương thức của lớp Thread thường hay sử dụng (53)
    • 3.6 Một số vấn đề liên quan đến luồng (54)
      • 3.6.1 Một số tham số của luồng (54)
      • 3.6.2 Sử dụng phương thức sleep() (56)
      • 3.6.3 Sử dụng join() và join(long millis) (57)
      • 3.6.4 Xử lý ngoại lệ cho luồng (59)
  • CHƯƠNG 4. LỚP INETADDRESS (61)
    • 4.1 Khởi tạo đối tượng InetAddress (63)
    • 4.2 Nhớ đệm (caching) (65)
    • 4.3 Tìm kiếm bằng địa chỉ IP (66)
    • 4.4 Các phương thức Get (66)
    • 4.5 Kiểm tra loại địa chỉ (68)
    • 4.6 Kiểm tra khả năng kết nối (reachable) (72)
    • 4.7 Các phương thức của Object (72)
    • 4.9 Lớp NetworkInterface (74)
  • CHƯƠNG 5. LẬP TRÌNH VỚI GIAO THỨC TCP (0)
    • 5.1 Khái niệm chung (0)
    • 5.2 Khái niệm cổng (port number) (0)
    • 5.3 Lớp Socket (0)
      • 5.3.1 Các phương thức tạo (0)
      • 5.3.2 Các phương thức kiểm soát vào-ra (0)
      • 5.3.3 Một số phương thức khác (0)
    • 5.4 Lớp ServerSocket (0)
      • 5.4.1 Các phương thức tạo (0)
      • 5.4.2 Các phương thức khác (0)
    • 5.5 Lập trình TCP bằng mô hình Client/Server (0)
    • 5.6 Xử lý ngoại lệ trong lập trình mạng (0)
    • 5.7 Một số ví dụ (0)
  • CHƯƠNG 6. LẬP TRÌNH VỚI GIAO THỨC UDP (0)
    • 6.1 Khái niệm chung (0)
    • 6.2 Lớp DatagramSocket (0)
    • 6.3 Lớp DatagramPacket (0)
    • 6.4 Lập trình UDP theo mô hình Client/Server (0)
    • 6.5 Một số ví dụ (0)
  • CHƯƠNG 7. KỸ THUẬT LẬP TRÌNH PHÂN TÁN RMI (0)
    • 7.1 Khái niệm chung (0)
    • 7.2 Kỹ thuật lập trình RMI theo mô hình Client/Server (0)
    • 7.3 Một số ví dụ (0)

Nội dung

Giáo trình Lập trình mạng: Phần 1 cung cấp cho người học những kiến thức như: Các khái niệm cơ bản về mạng máy tính; Các dòng vào-ra (stream); Lập trình đa luồng trong java; Lớp inetaddress. Mời các bạn cùng tham khảo!

CÁC KHÁI NIỆM CƠ BẢN VỀ MẠNG MÁY TÍNH

Mạng máy tính

Mạng máy tính là tập hợp các máy tính và thiết bị cho phép gửi và nhận dữ liệu lẫn nhau Trong mạng có dây, dữ liệu được chuyển đổi thành sóng điện từ di chuyển qua dây dẫn, trong khi mạng không dây sử dụng sóng vô tuyến để truyền thông tin Đối với khoảng cách lớn, cáp quang là phương tiện truyền tải dữ liệu hiệu quả, sử dụng sóng ánh sáng với các bước sóng khác nhau.

Mỗi thiết bị trong mạng được gọi là nút mạng (node), bao gồm không chỉ máy tính mà còn máy in, router, cầu nối và gateway, thiết bị kết nối các mạng khác nhau Ngoài ra, nút mạng còn có thể là các thiết bị cuối câm, không có bộ xử lý trung tâm và ổ đĩa.

Trong bài viết này, chúng ta sẽ sử dụng thuật ngữ "nút" để chỉ bất kỳ thiết bị nào trên mạng, trong khi "host" sẽ được dùng để chỉ một nút là máy tính đa năng Mỗi nút mạng đều có một địa chỉ riêng biệt.

Địa chỉ là chuỗi byte xác định duy nhất một nút trong mạng, và mỗi mạng có địa chỉ khác nhau Địa chỉ Ethernet được gán cho phần cứng vật lý, với các nhà sản xuất sử dụng mã nhà sản xuất để tránh xung đột địa chỉ Họ phải đảm bảo rằng không có hai card mạng Ethernet nào có cùng địa chỉ Trong khi đó, địa chỉ Internet thường được cấp cho máy tính bởi Nhà cung cấp dịch vụ Internet (ISP).

Các mạng máy tính hiện đại hoạt động dựa trên công nghệ chuyển mạch gói, trong đó dữ liệu được chia nhỏ thành các gói Mỗi gói chứa thông tin về địa chỉ của bên gửi và bên nhận Việc phân chia dữ liệu thành các gói có nhiều ưu điểm, bao gồm khả năng truyền tải hiệu quả và cải thiện độ tin cậy của việc truyền thông.

Các gói tin từ nhiều nguồn khác nhau có thể được truyền trên cùng một đường dây, giúp giảm chi phí xây dựng mạng Điều này cho phép các máy tính chia sẻ chung một đường truyền mà không lo bị can nhiễu lẫn nhau.

Các mã kiểm tra tổng (checksum) giúp phát hiện gói tin bị hư hại trong quá trình truyền tin Để mạng máy tính hoạt động hiệu quả, cần có các quy tắc gọi là giao thức (protocol) Giao thức là tập hợp các luật định nghĩa cách thức giao tiếp giữa các máy tính, bao gồm khuôn dạng địa chỉ và cách chia dữ liệu thành các gói.

+ Giao thức HTTP (Hypertext Transfer Protocol) quy định cách thức trao đổi thông tin giữa các trình duyệt web (web browsers) với máy chủ dịch vụ web (webserver)

+ Chuẩn IEEE 802.3 quy định cách mã hóa các bit thành các tín hiệu điện trên từng loại dây dẫn cụ thể

Các chuẩn giao thức mở cho phép phần mềm và thiết bị từ nhiều nhà cung cấp khác nhau giao tiếp hiệu quả Chẳng hạn, máy chủ web không cần lo lắng về hệ điều hành của người dùng, cho dù đó là Windows, Unix, Android hay iOS, vì tất cả đều sử dụng giao thức HTTP chung.

Các lớp của một mạng

Truyền dữ liệu trên mạng là một quá trình phức tạp, nhưng để người phát triển ứng dụng và người sử dụng không phải đối mặt với sự phức tạp này, các thành phần mạng được chia thành nhiều lớp Mỗi lớp đại diện cho một mức độ trừu tượng khác nhau giữa phần cứng vật lý như dây dẫn và điện, và thông tin được truyền.

Mỗi lớp trong mạng chỉ tương tác với lớp ngay trên và lớp ngay dưới, cho phép sửa đổi hoặc thay thế phần mềm trong một lớp mà không ảnh hưởng đến các lớp khác, miễn là các giao diện giữa các lớp giữ nguyên.

Hình 1.1 minh họa mô hình phân tầng của các giao thức trong mạng, cho thấy rằng các giao thức ở các lớp giữa trong mạng Internet thường ổn định, trong khi các giao thức ở tầng trên cùng và dưới cùng có sự thay đổi đáng kể.

Có nhiều mô hình chia lớp như OSI và TCP/IP, mỗi mô hình phục vụ cho các yêu cầu của từng loại mạng cụ thể Tài liệu này sẽ tập trung vào mô hình TCP/IP, một mô hình chuẩn bốn lớp phù hợp với mạng Internet Sơ đồ dưới đây sẽ minh họa cấu trúc của mô hình TCP/IP.

Hình 1.2: Các lớp trong mô hình TCP/IP

Các lớp trong mô hình TCP/IP bao gồm:

+ Lớp ứng dụng (Application Layer)

+ Lớp giao vận (Transport Layer) sử dụng các giao thức TCP hoặc UDP + Lớp Internet (Internet Layer) sử dụng giao thức IP

+ Lớp máy tính-mạng (Host-To-Network Layer) sử dụng các giao thức Ethernet, WiFi, LTE…

Ứng dụng hoạt động trong lớp ứng dụng và chỉ giao tiếp với lớp giao vận, trong khi lớp giao vận kết nối với lớp Internet và lớp ứng dụng Lớp Internet chỉ trao đổi thông tin với lớp giao vận và lớp máy tính-mạng, không trực tiếp với lớp ứng dụng Lớp máy tính-mạng truyền dữ liệu qua các phương tiện vật lý như dây dẫn và cáp quang đến các hệ thống từ xa Sau đó, lớp máy tính-mạng trên hệ thống từ xa sẽ chuyển dữ liệu lên các lớp trên cùng, cuối cùng đến lớp ứng dụng của hệ thống này Chúng ta sẽ khám phá chức năng của từng lớp trong các phần tiếp theo.

Lớp máy tính-mạng (lớp vật lý) quy định cách thức một giao diện mạng cụ thể

Một card Ethernet hoặc ăng-ten WiFi có khả năng gửi các IP datagram qua kết nối vật lý của nó, kết nối với mạng cục bộ và mạng diện rộng.

Lớp máy tính-mạng, hay còn gọi là lớp vật lý, bao gồm phần cứng kết nối các máy tính như dây dẫn, cáp quang và sóng vô tuyến Các lập trình viên sử dụng ngôn ngữ Java thường không cần quan tâm đến lớp này trừ khi gặp phải các vấn đề kỹ thuật Họ chỉ cần chú ý đến hiệu suất mạng; ví dụ, khi khách hàng sử dụng mạng cáp quang tốc độ cao, việc thiết kế giao thức và ứng dụng phù hợp với tốc độ mạng là rất quan trọng.

Lớp Internet, hay còn gọi là lớp mạng trong mô hình OSI, là lớp quan trọng đầu tiên trong một mạng Giao thức của lớp mạng quy định cách tổ chức dữ liệu thành các packet lớn hơn và thiết lập mô hình đánh địa chỉ để các máy tính có thể tìm thấy nhau Giao thức Internet (IP) là giao thức phổ biến nhất trên toàn cầu, và Java hỗ trợ hiểu các giao thức của lớp này.

Giao thức IP hiện có hai phiên bản chính là IPv4 và IPv6 Trong đó, IPv4 sử dụng địa chỉ 32 bit, trong khi IPv6 sử dụng địa chỉ 128 bit, mang lại nhiều tính năng kỹ thuật hỗ trợ quá trình định tuyến hiệu quả hơn IPv6 đang ngày càng được áp dụng rộng rãi và có khả năng vượt qua IPv4 về số lượng người dùng Để hai giao thức này có thể hoạt động song song trên cùng một mạng, cần thiết phải sử dụng các gateway đặc biệt và các giao thức đường ống (tunnel).

Cả hai giao thức IPv4 và IPv6 đều sử dụng lớp Internet để gửi dữ liệu qua các gói tin gọi là datagram Datagram trong IPv4 có phần đầu dài từ 20 đến 60 byte và payload tối đa 65.515 byte, nhưng thực tế thường chỉ từ vài chục byte đến khoảng 8 kilobyte Trong khi đó, datagram của IPv6 có phần đầu lớn hơn và payload có thể lên đến 4 gigabyte.

Hình 1.3 minh họa cấu trúc của một IPv4 datagram, trong đó tất cả các bit và byte được trình bày theo định dạng big-endian, từ bit cao nhất (MSB) đến bit thấp nhất (LSB) từ trái sang phải.

Hình 1.3: Cấu trúc của một IPv4 datagram

Lớp Internet không chỉ đảm nhận chức năng định tuyến và đánh địa chỉ, mà còn cho phép các máy tính trên các mạng khác nhau có thể giao tiếp với nhau Các router Internet thực hiện việc chuyển đổi giữa các giao thức như WiFi, Ethernet, DSL và cáp quang Nếu không có lớp Internet, các máy tính chỉ có thể trao đổi thông tin với những máy tính cùng kiểu mạng Do đó, lớp Internet đóng vai trò quan trọng trong việc kết nối các mạng không đồng nhất sử dụng các giao thức khác nhau.

Các datagram chưa được xử lý gặp nhiều hạn chế, đáng chú ý nhất là không có đảm bảo nào cho việc chuyển giao tới bên nhận Ngay cả khi được chuyển đi, các datagram có thể bị hư hỏng trong quá trình truyền Phần kiểm tra tổng của header chỉ phát hiện hư hỏng trong phần header, không thể phát hiện hư hỏng trong dữ liệu Hơn nữa, các datagram có thể đến đích không theo thứ tự gửi, vì chúng có thể đi qua những tuyến đường khác nhau Ví dụ, datagram A gửi trước datagram B nhưng không đảm bảo A sẽ đến trước B.

Lớp giao vận đảm bảo rằng các gói tin được nhận theo đúng thứ tự và dữ liệu không bị mất hay hư hỏng Nếu một gói tin bị mất, lớp giao vận có khả năng yêu cầu bên gửi gửi lại gói tin đó Các mạng IP thực hiện cơ chế này bằng cách thêm một phần header vào mỗi datagram Hai giao thức quan trọng nhất tại lớp giao vận là TCP (Transmission Control Protocol) và UDP (User Datagram Protocol).

Giao thức TCP là một giao thức đáng tin cậy, cho phép truyền tải dữ liệu bị mất hoặc hư hỏng và đảm bảo các byte được chuyển giao theo đúng thứ tự Ngược lại, giao thức UDP, mặc dù nhanh hơn nhiều so với TCP, lại không đảm bảo thứ tự và độ tin cậy trong việc chuyển giao dữ liệu, nhưng vẫn được sử dụng phổ biến trong các mạng Internet.

Giao thức IP, TCP và UDP

1.3.1 Khái quát về giao thức IP, TCP và UDP

Giao thức Internet (IP) được phát triển trong bối cảnh Chiến tranh Lạnh giữa Liên Xô và Mỹ, với sự hỗ trợ từ quân đội Mỹ Mục tiêu chính của IP là đảm bảo rằng hệ thống mạng vẫn hoạt động liên tục ngay cả khi xảy ra tấn công hạt nhân, ví dụ như tại Cleveland Để đạt được điều này, giao thức IP được thiết kế với khả năng tạo ra nhiều tuyến đường giữa hai điểm và định tuyến các gói tin qua những router không bị hỏng, đảm bảo thông tin vẫn được chuyển đến đích.

Quân đội Mỹ sử dụng nhiều loại máy tính khác nhau, yêu cầu giao thức IP phải là giao thức mở và không phụ thuộc vào hệ điều hành để đảm bảo khả năng trao đổi thông tin Do có nhiều tuyến đường giữa hai điểm và đường đi nhanh nhất có thể thay đổi, các gói tin trong một luồng dữ liệu có thể không đi cùng một tuyến đường và có thể đến đích không theo thứ tự Để khắc phục điều này, giao thức TCP được sử dụng bên trên IP, cho phép điểm cuối phản hồi các gói tin đã nhận và yêu cầu gửi lại các gói tin bị mất hoặc hư hỏng, đồng thời sắp xếp lại các gói tin theo thứ tự đã gửi Tuy nhiên, TCP có overhead cao, ảnh hưởng đến tốc độ truyền gói tin Nếu thứ tự và tính toàn vẹn của gói tin không quan trọng, giao thức UDP có thể được sử dụng để gửi gói tin mà không cần đảm bảo.

Giao thức UDP, mặc dù không tin cậy, có thể ảnh hưởng đến việc truyền file, nhưng lại phù hợp cho các ứng dụng mà việc mất một phần dữ liệu không ảnh hưởng đến trải nghiệm người dùng cuối Chẳng hạn, việc mất một vài bit trong tín hiệu video hoặc audio thường không làm giảm chất lượng của dòng dữ liệu Để khắc phục các thông tin bị mất, các mã sửa lỗi có thể được tích hợp vào dòng dữ liệu sử dụng UDP ở mức ứng dụng.

Nhiều giao thức có thể hoạt động trên giao thức IP, trong đó nổi bật nhất là giao thức Internet Control Message Protocol (ICMP), dùng để chuyển tiếp thông báo lỗi giữa các máy tính thông qua các datagram chưa được xử lý Ping là ứng dụng phổ biến nhất trong ICMP Tuy nhiên, Java chỉ hỗ trợ các giao thức TCP, UDP và các giao thức trong lớp ứng dụng, mà không hỗ trợ ICMP Để cài đặt các giao thức khác thuộc lớp giao vận, lớp Internet và các lớp bên dưới trong Java, người dùng cần sử dụng mã gốc (native code).

1.3.2 Địa chỉ IP và tên miền

Mỗi máy tính trong mạng IPv4 được xác định bởi một địa chỉ IP duy nhất, bao gồm 4 byte và thường được viết dưới dạng bốn chữ số thập phân ngăn cách nhau bởi dấu chấm, ví dụ 192.168.1.1 Mỗi chữ số thập phân đại diện cho một byte, với phạm vi từ 0 đến 255 Khi gói tin được truyền, địa chỉ IP của máy gửi và máy nhận nằm trong phần header của gói tin Các router sẽ sử dụng địa chỉ IP của máy nhận để chọn tuyến đường tốt nhất cho gói tin, trong khi máy nhận sẽ dùng địa chỉ IP của máy gửi để xác định nguồn gốc gói tin.

Có hơn bốn tỷ địa chỉ IPv4, nhưng không phải tất cả đều được sử dụng Đến tháng 9 năm 2012, châu Âu đã hết sạch các địa chỉ IP được phân phối Mặc dù Bắc Mỹ, Mỹ Latinh và châu Phi vẫn còn một số khối địa chỉ IP chưa sử dụng, nhưng chúng sẽ sớm được khai thác hết.

Sự chuyển đổi từ IPv4 sang IPv6 đang diễn ra mạnh mẽ, với IPv6 cung cấp địa chỉ dài 16 byte, đủ khả năng cấp phát địa chỉ IP cho toàn bộ nhân loại, máy tính và tất cả các thiết bị trên toàn cầu Cách biểu diễn địa chỉ IPv6 mang lại sự linh hoạt và khả năng mở rộng cần thiết cho tương lai.

Địa chỉ IPv6 thường được cấu trúc thành tám khối, mỗi khối bao gồm bốn chữ số trong hệ thập phân, được ngăn cách bởi dấu hai chấm (:) Ví dụ về một địa chỉ IPv6 là FEDC:BA98:7654:3210:FEDC:BA98:7654:3210.

Trong địa chỉ IPv6, các chữ số không (0) ở đầu không cần phải viết Nếu có nhiều khối toàn số 0, chúng có thể được thay thế bằng cặp dấu :: Ví dụ, địa chỉ FEDC:0000:0000:0000:00DC:0000:7076:0010 có thể được rút gọn thành FEDC::DC:0:7076:10 Lưu ý rằng mỗi cặp :: chỉ được phép xuất hiện một lần trong một địa chỉ IPv6.

Trong mạng kết hợp giữa IPv4 và IPv6, bốn byte cuối của địa chỉ IPv6 có thể được biểu diễn dưới dạng địa chỉ IPv4 Ví dụ, địa chỉ IPv6 FEDC:BA98:7654:3210:FEDC:BA98:7654:3210 có thể được viết lại thành FEDC:BA98:7654:3210:FEDC:BA98:118.84.50.16.

Con người gặp khó khăn trong việc nhớ các địa chỉ IP, đặc biệt là địa chỉ IPv6 Để giải quyết vấn đề này, hệ thống tên miền Domain Name System (DNS) đã được phát triển nhằm chuyển đổi địa chỉ IP thành các tên máy dễ nhớ, ví dụ như từ 208.201.239.101 thành www.oreilly.com Khi các chương trình Java truy cập mạng, chúng cần xử lý cả địa chỉ số và hostname tương ứng, và lớp java.net.InetAddress cung cấp các phương thức cần thiết cho công việc này.

Một số máy tính, đặc biệt là máy chủ, có địa chỉ IP cố định, trong khi các máy tính khác, như máy khách trên mạng cục bộ và kết nối không dây, nhận địa chỉ IP khác nhau mỗi khi khởi động và kết nối vào mạng Những địa chỉ này được cung cấp bởi DHCP Server, viết tắt của Giao thức cấu hình host động (Dynamic Host Configuration Protocol).

Trong địa chỉ IPv4, các khối địa chỉ đặc biệt như 10., 172.16 đến 172.31 và 192.168 là các khối địa chỉ không thể định tuyến (non-routable) và chỉ có thể sử dụng trên mạng cục bộ, không cho phép truy cập Internet Những địa chỉ này rất phù hợp cho việc xây dựng mạng riêng mà không xuất hiện trên Internet Địa chỉ IPv4 bắt đầu bằng 127 được gọi là địa chỉ loopback cục bộ, thường được nhận diện qua hostname localhost Trong IPv6, địa chỉ loopback tương ứng là 0:0:0:0:0:0:0:1 (hay ::1) Địa chỉ 0.0.0.0 chỉ đến máy gửi và có thể được dùng làm địa chỉ nguồn mà không phải địa chỉ đích, trong khi bất kỳ địa chỉ nào bắt đầu bằng 0 trong IPv4 chỉ đến một máy tính trong cùng mạng cục bộ.

Trong IPv4, địa chỉ 255.255.255.255 được sử dụng làm địa chỉ quảng bá (broadcast address), cho phép tất cả các máy trong mạng cục bộ nhận gói tin gửi đến địa chỉ này mà không được phát ra bên ngoài mạng Địa chỉ quảng bá thường được sử dụng để phát hiện các máy trong cùng một mạng, ví dụ khi một máy tính khởi động sẽ gửi thông báo đến địa chỉ 255.255.255.255 để tìm DHCP Server Tất cả máy tính trong mạng sẽ nhận thông báo này, nhưng chỉ có DHCP Server phản hồi với thông tin cấu hình mạng, bao gồm địa chỉ IP và địa chỉ DNS Server để máy tính có thể phân giải tên miền.

Các máy tính hiện đại có khả năng xử lý nhiều tác vụ đồng thời, bao gồm việc tách biệt email, yêu cầu FTP và lưu lượng truy cập web thông qua các cổng (port) Mỗi máy tính với địa chỉ IP có tới 65.535 cổng logic, hoàn toàn trừu tượng trong bộ nhớ và không phải là cổng vật lý như USB Mỗi cổng được xác định bằng một số từ 1 đến 65.535 và có thể được gán cho dịch vụ cụ thể; ví dụ, giao thức HTTP sử dụng cổng 80 Khi một máy tính gửi yêu cầu đến máy chủ web, nó sẽ gửi dữ liệu tới cổng 80, và máy chủ sẽ kiểm tra số hiệu cổng để gửi dữ liệu đến chương trình đang lắng nghe trên cổng đó Danh sách các cổng thường được sử dụng trong các ứng dụng sẽ được liệt kê trong bảng sau.

Giao thức Cổng Giao thức Mục đích echo 7 TCP/UDP

Mạng Internet

1.4.1 Các khối địa chỉ Internet

Mỗi nhà cung cấp dịch vụ Internet (ISP) được cấp một khối địa chỉ IPv4 để phân phối cho các tổ chức khi họ thiết lập mạng máy tính kết nối Internet Khối địa chỉ này có phần cố định, ví dụ như 216.245.85, cho phép tổ chức sử dụng các địa chỉ từ 216.245.85.0 đến 216.245.85.255, trong đó 216.245.85.0 và 216.245.85.255 không được dùng cho các máy tính Phần cố định 24 bit đầu tiên được ký hiệu là /24 Nếu khối địa chỉ có phần cố định là /23, sẽ có 9 bit còn lại để gán địa chỉ cho máy tính, cho phép tối đa 510 địa chỉ Ngược lại, với phần cố định là /30, chỉ còn 2 bit để gán địa chỉ, dẫn đến việc chỉ có tối đa 2 địa chỉ có thể được sử dụng cho máy tính trong mạng.

Do sự khan hiếm địa chỉ IPv4, hầu hết các mạng hiện nay sử dụng giao thức dịch địa chỉ mạng (NAT) Trong các mạng dựa trên NAT, hầu hết các nút chỉ có địa chỉ cục bộ không thể định tuyến, được chọn từ các khối địa chỉ 10.x.x.x, 172.16.x.x đến 172.31.x.x và 192.168.x.x Các router kết nối mạng cục bộ với ISP sẽ thực hiện việc dịch các địa chỉ cục bộ này thành một tập hợp nhỏ hơn các địa chỉ có thể định tuyến được.

Ngày nay, an toàn và an ninh mạng đang trở thành mối quan tâm lớn, vì vậy việc thiết lập một điểm truy nhập vào mạng cục bộ để kiểm tra tất cả luồng dữ liệu ra vào là cần thiết Thiết bị phần cứng và phần mềm giữa Internet và mạng cục bộ, được gọi là tường lửa (firewall), có nhiệm vụ kiểm tra và quyết định xem các gói tin có được phép đi qua hay không dựa trên các quy định đã được thiết lập Tường lửa thường được tích hợp trong router nhưng cũng có thể là máy tính chuyên dụng Nhiều hệ điều hành hiện đại như macOS và Red Hat Linux cũng tích hợp các tường lửa cá nhân để giám sát luồng dữ liệu đến máy tính.

Việc lọc gói tin chủ yếu dựa vào địa chỉ mạng và cổng Chẳng hạn, firewall có thể chặn tất cả gói tin từ lớp địa chỉ 192.28.25.x nếu một máy tính sử dụng địa chỉ trong lớp này đã từng tấn công mạng trước đó.

Máy chủ proxy đóng vai trò quan trọng trong việc kết nối mạng, đặc biệt khi kết hợp với tường lửa (firewall) Khi firewall ngăn cản máy tính trong mạng kết nối trực tiếp đến mạng bên ngoài, máy chủ proxy hoạt động như một trung gian, cho phép máy tính trong mạng yêu cầu tải trang từ máy chủ web bên ngoài thông qua nó Máy chủ proxy sau đó sẽ yêu cầu trang từ máy chủ web và chuyển nó đến máy tính đã yêu cầu Ngoài ra, proxy cũng có thể được sử dụng cho các dịch vụ FTP và các kết nối khác Việc sử dụng máy chủ proxy mang lại lợi thế về an toàn mạng, vì các máy tính bên ngoài mạng chỉ có thể tìm thấy máy chủ proxy mà không thể xác định được tên và địa chỉ IP của các máy bên trong mạng, từ đó làm giảm nguy cơ tấn công từ hacker.

Firewall hoạt động chủ yếu ở lớp giao vận và lớp Internet, trong khi máy chủ proxy hoạt động ở lớp ứng dụng Máy chủ proxy hiểu một số giao thức ứng dụng như HTTP và FTP, trong khi máy chủ proxy SOCKS hoạt động ở lớp giao vận, cho phép ủy quyền cho tất cả các kết nối TCP và UDP mà không cần quan tâm đến giao thức ứng dụng Các gói tin có thể được kiểm tra bởi máy chủ proxy để đảm bảo tính hợp lệ của dữ liệu, ví dụ như gói tin FTP chứa dữ liệu Telnet có thể bị loại bỏ.

Sơ đồ sau trình bày cách kết nối các lớp thông qua máy chủ proxy

Hình 1.4: Kết nối các lớp thông qua máy chủ proxy

Máy chủ proxy giúp kiểm soát và chuyển tiếp truy cập Internet, cho phép các công ty ngăn chặn truy cập đến các trang web độc hại trong khi vẫn cho phép truy cập đến các trang web an toàn Nhiều công ty sử dụng máy chủ proxy để cho phép nhân viên sử dụng giao thức FTP để nhận file nhưng hạn chế việc truyền file ra ngoài, bảo vệ dữ liệu bí mật khỏi bị rò rỉ Bên cạnh đó, máy chủ proxy cũng cho phép theo dõi hoạt động truy cập Internet của nhân viên, giúp công ty xác định ai sử dụng Internet cho công việc và ai sử dụng cho mục đích cá nhân.

Máy chủ proxy có thể được sử dụng để thiết lập các vùng nhớ cache cục bộ, giúp tối ưu hóa quy trình truy cập dữ liệu Khi người dùng yêu cầu một file từ máy chủ web, máy chủ proxy sẽ kiểm tra bộ nhớ cache trước Nếu file đã có sẵn, nó sẽ được gửi ngay cho người dùng mà không cần tìm kiếm trên Internet Ngược lại, nếu file không có trong bộ nhớ cache, máy chủ proxy sẽ tìm kiếm trên Internet, gửi file đến người dùng và lưu trữ nó vào bộ nhớ cache cho các yêu cầu sau Cơ chế này không chỉ giảm đáng kể lưu lượng truy cập Internet mà còn cải thiện thời gian phản hồi Một ví dụ điển hình về việc sử dụng máy chủ proxy như bộ nhớ cache là America Online (AOL).

Máy chủ proxy có nhiều ưu điểm nhưng cũng gặp phải một số hạn chế đáng kể Một trong những hạn chế lớn nhất là khả năng không hỗ trợ các giao thức mới, chỉ cho phép các giao thức phổ biến như HTTP, FTP và SMTP hoạt động Điều này gây khó khăn trong việc truyền tải các giao thức mới như BitTorrent, đặc biệt trong bối cảnh Internet đang thay đổi nhanh chóng Trong lập trình Java, việc tạo ra các giao thức mới để tối ưu hóa công việc rất dễ dàng, nhưng những hạn chế của máy chủ proxy làm giảm hiệu quả của các giao thức do người dùng tạo ra, vì máy chủ proxy không thể hiểu và xử lý chúng.

Mô hình khách/chủ (Client/Server) là nền tảng của hầu hết các lập trình mạng hiện đại, trong đó ứng dụng Client/Server lưu trữ lượng lớn dữ liệu trên các máy chủ mạnh mẽ và đắt tiền, trong khi phần mềm bên phía Client chạy trên máy tính cá nhân giá rẻ Thông thường, Server chủ yếu gửi dữ liệu trong khi Client nhận dữ liệu, mặc dù ít chương trình chỉ thực hiện một trong hai chức năng này Thường thì, Client khởi động cuộc hội thoại và Server chờ để đáp ứng, và trong một số trường hợp, cùng một chương trình có thể hoạt động như cả Client lẫn Server.

Không phải tất cả ứng dụng đều phù hợp với mô hình Client/Server; bên cạnh đó, còn có các mô hình kết nối khác như kết nối ngang hàng (peer to peer) Hệ thống điện thoại là ví dụ điển hình của mạng kết nối ngang hàng, nơi mỗi điện thoại có thể gọi và nhận cuộc gọi từ các điện thoại khác Thêm vào đó, mô hình lai (hybrid) giữa Client/Server và Peer-to-Peer cũng được sử dụng phổ biến trong các ứng dụng mạng.

Hình 1.5: Kết nối Client/Server

Java không hỗ trợ kết nối ngang hàng trong API một cách mặc định, nhưng các ứng dụng có thể dễ dàng thiết lập kết nối này Phương pháp phổ biến nhất là cho phép các ứng dụng hoạt động như cả Server và Client Một lựa chọn khác là sử dụng một chương trình Server trung gian để các máy ngang hàng có thể trao đổi dữ liệu với nhau.

CÂU HỎI, BÀI TẬP VẬN DỤNG:

1 Hãy trình bày hiểu biết cơ bản của anh/chị về mạng máy tính?

2 Mô hình TCP/IP có mấy lớp? Anh/chị hãy trình bày hiểu biết của mình về các lớp này?

3 Trình bày khái quát về các giao thức IP, TCP và UDP?

4 Hãy trình bày các khái niệm về địa chỉ IP, tên miền và cổng trong mạng máy tính?

5 Hãy phân biệt tường lửa (firewall) và máy chủ proxy?

6 Mô hình Client/Server là gì? Ngoài mô hình này các ứng dụng mạng còn có thể sử dụng các mô hình nào?

CÁC DÒNG VÀO-RA (STREAM)

Các dòng ra (output stream)

Lớp output cơ bản của Java là java.io.OutputStream , với khai báo như sau: public abstract class OutputStream{

Lớp này cung cấp các phương thức cơ bản để ghi dữ liệu, đó là:

• public abstract void write(int b) throws IOException

• public void write(byte[] data) throws IOException

• public void write(byte[] data, int offset, int length) throws IOException

• public void flush() throws IOException

• public void close() throws IOException

Các lớp con của OutputStream sử dụng các phương thức để ghi dữ liệu vào các phương tiện khác nhau Chẳng hạn, FileOutputStream ghi dữ liệu vào tệp, TelnetOutputStream ghi vào kết nối mạng, và ByteArrayOutputStream ghi dữ liệu vào một mảng byte có thể mở rộng.

Phương thức cơ bản của OutputStream là write(int b), cho phép ghi byte vào output stream bằng cách sử dụng một số nguyên từ 0 đến 255 Phương thức này được coi là trừu tượng (abstract), yêu cầu các lớp con phải tùy chỉnh để phù hợp với phương tiện cụ thể.

ByteArrayOutputStream cho phép sao chép byte vào mảng trong Java thông qua các phương thức cài đặt Trong khi đó, FileOutputStream yêu cầu mã riêng để ghi dữ liệu vào file, phù hợp với nền tảng hệ điều hành đang sử dụng.

Phương thức write(int b) trong Java sử dụng một số nguyên làm đối số nhưng thực tế ghi một byte không dấu Vì Java không hỗ trợ kiểu dữ liệu byte không dấu, nên phải sử dụng số nguyên để thực hiện Sự khác biệt giữa byte không dấu và byte có dấu nằm ở cách diễn giải, mặc dù cả hai đều được tạo ra từ 8 bit Khi truyền một số nguyên qua kết nối mạng bằng write(int b), chỉ 8 bit cuối cùng được gửi đi Nếu số nguyên nằm ngoài phạm vi 0 - 255, chỉ byte ít quan trọng nhất sẽ được ghi, trong khi 3 byte còn lại sẽ bị bỏ qua, thể hiện rõ hiệu ứng chuyển đổi từ số nguyên sang byte.

The character-generator protocol defines a server that transmits text encoded in ASCII One variant of this protocol sends lines, each containing 72 printable ASCII characters, with character codes ranging from 33 to 126, excluding whitespace and control characters.

• Dòng đầu chứa các ký tự từ 33 đến 104

• Dòng thứ hai chứa các ký tự từ 34 đến 105

• Dòng thứ ba chứa các ký tự từ 35 đến 106

Dòng thứ 29 bao gồm các ký tự từ 55 đến 126, trong khi dòng 30 sẽ chứa các ký tự từ 56 đến 126 và tiếp theo là ký tự 33 Các ký tự được sắp xếp theo cách xoay vòng để đảm bảo tính liên tục trong chuỗi ký tự.

Lines are terminated with an Enter key (ASCII 13) and a line feed (ASCII 10) The following code demonstrates how to implement the write() method: public static void generateCharacters(OutputStream out) throws.

IOException { int firstPrintableCharacter = 33; int numberOfPrintableCharacters = 94; int numberOfCharactersPerLine = 72; int start = firstPrintableCharacter; while (true) { /* infinite loop */ for (int i = start; i < start + numberOfCharactersPerLine; i++) { out.write(((i - firstPrintableCharacter) % numberOfPrintableCharacters) + firstPrintableCharacter);

} out.write('\r'); // carriage return out.write('\n'); // linefeed start = ((start + 1) - firstPrintableCharacter) % numberOfPrintableCharacters + firstPrintableCharacter;

Kết quả của giao thức bộ sinh ký tự là:

Trong ví dụ này, một OutputStream được truyền vào phương thức generateCharacters() qua đối số out Dữ liệu sẽ được ghi từng byte một lên out, với các byte này được cung cấp dưới dạng các số nguyên trong một chuỗi xoay vòng.

Khi xử lý chuỗi từ 33 đến 126, sau mỗi chuỗi 72 ký tự, hệ thống sẽ ghi dấu Enter (ASCII 13) và mã xuống dòng (ASCII 10) vào output stream Ký tự tiếp theo sẽ được tính toán và vòng lặp sẽ tiếp tục Phương thức out sẽ ném ra IOException, điều này rất quan trọng vì server sinh ký tự chỉ dừng lại khi client kết thúc kết nối, và mã Java sẽ nhận diện kết thúc này như một IOException.

Ghi từng byte một không hiệu quả do overhead lớn từ các TCP segment, thường là 40 byte, dẫn đến việc tải tổng cộng lên đến 41 byte cho mỗi byte dữ liệu gửi đi Để khắc phục điều này, hầu hết các cài đặt TCP/IP sử dụng vùng đệm dữ liệu để tích lũy và gửi dữ liệu khi đạt ngưỡng nhất định Việc sử dụng các phương thức như write(byte[] data) hoặc write(byte[] data, int offset, int length) giúp cải thiện tốc độ so với việc ghi từng phần tử riêng lẻ trong mảng Ví dụ, phương thức generateCharacters() có thể đóng gói một dòng dữ liệu vào một mảng byte và gửi đi, tối ưu hóa quá trình truyền tải dữ liệu.

IOException { int firstPrintableCharacter = 33; int numberOfPrintableCharacters = 94; int numberOfCharactersPerLine = 72; int start = firstPrintableCharacter; byte[] line = new byte[numberOfCharactersPerLine + 2];

// the +2 is for the carriage return and linefeed while (true) { /* infinite loop */ for (int i = start; i < start + numberOfCharactersPerLine; i++) { line[i - start] = (byte) ((i - firstPrintableCharacter) % numberOfPrintableCharacters + firstPrintableCharacter);

} line[72] = (byte) '\r'; // carriage return out.write(line); start = ((start + 1) - firstPrintableCharacter)

Các stream có thể được đưa vào vùng đệm thông qua phần mềm như mã Java hoặc phần cứng mạng Thông thường, quá trình này được thực hiện bằng cách sử dụng BufferedOutputStream.

BufferedWriter hoạt động như một lớp stream để ghi dữ liệu vào một kết nối Để đảm bảo dữ liệu trong vùng đệm được gửi đi, chúng ta sử dụng phương thức flush() Phương thức này yêu cầu dòng gửi tất cả dữ liệu đã được ghi vào vùng đệm, ngay cả khi vùng đệm chưa đầy Trước khi đóng các dòng, cần phải gọi phương thức flush() để đảm bảo không mất dữ liệu trong vùng đệm.

Hình 2.1: Dữ liệu có thể bị mất nếu không flush các luồng

Khi hoàn tất công việc với một stream, bạn có thể đóng nó bằng phương thức close(), giúp giải phóng tất cả tài nguyên liên quan như file handle và cổng Nếu stream được tạo ra từ một kết nối mạng, việc đóng stream sẽ chấm dứt kết nối đó Lưu ý rằng khi một output stream đã bị đóng, việc ghi thêm dữ liệu vào stream sẽ gây ra lỗi.

Mặc dù IOException có thể xảy ra, một số kiểu stream vẫn cho phép tiếp tục làm việc với đối tượng ngay cả khi stream đã bị đóng Chẳng hạn, một ByteArrayOutputStream đã đóng vẫn có thể được chuyển đổi thành mảng byte thực sự.

DigestOutputStream đã bị đóng vẫn có thể trả về giá trị digest của nó

Việc không đóng một stream trong các chương trình lớn có thể dẫn đến việc tiết lộ thông tin nhạy cảm như đặc tả tập tin, cổng mạng và các tài nguyên khác Vì vậy, trong Java 6 và các phiên bản trước, việc sử dụng khối finally để đóng các dòng là rất quan trọng, vì đây là khối cuối cùng trong chương trình Thông thường, biến stream được khai báo bên ngoài khối try, nhưng nên khởi tạo bên trong khối try để đảm bảo an toàn và hiệu quả.

Các dòng vào (input stream)

Lớp input cơ bản của Java là java.io.InputStream , với khai báo như sau: public abstract class InputStream{

Lớp này cung cấp các phương thức cơ bản để đọc dữ liệu như là các byte chưa được xử lý còn được gọi là byte thô:

• public abstract int read() throws IOException

• public int read(byte[] input) throws IOException

• public int read(byte[] input, int offset, int length) throws IOException

• public long skip(long n) throws IOException

• public int available() throws IOException

• public void close() throws IOException

Specific subclasses of InputStream utilize these methods to read data from particular sources For instance, a FileOutputStream employs these methods to read data from a file, while a TelnetOutputStream uses them to read data from a network connection Additionally, a ByteArrayOutputStream leverages these methods to read data from an array of bytes.

Phương thức chính của InputStream là read() không có đối số, cho phép đọc một byte dữ liệu từ nguồn input stream và trả về giá trị nguyên từ 0 đến 255 Khi kết thúc stream, phương thức sẽ trả về -1 Lưu ý rằng read() sẽ chờ cho đến khi có một byte dữ liệu sẵn sàng để đọc, ngăn không cho thực thi bất kỳ đoạn mã nào sau đó.

Đọc và ghi dữ liệu có thể diễn ra chậm, vì vậy khi chương trình thực hiện các đoạn mã quan trọng, nên tách các thao tác I/O ra thành một luồng riêng biệt để tối ưu hiệu suất.

Phương thức read() được định nghĩa là trừu tượng, yêu cầu các lớp con điều chỉnh để phù hợp với việc quản lý các phương tiện cụ thể.

ByteArrayInputStream cho phép sao chép byte từ mảng bằng mã Java, trong khi TelnetInputStream cần thư viện riêng để đọc dữ liệu từ giao diện mạng Đoạn mã dưới đây minh họa cách đọc 10 byte từ InputStream tên là in và lưu vào mảng byte input Nếu không còn dữ liệu để đọc, vòng lặp sẽ kết thúc sớm: byte[] input = new byte[10]; for (int i = 0; i < input.length; i++) { int b = in.read(); if (b == -1) break; input[i] = (byte) b;

Mặc dù phương thức read() chỉ đọc một byte, nhưng nó trả về một số nguyên Do đó, cần thực hiện chuyển đổi số nguyên thành byte bằng cách cast trước khi lưu trữ kết quả vào mảng byte Giá trị mà read() trả lại có phạm vi từ

128 đến 127 thay vì 0 đến 255 nên ta cần phải chuyển đổi một byte có dấu thành byte không dấu như sau: int i = b >= 0 ? b : 256 + b;

Việc đọc dữ liệu từ InputStream một cách hiệu quả yêu cầu sử dụng các phương thức overloaded như read(byte[] input) và read(byte[] input, int offset, int length) Hai phương thức này cho phép làm đầy một mảng byte từ dòng dữ liệu, với phương thức đầu tiên cố gắng làm đầy toàn bộ mảng input, trong khi phương thức thứ hai chỉ làm đầy một phần mảng từ vị trí offset và với độ dài length Để theo dõi số byte đã được đọc, có thể sử dụng đoạn mã sau: byte[] input = new byte[1024]; int bytesRead = in.read(input); Nếu chỉ có 512 byte để đọc, thì bytesRead sẽ được thiết lập là 512 Để đảm bảo rằng tất cả các byte được đọc, cần đặt thao tác đọc trong một vòng lặp cho đến khi mảng được làm đầy, như trong ví dụ: int bytesRead = 0; int bytesToRead = 1024; byte[] input = new byte[bytesToRead]; while (bytesRead < bytesToRead) { bytesRead += in.read(input, bytesRead, bytesToRead - bytesRead); }.

Tất cả các phương thức read() sẽ trả về giá trị -1 khi kết thúc một stream Nếu stream kết thúc nhưng vẫn còn dữ liệu chưa được đọc, các phương thức đọc nhiều byte sẽ tiếp tục đọc cho đến khi vùng đệm trống Khi đó, việc đọc tiếp sẽ trả lại giá trị -1, và giá trị này sẽ không được ghi vào mảng.

Đoạn mã trên chỉ chứa dữ liệu thực sự, nhưng chưa xem xét trường hợp tất cả 1024 byte có thể chưa được đọc vào vùng đệm Do đó, cần kiểm tra giá trị của hàm read() trước khi cập nhật biến bytesRead Ví dụ, khởi tạo biến bytesRead bằng 0 và bytesToRead là 1024, sau đó tạo mảng byte input với kích thước bytesToRead Trong vòng lặp, kiểm tra xem bytesRead có nhỏ hơn bytesToRead không, nếu có, thực hiện đọc dữ liệu vào mảng input và cập nhật bytesRead cho đến khi kết thúc luồng.

Phương thức available() cho phép xác định số lượng byte có thể đọc ngay lập tức mà không cần chờ đợi Phương thức này trả về số lượng byte tối thiểu có thể đọc, ví dụ: int bytesAvailable = in.available(); byte[] input = new byte[bytesAvailable]; int bytesRead = in.read(input, 0, bytesAvailable);.

// continue with rest of program immediately

Trong một số trường hợp, để bỏ qua việc đọc dữ liệu, ta có thể sử dụng phương thức skip() Sau khi hoàn tất việc đọc dữ liệu từ một stream, cần đóng stream bằng cách gọi phương thức close() của input stream để giải phóng tất cả tài nguyên liên quan, như các đặc tả tập tin và cổng Khi một input stream đã bị đóng, mọi thao tác đọc dữ liệu tiếp theo sẽ không còn khả thi.

IOException có thể xảy ra trong quá trình làm việc với các kiểu stream, nhưng một số stream vẫn cho phép thực hiện một số thao tác nhất định Chẳng hạn, với java.security.DigestInputStream, bạn sẽ không thể nhận được message digest cho đến khi dữ liệu được đọc xong và dòng đã được đóng lại.

Lớp InputStream có ba phương thức ít được sử dụng, cho phép các chương trình thực hiện sao chép dự phòng và đọc lại dữ liệu đã được đọc trước đó.

• public void mark(int readAheadLimit)

• public void reset() throws IOException

Phương thức `markSupported()` trong Java cho phép kiểm tra xem dòng có hỗ trợ đánh dấu vị trí hiện tại hay không Để đọc lại dữ liệu, bạn cần sử dụng phương thức `mark()` để đánh dấu vị trí, và sau đó có thể gọi phương thức `reset()` để quay lại điểm đã đánh dấu Các thao tác đọc tiếp theo sẽ cung cấp dữ liệu bắt đầu từ vị trí đánh dấu đó.

Số lượng byte có thể đọc từ vị trí đánh dấu được xác định bởi đối số readAheadLimit trong phương thức mark() Khi quay lại vượt quá điểm đã đánh dấu, chương trình sẽ phát sinh IOException Mỗi dòng chỉ có một đánh dấu duy nhất; việc đánh dấu lần thứ hai sẽ xóa bỏ đánh dấu trước đó Đánh dấu và thiết lập lại thường thông qua việc lưu trữ các byte đã đọc trong một vùng đệm nội bộ Tuy nhiên, không phải tất cả các dòng đều hỗ trợ tính năng mark và reset Để kiểm tra, ta sử dụng phương thức markSupported() Nếu phương thức này trả về true, dòng hỗ trợ mark và reset; nếu trả về false, mark() sẽ không thực hiện gì và reset() sẽ phát sinh lỗi.

Các dòng filter stream

InputStream và OutputStream là các lớp đơn giản trong Java, chuyên đọc và ghi từng byte hoặc nhóm byte Ý nghĩa của các byte, như số nguyên hay văn bản mã hóa, hoàn toàn phụ thuộc vào lập trình viên Một số định dạng dữ liệu phổ biến đã được cài đặt trong thư viện lớp, chẳng hạn như số nguyên 32 bit big-endian trong giao thức mạng, văn bản mã hóa ASCII 7-bit, Latin-1 8-bit, hay UTF-8 multibyte trên web, và file ZIP trong giao thức FTP Java cũng cung cấp nhiều lớp lọc để chuyển đổi byte thô sang các định dạng khác.

Các bộ lọc được phân thành hai loại: bộ lọc stream reader và writer Bộ lọc stream chủ yếu xử lý dữ liệu thô như byte, bao gồm nén và diễn dịch dữ liệu nhị phân Reader và writer quản lý các trường hợp đặc biệt của văn bản với nhiều mã hóa khác nhau, chẳng hạn như UTF-8 và ISO 8859-1.

Các bộ lọc được sắp xếp thành một chuỗi liên kết, trong đó mỗi liên kết nhận dữ liệu từ bộ lọc trước hoặc từ dòng dữ liệu và chuyển tiếp thông tin đến liên kết tiếp theo trong chuỗi.

Sơ đồ sau trình bày chuỗi các filter

Hình 2.2: Dòng dữ liệu qua một chuỗi các filter

Trong ví dụ này, một file văn bản đã được mã hóa và nén được truyền qua mạng cục bộ Chương trình chuyển file sử dụng TelnetInputStream, trong khi BufferedInputStream giúp tăng tốc độ truyền dữ liệu bằng cách đưa file vào vùng đệm Cuối cùng, CipherInputStream thực hiện giải mã dữ liệu để hoàn tất quá trình.

GZIPInputStream thực hiện việc giải nén dữ liệu, trong khi InputStreamReader chuyển đổi dữ liệu đã giải nén thành file văn bản với mã hóa Unicode Cuối cùng, file văn bản này sẵn sàng để được xử lý trong lớp ứng dụng.

Filter output streams share methods such as write(), close(), and flush() similar to java.io.OutputStream Similarly, filter input streams have methods like read(), close(), and available() akin to java.io.InputStream.

2.3.1 Gắn kết các filter stream

Các bộ lọc được kết nối với nhau thông qua các constructor Đoạn mã dưới đây sẽ nạp dữ liệu vào vùng đệm từ file data.txt Trước tiên, đối tượng fin được khởi tạo.

FileInputStream được tạo bằng cách truyền tên của file như là một đối số cho constructor của FileInputStream Sau đó một đối tượng có tên bin của

BufferedInputStream được tạo bằng cách truyền fin như là một đối số cho constructor của BufferedInputStream :

FileInputStream fin = new FileInputStream("data.txt");

BufferedInputStream bin = new BufferedInputStream(fin);

Chúng ta có thể áp dụng phương thức read() của cả fin và bin để đọc dữ liệu từ file data.txt Trong hầu hết các trường hợp, nên chỉ sử dụng filter cuối cùng trong chuỗi để thực hiện việc đọc và ghi dữ liệu một cách hiệu quả.

2.3.2 Các lớp BufferedInputStream và BufferedOutputStream

Lớp BufferedOutputStream lưu trữ dữ liệu trong một vùng đệm (mảng byte buf) cho đến khi vùng đệm đầy hoặc dòng được đẩy đi, sau đó ghi tất cả dữ liệu vào output stream lớp dưới Việc ghi nhiều byte cùng lúc lên kết nối mạng sẽ nhanh hơn so với ghi từng byte một, do mỗi TCP segment hay gói UDP phải mang thêm khoảng 40 byte overhead.

Lớp BufferedInputStream có một mảng byte bảo vệ tên là buf, hoạt động như một vùng đệm Khi phương thức read() được gọi, nó sẽ ưu tiên đọc dữ liệu từ vùng đệm trước Nếu vùng đệm trống, dòng sẽ tiếp tục đọc dữ liệu từ nguồn lớp dưới, nạp dữ liệu vào vùng đệm cho các lần gọi read() sau Việc sử dụng vùng đệm giúp cải thiện hiệu suất đáng kể BufferedInputStream và BufferedOutputStream đều có hai constructor.

• public BufferedInputStream(InputStream in, int bufferSize)

Lớp BufferedOutputStream trong Java nhận hai đối số: OutputStream out và int bufferSize Đối số đầu tiên là dòng lớp dưới, nơi dữ liệu chưa được đưa vào vùng đệm sẽ được đọc, hoặc dữ liệu đã được đưa vào vùng đệm sẽ được ghi lên dòng này Đối số thứ hai (nếu có) xác định kích thước vùng đệm; nếu không, kích thước mặc định là 2018 byte cho input stream và 512 byte cho output stream Kích thước lý tưởng của vùng đệm phụ thuộc vào loại dòng mà bạn đang sử dụng.

BufferedInputStream không có các phương thức riêng mà chỉ ghi đè (override) lên các phương thức của InputStream BufferedInputStream hỗ trợ mark và reset

BufferedOutputStream không có các phương thức riêng biệt, mà chúng ta có thể sử dụng các phương thức của nó giống như các output stream khác Điểm khác biệt là phương thức write() trong BufferedOutputStream lưu trữ dữ liệu vào một vùng đệm thay vì ghi trực tiếp xuống output stream lớp dưới Do đó, để gửi dữ liệu, chúng ta cần sử dụng phương thức flush() để đẩy dữ liệu ra khỏi vùng đệm khi cần thiết.

Lớp PrintStream là luồng đầu ra lọc đầu tiên mà hầu hết lập trình viên gặp, vì System.out là một PrintStream Nhiều luồng đầu ra khác cũng có thể được kết nối với PrintStream thông qua hai constructor.

• public PrintStream(OutputStream out, boolean autoFlush)

Mặc định, các print stream sẽ gửi dữ liệu ra kết nối; nếu tham số autoFlush được đặt là true, dữ liệu sẽ được tự động gửi mỗi khi một mảng byte, ký hiệu linefeed được viết hoặc phương thức println() được gọi.

PrintStream có 9 phương thức overloaded print() và 10 phương thức overloaded println() :

• public void print(char[] text)

• public void println(char[] text)

Phương thức print() chuyển đổi đối số thành xâu và ghi lên output stream với mã hóa mặc định, trong khi phương thức println() cũng thực hiện chức năng tương tự nhưng thêm ký hiệu ngăn cách dòng vào cuối Ký hiệu ngăn cách dòng trong Unix là \n, trong khi hệ điều hành Mac sử dụng ký hiệu khác.

OS 9 là \r , trong Windows là một cặp ( \r\n )

2.3.4 Các lớp DataInputStream và DataOutputStream

Các lớp Reader and Writer

Lớp Writer tương ứng với lớp java.io.OutputStream Lớp Writer là abstract và có hai protected constructor Cũng giống như OutputStream , lớp

Lớp Writer không được sử dụng trực tiếp, mà phải thông qua các lớp con của nó theo kiểu polymorphic Lớp này bao gồm năm phương thức write() cùng với các phương thức flush() và close().

• public abstract void write(char[] text, int offset, int length) throws IOException

• public void write(int c) throws IOException

• public void write(char[] text) throws IOException

• public void write(String s) throws IOException

• public void write(String s, int offset, int length) throws IOException

• public abstract void flush() throws IOException

• public abstract void close() throws IOException

Phương thức write(char[] text, int offset, int length) là phương thức cơ bản cho việc triển khai bốn phương thức write() khác Một lớp con cần phải ít nhất ghi đè phương thức này cùng với các phương thức flush() và close().

OutputStreamWriter là lớp con cụ thể quan trọng nhất của lớp Writer Một

OutputStreamWriter nhận ký tự từ chương trình Java, chuyển đổi chúng thành byte theo mã hóa cụ thể và ghi vào output stream lớp dưới Constructor của OutputStreamWriter xác định output stream và phương thức mã hóa được sử dụng.

• public OutputStreamWriter(OutputStream out, String encoding) throws UnsupportedEncodingException

Ngoài các constructor, OutputStreamWriter cung cấp các phương thức thông thường của lớp Writer và một phương thức để trả về mã hóa của đối tượng.

Lớp Reader tương ứng với lớp java.io.InputStream Lớp Reader là abstract và có hai protected constructor Giống như InputStream và Writer , lớp

Lớp Reader không thể được sử dụng trực tiếp mà chỉ có thể sử dụng thông qua các lớp con của nó Lớp này có ba phương thức read() cùng với các phương thức skip(), close(), ready(), mark(), reset() và markSupported().

• public abstract int read(char[] text, int offset, int length) throws IOException

• public int read() throws IOException

• public int read(char[] text) throws IOException

• public long skip(long n) throws IOException

• public void mark(int readAheadLimit) throws IOException

• public void reset() throws IOException

Phương thức read(char[] text, int offset, int length) là phương thức cơ bản để cài đặt hai phương thức read() khác Mỗi lớp con cần phải ít nhất ghi đè phương thức này và cũng phải ghi đè phương thức close().

InputStreamReader là lớp con cụ thể quan trọng nhất của Reader Một

InputStreamReader đọc các byte từ một input stream lớp dưới, chẳng hạn như một

FileInputStream hoặc TelnetInputStream sẽ chuyển đổi các byte thành ký tự dựa trên một phương pháp mã hóa cụ thể và trả về các ký tự này Constructor của chúng thực hiện quá trình này một cách hiệu quả.

InputStreamReader xác định input stream để đọc và cách mã hóa được sử dụng:

• public InputStreamReader(InputStream in, String encoding) throws UnsupportedEncodingException

2.4.4 Các lớp Filter Reader và Filter Writer

The InputStreamReader and OutputStreamWriter classes operate on top of input and output streams, transforming a byte-oriented interface into a character-oriented interface After this transformation, additional character-oriented filters are applied on top of the reader and writer using the java.io.FilterReader and java.io.FilterWriter classes Similar to filter streams, there are several subclasses that perform specific filtering operations.

Các lớp BufferedReader và BufferedWriter là các lớp dựa trên ký tự (character-based) tương đương với các lớp hướng byte (byte-oriented)

BufferedInputStream và BufferedOutputStream là hai lớp quan trọng trong Java để xử lý dữ liệu Khi sử dụng BufferedReader, dữ liệu được đọc từ vùng đệm thay vì từ input stream trực tiếp, giúp tối ưu hóa hiệu suất Vùng đệm sẽ được làm đầy với số lượng ký tự tối đa khi cần thiết, cho phép tái sử dụng dữ liệu cho các lần đọc sau Tương tự, khi ghi dữ liệu qua BufferedWriter, văn bản sẽ được lưu vào vùng đệm và chỉ được chuyển đến output stream khi vùng đệm đầy hoặc được flush, giúp tăng tốc độ ghi dữ liệu.

BufferedReader và BufferedWriter là hai lớp quan trọng trong Java, cung cấp các phương thức như read(), ready(), write() và close() Mỗi lớp có hai constructor, cho phép kết nối với các lớp reader hoặc writer khác và thiết lập kích thước vùng đệm Nếu không chỉ định kích thước, vùng đệm sẽ mặc định là 8192 ký tự.

• public BufferedReader(Reader in, int bufferSize)

• public BufferedWriter(Writer out, int bufferSize)

Lớp BufferedReader cũng có một phương thức readLine() để đọc một dòng văn bản và trả về như một dòng:

• public String readLine() throws IOException

Phương thức này thay thế cho phương thức đã bị phản đối readLine() trong

DataInputStream Sự khác biệt giữa hai phương thức này khi gắn một

BufferedReader vào một InputStreamReader ta có thể đọc chính xác các dòng trong một tập hợp các ký tự thay vì cách mã hóa mặc định của hệ thống

Lớp BufferedWriter bổ sung một phương thức mới không có trong lớp cha (superclass), được gọi là newLine() , phương thức này hướng đến việc ghi các dòng:

• public void newLine() throws IOException

Lớp Scanner trong Java cho phép người dùng quét các dòng dữ liệu từ nhiều nguồn như bàn phím, socket, hoặc xâu kí tự để lấy các giá trị cần thiết như xâu kí tự, số nguyên và số thực Một số phương thức phổ biến của lớp Scanner bao gồm nextLine(), nextInt(), và nextDouble(), giúp dễ dàng xử lý dữ liệu đầu vào trong các ứng dụng Java.

The Java Scanner class provides various methods for input retrieval, including `next()` which returns a string up to the next whitespace, and `nextLine()` which captures an entire line of text Additionally, it offers methods to retrieve different data types: `nextByte()` for byte, `nextShort()` for short, `nextInt()` for integer, `nextLong()` for long, `nextFloat()` for float, and `nextDouble()` for double To utilize these methods, the Scanner class must be imported from `java.util`.

Scanner sc = new Scanner(System.in);

System.out.print("Vui lòng nhập một: "); int so = sc.nextInt();

System.out.print("Số bạn vừa nhập: " + so);

Lớp PrintWriter đã thay thế lớp PrintStream trong Java 1.0, cung cấp khả năng kiểm soát tốt hơn đối với các ký tự mã hóa nhiều byte và hỗ trợ cho văn bản quốc tế.

Ngoài các constructor, lớp PrintWriter cũng có một tập hợp các phương thức tương tự như lớp PrintStream , đó là:

• public PrintWriter(Writer out, boolean autoFlush)

• public PrintWriter(OutputStream out, boolean autoFlush)

• public void write(char[] text, int offset, int length)

• public void write(char[] text)

• public void write(String s, int offset, int length)

• public void print(char[] text)

Hầu hết các phương thức trên hoạt động tương tự như các phương thức trong

PrintStream ngoại trừ bốn phương thức write() Bốn phương thức write() sẽ không ghi các byte mà ghi các ký tự

Trong lập trình mạng hiện nay, lớp Scanner thường được sử dụng để đọc dữ liệu đầu vào, trong khi lớp PrintWriter được dùng để ghi dữ liệu đầu ra Cả hai lớp này hỗ trợ đọc và ghi nhiều kiểu dữ liệu khác nhau thông qua các phương thức linh hoạt và hiệu quả.

CÂU HỎI, BÀI TẬP VẬN DỤNG:

1 Các lớp InputStream và OutputStream trong Java được khai báo như thế nào? Liệt kê các phương thức cơ bản của 2 lớp này?

2 Trình bày hiểu biết của anh (chị) về các lớp BufferedInputStream, BufferedOutputStream?

3 Trình bày cấu tạo của lớp PrintStream?

4 Các lớp DataInputStream và DataOutputStream có những đặc điểm gì cần chú ý?

5 Trình bày cấu tạo của lớp Reader, Writer và các lớp dẫn xuất của nó?

6 Lớp Scanner và PrintWriter dùng để làm gì? Nêu cấu tạo của chúng? Tại sao hai lớp này được dùng nhiều trong các ứng dụng mạng?

LẬP TRÌNH ĐA LUỒNG TRONG JAVA

LỚP INETADDRESS

LẬP TRÌNH VỚI GIAO THỨC TCP

LẬP TRÌNH VỚI GIAO THỨC UDP

KỸ THUẬT LẬP TRÌNH PHÂN TÁN RMI

Ngày đăng: 14/12/2021, 10:43

HÌNH ẢNH LIÊN QUAN

Sơ đồ trong Hình 1.1 trình bày một mô hình phân tầng của các giao thức có thể - Giáo trình Lập trình mạng: Phần 1 - Trường ĐH Tây Bắc
Sơ đồ trong Hình 1.1 trình bày một mô hình phân tầng của các giao thức có thể (Trang 13)
Hình 1.3: Cấu trúc của một IPv4 datagram - Giáo trình Lập trình mạng: Phần 1 - Trường ĐH Tây Bắc
Hình 1.3 Cấu trúc của một IPv4 datagram (Trang 15)
Sơ đồ sau trình bày cách kết nối các lớp thông qua máy chủ proxy. - Giáo trình Lập trình mạng: Phần 1 - Trường ĐH Tây Bắc
Sơ đồ sau trình bày cách kết nối các lớp thông qua máy chủ proxy (Trang 22)
Hình 1.5: Kết nối Client/Server - Giáo trình Lập trình mạng: Phần 1 - Trường ĐH Tây Bắc
Hình 1.5 Kết nối Client/Server (Trang 24)
Hình 2.1: Dữ liệu có thể bị mất nếu không flush các luồng - Giáo trình Lập trình mạng: Phần 1 - Trường ĐH Tây Bắc
Hình 2.1 Dữ liệu có thể bị mất nếu không flush các luồng (Trang 28)
Hình 2.2: Dòng dữ liệu qua một chuỗi các filter - Giáo trình Lập trình mạng: Phần 1 - Trường ĐH Tây Bắc
Hình 2.2 Dòng dữ liệu qua một chuỗi các filter (Trang 34)
Hình 3.1: Các trạng thái của luồng - Giáo trình Lập trình mạng: Phần 1 - Trường ĐH Tây Bắc
Hình 3.1 Các trạng thái của luồng (Trang 47)
Hình 3.2: Tạo luồng bằng cách extends từ lớp Thread - Giáo trình Lập trình mạng: Phần 1 - Trường ĐH Tây Bắc
Hình 3.2 Tạo luồng bằng cách extends từ lớp Thread (Trang 51)
Hình 3.3: Tạo luồng bằng cách implements từ giao diện Runnable - Giáo trình Lập trình mạng: Phần 1 - Trường ĐH Tây Bắc
Hình 3.3 Tạo luồng bằng cách implements từ giao diện Runnable (Trang 53)

TỪ KHÓA LIÊN QUAN

w