I/O là viết tắt của Input/Output (Đầu vào/Đầu ra).
Với input, chúng ta nói về tất cả dữ liệu & thông tin đi vào cái gì đó (máy tính, phương thức Ruby, não bộ bạn).
Ví dụ về input:
- Phím bạn nhấn trên bàn phím
- Click chuột
- Sách bạn đọc
Với output, chúng ta nói về mọi thứ ra ngoài như kết quả của input.
Ví dụ về output:
- Kết quả của 1 + 1
- Viết tóm tắt bài viết bạn đọc
- Cà phê
Trong Ruby, khi nói về I/O, chúng ta thường nói về đọc file, làm việc với network socket & in thông tin ra màn hình.
Hiểu về Class IO
IO cũng là một class trong Ruby.
Nó là class cha cho các object khác như File và Socket.
Tất cả object IO có các operation này:
- open / close
- read
- write
Object IO dựa trên file descriptors (bộ mô tả file).
Mọi tiến trình chạy trong máy tính của bạn có một bảng thông tin nội bộ.
Phần của bảng này bao gồm danh sách file descriptors.
File descriptor là ánh xạ giữa một integer và tên file.
Bạn có thể xem danh sách file descriptors này trong Linux.
Với lệnh này:
# Giả sử bạn mở irb ở cửa sổ khác ls -lh /proc/`pgrep irb`/fd/ # lrwx------ 1 jesus jesus 64 Feb 3 15:54 0 -> /dev/pts/3 # lrwx------ 1 jesus jesus 64 Feb 3 15:54 1 -> /dev/pts/3 # lrwx------ 1 jesus jesus 64 Feb 3 15:54 2 -> /dev/pts/3
Bạn có thể tạo file descriptor mới trong Ruby với phương thức sysopen.
Ví dụ:
fd = IO.sysopen('/dev/null', 'w') # 10
Bây giờ bạn có thể mở kênh giao tiếp với file descriptor này (10).
Như sau:
io = IO.new(fd)
Và ghi vào nó:
io << 'orange'
Chú ý rằng IO không quan tâm file descriptor của bạn ánh xạ đến file thực tế, thiết bị (như ổ cứng), hay network socket.
Cách mở File Descriptor để ghi
Khi bạn tạo file descriptor mới dùng sysopen, hoặc mở file dùng File.open, bạn có một vài tùy chọn.
Mặc định giao tiếp là chỉ đọc.
Bạn có thể chỉ định mode:
r(read, mặc định)w(write)w+(read & write)a(append)
Ví dụ:
fd = IO.sysopen('/dev/tty1', 'w+')
Điều này mở /dev/tty1 với chế độ đọc + ghi.
3 Standard IO Streams (3 luồng IO chuẩn)
Khi bạn mở một chương trình mới (editor, terminal, trình thông dịch Ruby, v.v.), nó mở 3 kênh giao tiếp mặc định.
3 file descriptors.
Chúng là:
- stdin (input)
- stdout (output)
- stderr (errors)
Trong Ruby, bạn có thể truy cập trực tiếp chúng, dùng dạng constant hoặc dạng global variable.
Ví dụ:
$stdout << "abc\n"
Khi bạn ghi vào $stdout, nó giống như gọi phương thức puts.
Standard output liên kết với terminal của bạn.
lrwx------ 1 jesus jesus 64 Feb 3 15:54 0 -> /dev/pts/3
/dev/pts/3 này là thiết bị ảo mà terminal đọc để hiển thị text trên màn hình.
Standard error cũng dùng cho output, nhưng nó cho terminal cơ hội trình bày nội dung này khác biệt, như font to hơn, hoặc màu khác so với text thường.
Bạn có thể dùng phương thức warn để in ra standard error.
Standard input là nơi đọc input từ người dùng.
Bạn có thể dùng phương thức gets để đọc từ standard input.
Cách Flush STDOUT & Tắt Buffering
Object IO có buffer nội bộ.
Một lượng nhất định dữ liệu input sẽ được lưu trước khi có thể đọc.
Đó là lý do đôi khi bạn dùng puts…
Nhưng không thấy gì cho đến khi chương trình kết thúc, hoặc buffer đầy!
Bạn có thể tắt buffering như sau:
$stdout.sync = true
Hoặc bạn có thể flush buffer thủ công:
$stdout.flush
Với buffer tắt, bạn sẽ thấy output mà không phải chờ.
Đọc theo từng phần (Reading In Chunks)
Khi bạn đọc từ object IO, bạn read cho đến khi stream kết thúc bằng tín hiệu END-OF-FILE (EOF), stream bị đóng, hoặc bạn nhận được lượng byte cụ thể.
Đối với lập trình mạng & file lớn, đọc theo từng phần nhỏ (chunks) rất hữu ích.
Bạn có thể đọc theo chunks như sau:
data = "" until io.eof? data << io.read(2048) end
Với code này, bạn có thể xử lý dữ liệu khi nó có sẵn, không phải chờ tải xong toàn bộ.
Chú ý rằng eof? sẽ "block" (chặn).
Blocking nghĩa là code của bạn sẽ đi ngủ cho đến khi hệ điều hành báo hiệu có dữ liệu mới để đọc.
Cách dùng IO Pipes cho giao tiếp hai chiều
Ruby cho phép bạn tạo pipes.
Pipe là một cặp file descriptor được kết nối với nhau.
Điều này dùng cho Inter-Process Communication (IPC - giao tiếp giữa các tiến trình).
Ví dụ:
read, write = IO.pipe
Bạn có thể dùng điều này với phương thức fork (không có sẵn trong Windows) để tạo bản sao của tiến trình hiện tại.
Hai tiến trình này có thể giao tiếp dùng pipe.
Ví dụ:
if fork
write.close
puts "Message received: #{read.gets}"
read.close
else
read.close
write << "Buy some bananas!"
write.close
end
Kết quả:
# Message received: Buy some bananas!
Cách dùng phương thức Kernel Open
Ruby có một phương thức open rất linh hoạt.
Nó cho phép bạn mở file & đọc dữ liệu từ URL, thậm chí mở pipe để chạy lệnh bên ngoài.
Ví dụ:
open "abc.txt"
Đó là lý do phương thức này nên tránh dùng.
Đó là rủi ro bảo mật mà nó có thể làm tất cả những điều đó.
Thay vào đó, tốt hơn là dùng phương thức open cụ thể mà bạn cần.
Ví dụ:
File.open # File IO.popen # Process URI.parse(...).open # URL
Tất cả đều trả về object IO.
Tóm tắt
Bạn đã học về input/output trong Ruby! Bạn đã học về file descriptors, class IO, buffering, đọc theo chunks & standard IO streams.

Đừng quên chia sẻ bài viết này nếu bạn thích nó.
Cảm ơn bạn đã đọc.