Bạn có thể giải thích khái niệm về dòng?


186

Tôi hiểu rằng một luồng là một đại diện của một chuỗi byte. Mỗi luồng cung cấp phương tiện để đọc và ghi byte vào cửa hàng sao lưu đã cho. Nhưng điểm của luồng là gì? Tại sao không phải là cửa hàng sao lưu chính những gì chúng ta tương tác?

Vì lý do nào đó khái niệm này chỉ không nhấp cho tôi. Tôi đã đọc một loạt các bài báo, nhưng tôi nghĩ rằng tôi cần một sự tương tự hoặc một cái gì đó.

Câu trả lời:


234

Từ "stream" đã được chọn vì nó đại diện cho (trong cuộc sống thực) một ý nghĩa rất giống với những gì chúng ta muốn truyền tải khi chúng ta sử dụng nó.

Hãy quên đi cửa hàng ủng hộ một chút và bắt đầu suy nghĩ về sự tương tự với dòng nước. Bạn nhận được một luồng dữ liệu liên tục, giống như nước liên tục chảy trong một dòng sông. Bạn không nhất thiết phải biết dữ liệu đến từ đâu và thường thì bạn không cần phải đến; có thể là từ một tập tin, một ổ cắm hoặc bất kỳ nguồn nào khác, nó không (không nên) thực sự quan trọng. Điều này rất giống với việc nhận một dòng nước, theo đó bạn không cần biết nó đến từ đâu; có thể là từ hồ, đài phun nước hoặc bất kỳ nguồn nào khác, điều đó không (không nên) thực sự quan trọng.

Điều đó nói rằng, một khi bạn bắt đầu nghĩ rằng bạn chỉ quan tâm đến việc lấy dữ liệu bạn cần, bất kể nó đến từ đâu, sự trừu tượng mà người khác nói đến trở nên rõ ràng hơn. Bạn bắt đầu nghĩ rằng bạn có thể bao bọc các luồng và phương thức của bạn sẽ vẫn hoạt động hoàn hảo. Ví dụ: bạn có thể làm điều này:

int ReadInt(StreamReader reader) { return Int32.Parse(reader.ReadLine()); }

// in another method:
Stream fileStream = new FileStream("My Data.dat");
Stream zipStream = new ZipDecompressorStream(fileStream);
Stream decryptedStream = new DecryptionStream(zipStream);
StreamReader reader = new StreamReader(decryptedStream);

int x = ReadInt(reader);

Như bạn thấy, việc thay đổi nguồn đầu vào của bạn trở nên rất dễ dàng mà không thay đổi logic xử lý của bạn. Ví dụ: để đọc dữ liệu của bạn từ ổ cắm mạng thay vì tệp:

Stream stream = new NetworkStream(mySocket);
StreamReader reader = new StreamReader(stream);
int x = ReadInt(reader);

Dễ dàng như nó có thể được. Và vẻ đẹp vẫn tiếp tục, khi bạn có thể sử dụng bất kỳ loại nguồn đầu vào nào, miễn là bạn có thể xây dựng một "trình bao bọc" cho luồng đó. Bạn thậm chí có thể làm điều này:

public class RandomNumbersStreamReader : StreamReader {
    private Random random = new Random();

    public String ReadLine() { return random.Next().ToString(); }
}

// and to call it:
int x = ReadInt(new RandomNumbersStreamReader());

Xem? Miễn là phương pháp của bạn không quan tâm nguồn đầu vào là gì, bạn có thể tùy chỉnh nguồn của mình theo nhiều cách khác nhau. Sự trừu tượng hóa cho phép bạn tách rời đầu vào khỏi việc xử lý logic theo một cách rất thanh lịch.

Lưu ý rằng luồng chúng tôi tự tạo không có cửa hàng sao lưu, nhưng nó vẫn phục vụ mục đích của chúng tôi một cách hoàn hảo.

Vì vậy, để tóm tắt, một luồng chỉ là một nguồn đầu vào, ẩn đi (trừu tượng hóa) một nguồn khác. Miễn là bạn không phá vỡ sự trừu tượng, mã của bạn sẽ rất linh hoạt.


6
Suy nghĩ trừu tượng (và giải thích) dường như có trong máu của bạn;) Sự tương đồng của bạn với nước (và do đó là các tham chiếu ẩn dụ) khiến tôi nhớ đến Omar Khayyam.
java.is.for.desktop

@HosamAly Giải thích của bạn rất rõ ràng nhưng có gì đó làm tôi bối rối một chút trong mã mẫu. Việc chuyển đổi rõ ràng từ chuỗi sang int được thực hiện tự động khi thực hiện ReadInt? tôi tin rằng tôi cũng có thể làm ReadString?
Rushino

1
@Rushino Không có chuyển đổi trong mã ở trên. Phương thức ReadIntđược xác định ở đầu sử dụng int.Parse, nhận chuỗi được trả về reader.ReadLine()và phân tích cú pháp. Tất nhiên bạn có thể tạo ra một ReadStringphương pháp tương tự . Điều này đã đủ rõ ràng chưa?
Hosam Aly

Vâng đặt. Streams với tôi là trừu tượng chung đơn giản và mạnh mẽ nhất trong toàn bộ chương trình. Có .net cơ bản Stream.Copylàm cho cuộc sống dễ dàng hơn rất nhiều trong rất nhiều ứng dụng.
Felype

38

Vấn đề là bạn không cần phải biết cửa hàng sao lưu là gì - đó là một sự trừu tượng đối với nó. Thật vậy, thậm chí có thể không một cửa hàng sao lưu - bạn có thể đọc từ mạng và dữ liệu không bao giờ được "lưu trữ".

Nếu bạn có thể viết mã hoạt động cho dù bạn đang nói chuyện với một hệ thống tệp, bộ nhớ, mạng hoặc bất cứ thứ gì hỗ trợ ý tưởng truyền phát, mã của bạn sẽ linh hoạt hơn rất nhiều.

Ngoài ra, các luồng thường được kết nối với nhau - bạn có thể có một luồng nén bất cứ thứ gì được đưa vào đó, viết biểu mẫu nén vào luồng khác hoặc mã hóa dữ liệu, v.v ... Ở đầu kia, ngược lại chuỗi, giải mã, giải nén hoặc bất cứ điều gì.


Đừng để các loại trình đọc luồng khác nhau được sử dụng trong ví dụ @HosamAly ở trên ngụ ý rằng bạn có biết cửa hàng sao lưu là gì không? Tôi lấy nó FileStream, NetworkStream, v.v ... đang đọc từ những loại nguồn đó. Ngoài ra, có những trường hợp bạn không biết cửa hàng sao lưu có thể là gì và điều đó sẽ được chọn động trong khi chương trình chạy? Tôi chỉ không cá nhân đi qua điều này và muốn biết thêm.
dùng137717

Ngoài ra, có thể truyền dữ liệu đường ống qua một số quy trình khi dữ liệu được tạo hoặc tôi có cần truy cập vào bộ dữ liệu đầy đủ mà tôi muốn thao tác khi bắt đầu quá trình không?
dùng137717

@ user137717: Không, nếu bạn chỉ thực hiện StreamReader- hoặc tốt hơn, TextReaderthì mã của bạn không biết loại luồng nào làm cơ sở cho luồng dữ liệu. Hay đúng hơn, nó có thể sử dụng thuộc BaseStreamtính để tìm ra loại - nhưng nó có thể là loại mà mã của bạn chưa từng thấy trước đây. Vấn đề là bạn không nên quan tâm. Và vâng, bạn hoàn toàn có thể kết thúc việc viết mã đôi khi sẽ được sử dụng cho luồng mạng và đôi khi được sử dụng cho luồng tệp. Đối với luồng dữ liệu đường ống thông qua một quy trình - cũng sẽ không được thực hiện trong quy trình ... đó sẽ là nhà cung cấp luồng.
Jon Skeet

30

Điểm của luồng là cung cấp một lớp trừu tượng giữa bạn và cửa hàng sao lưu. Do đó, một khối mã đã cho sử dụng luồng không cần quan tâm nếu kho lưu trữ sao lưu là tệp đĩa, bộ nhớ, v.v ...


Vâng, nó cho phép bạn trao đổi loại luồng mà không phá vỡ mã của bạn. Ví dụ: bạn có thể đọc từ một tệp trong một cuộc gọi và sau đó là bộ nhớ đệm ở lần tiếp theo.
Craig

Tôi sẽ nói thêm rằng lý do bạn muốn làm điều này là vì bạn thường không cần khả năng tìm kiếm tệp khi đọc hoặc viết tệp, và do đó, nếu bạn sử dụng một luồng mà cùng một mã có thể dễ dàng được sử dụng để đọc hoặc ghi vào một ổ cắm mạng, ví dụ.
alxp

11

Đó không phải là về dòng suối - đó là về bơi lội. Nếu bạn có thể bơi một Stream, hơn bạn có thể bơi bất kỳ Stream nào bạn gặp.


7

Để thêm vào buồng phản hồi, luồng là một sự trừu tượng để bạn không quan tâm đến cửa hàng bên dưới. Nó có ý nghĩa nhất khi bạn xem xét các kịch bản có và không có luồng.

Phần lớn các tệp không thú vị vì các luồng không thực hiện nhiều ở trên và vượt ra ngoài các phương pháp không dựa trên luồng mà tôi quen thuộc đã làm. Hãy bắt đầu với các tập tin internet.

Nếu tôi muốn tải xuống một tệp từ internet, tôi phải mở một ổ cắm TCP, tạo kết nối và nhận byte cho đến khi không còn byte nữa. Tôi phải quản lý bộ đệm, biết kích thước của tệp dự kiến ​​và viết mã để phát hiện khi kết nối bị ngắt và xử lý việc này một cách thích hợp.

Giả sử tôi có một số loại đối tượng TcpDataStream. Tôi tạo nó với thông tin kết nối phù hợp, sau đó đọc byte từ luồng cho đến khi nó nói không còn byte nào nữa. Luồng xử lý việc quản lý bộ đệm, điều kiện cuối dữ liệu và quản lý kết nối.

Theo cách này, các luồng làm cho I / O dễ dàng hơn. Bạn chắc chắn có thể viết một lớp TcpFileDoader thực hiện những gì luồng đó làm, nhưng sau đó bạn có một lớp dành riêng cho TCP. Hầu hết các giao diện luồng chỉ đơn giản cung cấp phương thức Read () và Write () và bất kỳ khái niệm phức tạp nào khác đều được xử lý bởi việc triển khai bên trong. Do đó, bạn có thể sử dụng cùng một mã cơ bản để đọc hoặc ghi vào bộ nhớ, tệp đĩa, ổ cắm và nhiều kho dữ liệu khác.


5

Hình dung mà tôi sử dụng là băng tải, không phải trong các nhà máy thực sự bởi vì tôi không biết gì về điều đó, nhưng trong các nhà máy hoạt hình nơi các mặt hàng di chuyển dọc theo đường và được đóng dấu, đóng hộp và đếm và kiểm tra bằng một chuỗi các thiết bị câm.

Bạn có các thành phần đơn giản làm một việc, ví dụ như một thiết bị để đặt một quả anh đào lên bánh. Thiết bị này có một luồng đầu vào của bánh anh đào, và một luồng đầu ra của bánh với anh đào. Có ba lợi thế đáng nói đến khi cấu trúc xử lý của bạn theo cách này.

Đầu tiên, nó đơn giản hóa các thành phần: nếu bạn muốn đặt kem sô cô la lên bánh, bạn không cần một thiết bị phức tạp biết mọi thứ về bánh, bạn có thể tạo ra một thiết bị câm dán sô cô la đóng băng vào bất cứ thứ gì được đưa vào ( phim hoạt hình, điều này đi xa đến mức không biết rằng vật phẩm tiếp theo trong không phải là bánh, đó là Wile E. Coyote).

Thứ hai, bạn có thể tạo ra các sản phẩm khác nhau bằng cách đặt các thiết bị thành các chuỗi khác nhau: có thể bạn muốn bánh của bạn có lớp kem trên quả anh đào thay vì quả anh đào trên lớp kem và bạn có thể làm điều đó chỉ bằng cách hoán đổi các thiết bị trên đường .

Thứ ba, các thiết bị không cần quản lý hàng tồn kho, quyền anh hoặc bỏ hộp. Cách tổng hợp và đóng gói hiệu quả nhất có thể thay đổi: có thể hôm nay bạn đặt bánh của bạn vào hộp 48 và gửi chúng bằng xe tải, nhưng ngày mai bạn muốn gửi hộp sáu để đáp ứng các đơn đặt hàng tùy chỉnh. Loại thay đổi này có thể được cung cấp bằng cách thay thế hoặc cấu hình lại các máy móc ở đầu và cuối dây chuyền sản xuất; Máy cherry ở giữa dòng không cần phải thay đổi để xử lý một số lượng mặt hàng khác nhau cùng một lúc, nó luôn hoạt động với một mặt hàng tại một thời điểm và không cần biết đầu vào hoặc đầu ra của nó như thế nào được nhóm lại.


Ví dụ tuyệt vời của sự tương tự như giải thích.
Richie Thomas

5

Khi tôi nghe về phát trực tuyến lần đầu tiên, đó là trong bối cảnh phát trực tiếp với webcam. Vì vậy, một máy chủ đang phát nội dung video và máy chủ khác đang nhận nội dung video. Vì vậy, là truyền phát này? Chà ... vâng ... nhưng một luồng trực tiếp là một khái niệm cụ thể và tôi nghĩ rằng câu hỏi đề cập đến khái niệm trừu tượng của Truyền phát. Xem https://en.wikipedia.org/wiki/Live_streaming

Vì vậy, hãy tiếp tục.


Video không phải là tài nguyên duy nhất có thể được phát trực tuyến. Âm thanh có thể được truyền phát quá. Vì vậy, bây giờ chúng ta đang nói về phương tiện truyền thông trực tuyến. Xem https://en.wikipedia.org/wiki/Streaming_media . Âm thanh có thể được truyền từ nguồn đến đích theo nhiều cách. Vì vậy, hãy so sánh một số phương pháp phân phối dữ liệu với nhau.

Tải xuống tệp cổ điển Tải xuống tệp cổ điển không xảy ra theo thời gian thực. Trước khi lấy tệp để sử dụng, bạn sẽ phải đợi cho đến khi quá trình tải xuống hoàn tất.

Tải xuống lũy tiến Tải xuống lũy ​​tiến dữ liệu tải xuống từ tệp phương tiện truyền phát trực tuyến vào bộ đệm tạm thời. Dữ liệu trong bộ đệm đó hoàn toàn khả thi: dữ liệu âm thanh-video trong bộ đệm có thể phát được. Do đó, người dùng có thể xem / nghe tệp phương tiện truyền phát trong khi tải xuống. Chuyển tiếp nhanh và tua lại là có thể, ngoại trừ bộ đệm. Dù sao, tải xuống lũy ​​tiến không phải là phát trực tiếp.

Truyền trực tuyến xảy ra thời gian thực và dữ liệu khối. Truyền phát được thực hiện trong các chương trình phát sóng trực tiếp. Khách hàng nghe chương trình phát sóng không thể chuyển tiếp nhanh hoặc tua lại. Trong các luồng video, dữ liệu bị loại bỏ sau khi phát lại.

Máy chủ phát trực tuyến giữ kết nối 2 chiều với máy khách của nó, trong khi Máy chủ Web đóng kết nối sau khi máy chủ phản hồi.


Âm thanh và video không phải là thứ duy nhất có thể phát trực tuyến. Chúng ta hãy xem khái niệm về các luồng trong hướng dẫn PHP.

một luồng là một đối tượng tài nguyên thể hiện hành vi có thể truyền phát. Đó là, nó có thể được đọc từ hoặc được viết theo kiểu tuyến tính và có thể chuyển fseek () đến một vị trí tùy ý trong luồng. Liên kết: https://www.php.net/manual/en/intro.stream.php

Trong PHP, một tài nguyên là một tham chiếu đến một nguồn bên ngoài như một tệp, kết nối cơ sở dữ liệu. Vì vậy, nói cách khác, một luồng là một nguồn có thể được đọc từ hoặc ghi vào. Vì vậy, nếu bạn đã làm việc với fopen(), thì bạn đã làm việc với các luồng.

Một ví dụ về tệp văn bản chịu sự truyền phát:

// Let's say that cheese.txt is a file that contains this content: 
// I like cheese, a lot! My favorite cheese brand is Leerdammer.
$fp = fopen('cheese.txt', 'r');

$str8 = fread($fp, 8); // read first 8 characters from stream. 

fseek($fp, 21); // set position indicator from stream at the 21th position (0 = first position)
$str30 = fread($fp, 30); // read 30 characters from stream

echo $str8; // Output: I like c 
echo $str30; // Output: My favorite cheese brand is L

Tập tin zip có thể được truyền phát quá. Trên hết, phát trực tuyến không giới hạn các tệp. Kết nối HTTP, FTP, SSH và Đầu vào / Đầu ra cũng có thể được truyền phát.


Wikipedia nói gì về khái niệm Truyền phát?

Trong khoa học máy tính, một luồng là một chuỗi các yếu tố dữ liệu được tạo sẵn theo thời gian. Một luồng có thể được coi là các mục trên băng chuyền đang được xử lý cùng một lúc thay vì theo lô lớn.

Xem: https://en.wikipedia.org/wiki/Stream_%28computing%29 .

Wikipedia liên kết đến đây: https://srfi.schemers.org/srfi-41/srfi-41.html và các nhà văn có điều này để nói về các luồng:

Các luồng, đôi khi được gọi là danh sách lười biếng, là một cấu trúc dữ liệu tuần tự chứa các phần tử chỉ được tính toán theo yêu cầu. Một luồng là null hoặc là một cặp với một luồng trong cdr của nó. Vì các phần tử của một luồng chỉ được tính khi truy cập, các luồng có thể là vô hạn.

Vì vậy, một luồng thực sự là một cấu trúc dữ liệu.


Kết luận của tôi: một luồng là một nguồn có thể chứa dữ liệu có thể được đọc từ hoặc ghi vào một cách tuần tự. Một luồng không đọc mọi thứ mà nguồn chứa cùng một lúc, nó đọc / ghi tuần tự.


Liên kết hữu ích:

  1. http://www.sl slideshoware.net/auroraeosrose/writer-and-USE-php-streams-and-sockets-zendcon-2011 Cung cấp một bản trình bày rất rõ ràng
  2. https://www.sk89q.com/2010/04/intributiontion-to-php-streams/
  3. http://www.netlingo.com/word/stream-or-streaming.php
  4. http://www.brainbell.com/tutorials/php/Using_PHP_Streams.htm
  5. http://www.sitepoint.com/php-streaming-output-buffering-explained/
  6. http://php.net/manual/en/wrappers.php
  7. http://www.digidata-lb.com/streaming/Streaming_Proposal.pdf
  8. http://www.webopedia.com/TERM/S/streaming.html
  9. https://en.wikipedia.org/wiki/Stream_%28computing%29
  10. https://srfi.schemers.org/srfi-41/srfi-41.html

4

Đó chỉ là một khái niệm, một mức độ trừu tượng khác giúp cuộc sống của bạn dễ dàng hơn. Và tất cả chúng đều có giao diện chung có nghĩa là bạn có thể kết hợp chúng theo cách giống như ống. Ví dụ, mã hóa vào base64, sau đó zip và sau đó ghi nó vào đĩa và tất cả trong một dòng!


Điều đó hữu ích, chắc chắn, nhưng tôi sẽ không nói đó là "toàn bộ vấn đề". Ngay cả khi không có chuỗi, nó vẫn hữu ích để có một sự trừu tượng chung.
Jon Skeet

Uh, đúng vậy. Tôi đã thay đổi các từ để làm rõ điều này.
vava

Yup, điều đó tốt hơn. Hy vọng bạn không nghĩ rằng tôi đã quá kén chọn!
Jon Skeet

3

Giải thích tốt nhất về các luồng tôi đã thấy là chương 3 của SICP . (Bạn có thể cần đọc 2 chương đầu tiên để có ý nghĩa, nhưng dù sao thì bạn cũng nên. :-)

Họ không sử dụng steram cho byte, mà là số nguyên. Những điểm lớn mà tôi có được từ nó là:

  • Các luồng là danh sách bị trì hoãn
  • Chi phí tính toán [của việc háo hức tính toán mọi thứ trước thời hạn, trong một số trường hợp] là thái quá
  • Chúng ta có thể sử dụng các luồng để biểu diễn các chuỗi dài vô hạn

Tôi thực sự hiện đang ở chương 1 của SICP. Cảm ơn!
Rob Sobers

2
người ta muốn nói luồng SICP từ những người khác. một tính năng quan trọng của luồng SICPsự lười biếng , trong khi khái niệm luồng chung nhấn mạnh đến sự trừu tượng hóa trên các chuỗi dữ liệu .
象 嘉

2

Một điểm khác (Đối với tình huống đọc tệp):

  1. streamcó thể cho phép bạn làm một cái gì đó khác trước finished reading all content of the file.
  2. bạn có thể lưu bộ nhớ vì không cần tải tất cả nội dung tệp cùng một lúc.

1

Hãy nghĩ về các luồng như là một nguồn dữ liệu trừu tượng (byte, ký tự, v.v.). Chúng trừu tượng các cơ chế thực tế của việc đọc và ghi vào nguồn dữ liệu cụ thể, có thể là ổ cắm mạng, tệp trên đĩa hoặc phản hồi từ máy chủ web.


1

Tôi nghĩ rằng bạn cần phải xem xét rằng chính cửa hàng sao lưu thường chỉ là một sự trừu tượng hóa. Luồng bộ nhớ khá dễ hiểu, nhưng một tệp hoàn toàn khác nhau tùy thuộc vào hệ thống tệp bạn đang sử dụng, đừng bận tâm bạn đang sử dụng ổ cứng nào. Trên thực tế, không phải tất cả các luồng đều nằm trên một cửa hàng sao lưu: các luồng mạng gần như chỉ là các luồng.

Điểm của một luồng là chúng tôi hạn chế sự chú ý của chúng tôi vào những gì quan trọng. Bằng cách có một sự trừu tượng tiêu chuẩn, chúng ta có thể thực hiện các hoạt động chung. Ví dụ, ngay cả khi bạn không muốn tìm kiếm tệp hoặc phản hồi HTTP cho URL hôm nay, điều đó không có nghĩa là bạn sẽ không muốn đến ngày mai.

Các luồng ban đầu được hình thành khi bộ nhớ nhỏ so với lưu trữ. Chỉ cần đọc một tệp C có thể là một tải đáng kể. Giảm thiểu dấu chân bộ nhớ là vô cùng quan trọng. Do đó, một sự trừu tượng trong đó rất ít cần thiết để được tải là rất hữu ích. Ngày nay, nó cũng hữu ích không kém khi thực hiện giao tiếp mạng và, hóa ra, hiếm khi hạn chế khi chúng ta xử lý các tệp. Khả năng thêm vào những thứ như bộ đệm một cách minh bạch làm cho nó thậm chí còn hữu ích hơn.


0

Một luồng là một bản tóm tắt của một chuỗi các byte. Ý tưởng là bạn không cần biết các byte đến từ đâu, chỉ là bạn có thể đọc chúng theo cách chuẩn.

Ví dụ: nếu bạn xử lý dữ liệu qua luồng thì mã của bạn không thành vấn đề nếu dữ liệu đến từ tệp, kết nối mạng, chuỗi, blob trong cơ sở dữ liệu, v.v.

Không có gì sai khi tương tác với chính cửa hàng sao lưu ngoại trừ thực tế là nó ràng buộc bạn với việc triển khai cửa hàng sao lưu.


0

Luồng là một sự trừu tượng hóa cung cấp một tập hợp các phương thức và thuộc tính tiêu chuẩn để tương tác với dữ liệu. Bằng cách trừu tượng hóa khỏi phương tiện lưu trữ thực tế, mã của bạn có thể được viết mà không phụ thuộc hoàn toàn vào phương tiện đó là gì hoặc thậm chí là việc thực hiện phương tiện đó.

Một sự tương tự tốt có thể là xem xét một cái túi. Bạn không quan tâm túi được làm từ gì hoặc làm gì khi bạn đặt đồ vào đó, miễn là túi thực hiện công việc là túi và bạn có thể lấy đồ ra. Luồng xác định cho phương tiện lưu trữ những gì khái niệm túi xác định cho các trường hợp khác nhau của túi (như túi rác, túi xách, ba lô, v.v.) - quy tắc tương tác.


0

Tôi sẽ nói ngắn gọn, tôi chỉ thiếu từ ở đây:

Luồng là hàng đợi thường được lưu trữ trong bộ đệm chứa bất kỳ loại dữ liệu nào.

(Bây giờ, vì tất cả chúng ta đều biết hàng đợi là gì, không cần phải giải thích điều này thêm nữa.)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.