Điều gì xảy ra với dữ liệu phụ trợ luồng unix trên đọc một phần?


18

Vì vậy, tôi đã đọc rất nhiều thông tin về dữ liệu phụ trợ unix-stream, nhưng một điều còn thiếu trong tất cả các tài liệu là điều gì sẽ xảy ra khi có một phần đọc?

Giả sử tôi đang nhận các thông báo sau vào bộ đệm 24 byte

msg1 [20 byes]   (no ancillary data)
msg2 [7 bytes]   (2 file descriptors)
msg3 [7 bytes]   (1 file descriptor)
msg4 [10 bytes]  (no ancillary data)
msg5 [7 bytes]   (5 file descriptors)

Cuộc gọi đầu tiên tới recvmsg, tôi nhận được tất cả các tin nhắn1 (và một phần của Trình tin2? Hệ điều hành có bao giờ làm điều đó không?) khi tôi biết tin nhắn thực sự bảo tôi làm gì với dữ liệu? Nếu tôi giải phóng 20 byte từ Trình tin1 và sau đó gọi lại recvmsg, liệu nó có bao giờ phân phối tệp tin 3 và Trình tin 4 không? Liệu các dữ liệu phụ trợ từ dir3 và dir4 có được nối với nhau trong cấu trúc thông điệp điều khiển không?

Mặc dù tôi có thể viết các chương trình thử nghiệm để tìm hiểu thực nghiệm điều này, tôi đang tìm tài liệu về cách dữ liệu phụ trợ hoạt động trong bối cảnh phát trực tuyến. Có vẻ kỳ lạ là tôi không thể tìm thấy bất cứ điều gì chính thức về nó.


Tôi sẽ thêm các kết quả thử nghiệm của mình vào đây, mà tôi đã nhận được từ chương trình thử nghiệm này:

https://github.com/nrdvana/daemonproxy/blob/master/src/ancillary_test.c

Linux 3.2.59, 3.17.6

Dường như Linux sẽ nối các phần của các tin nhắn mang phụ trợ vào cuối các tin nhắn khác miễn là không cần phải tải trọng phụ trợ trước trong cuộc gọi này tới recvmsg. Khi dữ liệu phụ trợ của một tin nhắn được gửi, nó sẽ trả về một lần đọc ngắn thay vì bắt đầu tin nhắn dữ liệu phụ trợ tiếp theo. Vì vậy, trong ví dụ trên, số lần đọc tôi nhận được là:

recv1: [24 bytes] (msg1 + partial msg2 with msg2's 2 file descriptors)
recv2: [10 bytes] (remainder of msg2 + msg3 with msg3's 1 file descriptor)
recv3: [17 bytes] (msg4 + msg5 with msg5's 5 file descriptors)
recv4: [0 bytes]

BSD 4.4, 10.0

BSD cung cấp sự liên kết nhiều hơn Linux và đọc ngắn ngay trước khi bắt đầu một tin nhắn có dữ liệu phụ trợ. Nhưng, nó sẽ vui vẻ nối một tin nhắn không mang phụ trợ vào cuối tin nhắn mang phụ trợ. Vì vậy, đối với BSD, có vẻ như nếu bộ đệm của bạn lớn hơn thông báo mang phụ trợ, bạn sẽ có hành vi gần giống như gói. Các bài đọc tôi nhận được là:

recv1: [20 bytes] (msg1)
recv2: [7 bytes]  (msg2, with msg2's 2 file descriptors)
recv3: [17 bytes] (msg3, and msg4, with msg3's 1 file descriptor)
recv4: [7 bytes]  (msg5 with 5 file descriptors)
recv5: [0 bytes]

LÀM:

Vẫn muốn biết nó xảy ra như thế nào trên Linux, iOS, Solaris, v.v. và làm thế nào nó thể xảy ra trong tương lai.


Đừng nhầm lẫn giữa các luồng và gói, trong một luồng không có gì đảm bảo rằng dữ liệu sẽ được gửi trong cùng một khối mà nó được gửi, vì điều này bạn sẽ cần một giao thức dựa trên gói, không phải dựa trên luồng.
ctrl-alt-delor 15/2/2015

đó chính xác là lý do tại sao tôi hỏi câu hỏi này
M Conrad

Thứ tự cần được bảo tồn. Đó là những gì luồng làm. Nếu một lần đọc chặn trả về 0, thì đó là kết thúc của luồng. Nếu nó trả về một số khác thì có thể có nhiều hơn, bạn phải đọc ít nhất một lần nữa để tìm hiểu. Không có những thứ như message1, message2, v.v. Không có dấu phân cách tin nhắn nào được truyền đi. Bạn phải thêm nó vào giao thức của bạn, nếu bạn cần nó.
ctrl-alt-delor 17/2/2015

1
Cụ thể, tôi có một giao thức luồng văn bản và tôi đang thêm một lệnh truyền một bộ mô tả tệp với một dòng văn bản. Tôi cần biết thứ tự dữ liệu phụ trợ này được nhận theo thứ tự nào liên quan đến văn bản của tin nhắn để viết mã đúng.
M Conrad

1
@MConrad: Tôi sẽ cố gắng để có một bản sao của đặc tả POSIX.1g. Nếu nó không được viết rõ ràng ở đó, thì bạn có thể mong đợi một hành vi cụ thể thực hiện.
Laszlo Valko 24/2/2015

Câu trả lời:


1

Dữ liệu phụ trợ được nhận như thể nó được xếp hàng cùng với octet dữ liệu bình thường đầu tiên trong phân khúc (nếu có).

- POSIX.1-2017

Đối với phần còn lại của câu hỏi của bạn, mọi thứ có một chút lông.

... Đối với mục đích của phần này, một datagram được coi là một phân đoạn dữ liệu chấm dứt một bản ghi và bao gồm một địa chỉ nguồn dưới dạng một loại dữ liệu phụ trợ đặc biệt.

Các phân đoạn dữ liệu được đặt vào hàng đợi khi dữ liệu được phân phối đến ổ cắm bằng giao thức. Các phân đoạn dữ liệu thông thường được đặt ở cuối hàng đợi khi chúng được phân phối. Nếu một phân đoạn mới chứa cùng loại dữ liệu với phân đoạn trước và không bao gồm dữ liệu phụ trợ và nếu phân đoạn trước không chấm dứt một bản ghi, các phân đoạn đó được hợp nhất một cách hợp lý vào một phân đoạn duy nhất ...

Một hoạt động nhận sẽ không bao giờ trả lại dữ liệu hoặc dữ liệu phụ trợ từ nhiều hơn một phân đoạn.

Vì vậy, ổ cắm BSD hiện đại hoàn toàn phù hợp với trích xuất này. Điều này không đáng ngạc nhiên :-).

Hãy nhớ tiêu chuẩn POSIX được viết sau UNIX và sau khi phân tách như BSD so với System V. Một trong những mục tiêu chính là giúp hiểu phạm vi hành vi hiện có và ngăn chặn nhiều sự chia tách hơn trong các tính năng hiện có.

Linux đã được triển khai mà không cần tham chiếu đến mã BSD. Nó xuất hiện để hành xử khác nhau ở đây.

  1. Nếu tôi đọc bạn một cách chính xác, nó có vẻ như Linux được sáp nhập thêm "phân đoạn" khi một phân khúc mới không bao gồm dữ liệu phụ trợ, nhưng phân khúc trước đó thì không.

  2. Quan điểm của bạn rằng "Linux sẽ nối các phần của các tin nhắn mang phụ trợ vào cuối các tin nhắn khác miễn là không có tải trọng phụ trợ nào được gửi trong cuộc gọi này tới recvmsg", dường như không được giải thích hoàn toàn theo tiêu chuẩn. Một lời giải thích có thể sẽ liên quan đến một điều kiện cuộc đua. Nếu bạn đọc một phần của "phân khúc", bạn sẽ nhận được dữ liệu phụ trợ. Có lẽ Linux giải thích điều này có nghĩa là phần còn lại của phân khúc không còn được tính là bao gồm cả dữ liệu phụ trợ! Vì vậy, khi một phân khúc mới được nhận, nó được hợp nhất - theo tiêu chuẩn hoặc theo chênh lệch 1 ở trên.

Nếu bạn muốn viết một chương trình di động tối đa, bạn nên tránh hoàn toàn khu vực này. Khi sử dụng dữ liệu phụ trợ, việc sử dụng ổ cắm datagram phổ biến hơn nhiều . Nếu bạn muốn làm việc trên tất cả các nền tảng lạ mà về mặt kỹ thuật mong muốn cung cấp một cái gì đó chủ yếu như POSIX, câu hỏi của bạn dường như được đặt vào một góc tối và chưa được kiểm tra.


Bạn có thể tranh luận Linux vẫn tuân theo một số nguyên tắc quan trọng:

  1. "Dữ liệu phụ trợ được nhận như thể nó được xếp hàng cùng với octet dữ liệu bình thường đầu tiên trong phân khúc".
  2. Dữ liệu phụ trợ không bao giờ được "nối", như bạn đặt nó.

Tuy nhiên, tôi không tin rằng hành vi Linux đặc biệt hữu ích , khi bạn so sánh nó với hành vi BSD. Có vẻ như chương trình bạn mô tả sẽ cần thêm một cách giải quyết dành riêng cho Linux. Và tôi không biết một lời biện minh cho lý do tại sao Linux sẽ mong bạn làm điều đó.

Nó có thể trông có vẻ hợp lý khi viết mã hạt nhân Linux, nhưng chưa từng được kiểm tra hoặc thực hiện bởi bất kỳ chương trình nào.

Hoặc nó có thể được thực thi bởi một số mã chương trình, phần lớn hoạt động theo tập hợp con này, nhưng về nguyên tắc có thể có "lỗi" trường hợp cạnh hoặc điều kiện chủng tộc.

Nếu bạn không thể hiểu được hành vi của Linux và mục đích sử dụng của nó, tôi nghĩ rằng lập luận cho việc coi đây là một "góc tối, chưa được kiểm tra" trên Linux.


Cảm ơn vì đã xem xét! Tôi nghĩ rằng điều đáng nói ở đây là tôi có thể xử lý việc này một cách an toàn với hai bộ đệm (mỗi bộ đệm có phần dữ liệu và phần phụ trợ); Nếu tôi nhận được mô tả tệp trong lần đọc đầu tiên và chúng không thuộc về tin nhắn, nhưng một tin nhắn khác bắt đầu, thì nếu lần đọc tiếp theo cũng chứa dữ liệu phụ trợ, điều đó có nghĩa là tôi chắc chắn sẽ tìm thấy phần cuối của thông điệp dữ liệu của mình sở hữu tải trọng phụ trợ đầu tiên trong đó đọc thứ hai. Luân phiên qua lại, tôi sẽ luôn có thể khớp thông báo với tải trọng dựa trên vị trí của byte đầu tiên.
M Conrad
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.