Sự khác biệt giữa read () và recv () và giữa send () và write () là gì?


198

Sự khác biệt giữa read()recv(), và giữa send()write()trong lập trình ổ cắm về hiệu suất, tốc độ và các hành vi khác là gì?


3
Hãy nghĩ về việc viết như được thực hiện như thế này : #define write(...) send(##__VA_ARGS__, 0).
cẩn thận1

Câu trả lời:


128

Sự khác biệt là recv()/ send()chỉ hoạt động trên các mô tả ổ cắm và cho phép bạn chỉ định các tùy chọn nhất định cho hoạt động thực tế. Các chức năng này chuyên dụng hơn một chút (ví dụ: bạn có thể đặt cờ để bỏ quaSIGPIPE hoặc gửi tin nhắn ngoài băng ...).

Hàm read()/ write()là các hàm mô tả tệp phổ quát làm việc trên tất cả các mô tả.


3
Điều này là không chính xác, có một sự khác biệt khác trong trường hợp các datagram có độ dài 0 - Nếu một datagram có độ dài bằng 0 đang chờ xử lý, hãy đọc (2) và recv () với đối số cờ là 0 cung cấp hành vi khác nhau. Trong trường hợp này, đọc (2) không có hiệu lực (datagram vẫn đang chờ xử lý), trong khi recv () tiêu thụ datagram đang chờ xử lý.
Abhinav Gauniyal

2
@AbhinavGauniyal Điều đó sẽ cung cấp hành vi khác nhau như thế nào? Nếu có một datagram 0 byte, cả hai recvreadsẽ không cung cấp dữ liệu cho người gọi nhưng cũng không có lỗi. Đối với người gọi, hành vi là như nhau. Người gọi thậm chí có thể không biết gì về datagram (có thể không biết rằng đây là một socket và không phải là một tập tin, nó có thể không biết rằng đây là một socket datagram chứ không phải socket socket). Rằng datagram vẫn đang chờ xử lý là kiến ​​thức ngầm về cách các ngăn xếp IP hoạt động trong các hạt nhân và không hiển thị cho người gọi. Từ quan điểm của người gọi, họ vẫn sẽ cung cấp hành vi bình đẳng.
Mecki

2
@Mecki đó không phải là kiến ​​thức tiềm ẩn cho tất cả mọi người, lấy tôi làm ví dụ :)
Abhinav Gauniyal

1
@Mecki đọc không thành công 0 byte chỉ ra điều gì? Liệu datagram vẫn đang chờ xử lý? Chính xác điều đó, và chỉ có điều đó, làm tôi lo lắng: hành vi mà một datagram có thể chờ xử lý ngay cả khi đọc thành công. Tôi không chắc liệu tình huống có thể phát sinh hay không, điều mà tôi muốn ghi nhớ.
sehe

2
@sehe Nếu bạn lo lắng, tại sao bạn không sử dụng recv? Lý do tại sao recvsendnơi được giới thiệu ở nơi đầu tiên là thực tế là không phải tất cả các khái niệm datagram có thể được ánh xạ tới thế giới của các luồng. readwritecoi mọi thứ là một luồng dữ liệu, cho dù đó là đường ống, tệp, thiết bị (ví dụ: cổng nối tiếp) hoặc ổ cắm. Tuy nhiên, một socket chỉ là một luồng thực nếu nó sử dụng TCP. Nếu nó sử dụng UDP, nó giống như một thiết bị khối. Nhưng nếu cả hai bên sử dụng nó như một luồng, nó sẽ hoạt động như một luồng và bạn thậm chí không thể gửi một gói UDP trống bằng writecác cuộc gọi, vì vậy tình huống này sẽ không xảy ra.
Mecki

85

Mỗi lần truy cập đầu tiên trên Google

read () tương đương với recv () với tham số cờ là 0. Các giá trị khác cho tham số cờ thay đổi hành vi của recv (). Tương tự, write () tương đương với send () với cờ == 0.


31
Đây không phải là toàn bộ câu chuyện. recvchỉ có thể được sử dụng trên một ổ cắm và sẽ gây ra lỗi nếu bạn cố gắng sử dụng nó trên, giả sử STDIN_FILENO.
Joey Adams

76
Chủ đề này hiện là hit đầu tiên trên Google, Google yêu thích stackoverflow
Eloff

12

read()write()chung chung hơn, chúng làm việc với bất kỳ mô tả tập tin. Tuy nhiên, chúng sẽ không hoạt động trên Windows.

Bạn có thể chuyển các tùy chọn bổ sung cho send()recv(), vì vậy bạn có thể phải sử dụng chúng trong một số trường hợp.


7

Gần đây tôi mới nhận thấy rằng khi tôi sử dụng write()trên một ổ cắm trong Windows, nó gần như hoạt động (FD được chuyển đến write()không giống với cái được truyền cho send(); tôi đã sử dụng _open_osfhandle()để chuyển FD sang write()). Tuy nhiên, nó không hoạt động khi tôi cố gửi dữ liệu nhị phân bao gồm ký tự 10. write()ở đâu đó đã chèn ký tự 13 trước đó. Thay đổi nó thành send()một tham số cờ 0 đã khắc phục vấn đề đó. read()có thể có vấn đề ngược lại nếu 13-10 liên tiếp trong dữ liệu nhị phân, nhưng tôi chưa kiểm tra nó. Nhưng đó dường như là một sự khác biệt có thể có giữa send()write().



6

Một điều khác trên linux là:

sendkhông cho phép hoạt động trên fd không socket. Vì vậy, ví dụ để viết trên cổng usb, writelà cần thiết.


3

"Hiệu suất và tốc độ"? Không phải những loại ... từ đồng nghĩa ở đây sao?

Dù sao, recv()cuộc gọi lấy cờ read()không, điều đó làm cho nó mạnh hơn hoặc ít nhất là thuận tiện hơn. Đó là một sự khác biệt. Tôi không nghĩ rằng có một sự khác biệt đáng kể về hiệu suất, nhưng chưa được thử nghiệm cho nó.


15
Có lẽ không phải đối phó với cờ có thể được coi là thuận tiện hơn.
semaj

2

Trên Linux tôi cũng nhận thấy rằng:

Ngắt các cuộc gọi hệ thống và chức năng thư viện bằng trình xử lý tín hiệu
Nếu trình xử lý tín hiệu được gọi trong khi cuộc gọi hệ thống hoặc cuộc gọi chức năng thư viện bị chặn, thì:

  • cuộc gọi được tự động khởi động lại sau khi bộ xử lý tín hiệu trở lại; hoặc là

  • cuộc gọi thất bại với lỗi EINTR.

... Các chi tiết khác nhau trên các hệ thống UNIX; bên dưới, các chi tiết cho Linux.

Nếu một cuộc gọi bị chặn đến một trong các giao diện sau bị gián đoạn bởi trình xử lý tín hiệu, thì cuộc gọi sẽ tự động được khởi động lại sau khi trình xử lý tín hiệu trả về nếu cờ SA_RESTART được sử dụng; nếu không, cuộc gọi không thành công với lỗi EINTR:

  • đọc (2), readv (2), viết (2), writev (2) và ioctl (2) gọi trên các thiết bị "chậm".

.....

Các giao diện sau không bao giờ được khởi động lại sau khi bị ngắt bởi bộ xử lý tín hiệu, bất kể việc sử dụng SA_RESTART; chúng luôn bị lỗi với EINTR khi bị ngắt bởi bộ xử lý tín hiệu:

  • Giao diện ổ cắm "đầu vào", khi thời gian chờ (SO_RCVTIMEO) đã được đặt trên ổ cắm bằng cách sử dụng setsockopt (2): accept (2), recv (2), recvfrom (2), recvmmsg (2) (cũng không phải là NULL đối số hết thời gian chờ) và recvmsg (2).

  • Giao diện ổ cắm "đầu ra", khi thời gian chờ (SO_RCVTIMEO) đã được đặt trên ổ cắm bằng setsockopt (2): kết nối (2), gửi (2), sendto (2) và sendmsg (2).

Kiểm tra man 7 signalđể biết thêm chi tiết.


Một cách sử dụng đơn giản sẽ là tín hiệu sử dụng để tránh recvfromchặn vô thời hạn.

Một ví dụ từ APUE :

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>

#define BUFLEN      128
#define TIMEOUT     20

void
sigalrm(int signo)
{
}

void
print_uptime(int sockfd, struct addrinfo *aip)
{
    int     n;
    char    buf[BUFLEN];

    buf[0] = 0;
    if (sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0)
        err_sys("sendto error");
    alarm(TIMEOUT);
    //here
    if ((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0) {
        if (errno != EINTR)
            alarm(0);
        err_sys("recv error");
    }
    alarm(0);
    write(STDOUT_FILENO, buf, n);
}

int
main(int argc, char *argv[])
{
    struct addrinfo     *ailist, *aip;
    struct addrinfo     hint;
    int                 sockfd, err;
    struct sigaction    sa;

    if (argc != 2)
        err_quit("usage: ruptime hostname");
    sa.sa_handler = sigalrm;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGALRM, &sa, NULL) < 0)
        err_sys("sigaction error");
    memset(&hint, 0, sizeof(hint));
    hint.ai_socktype = SOCK_DGRAM;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
        err_quit("getaddrinfo error: %s", gai_strerror(err));

    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = socket(aip->ai_family, SOCK_DGRAM, 0)) < 0) {
            err = errno;
        } else {
            print_uptime(sockfd, aip);
            exit(0);
        }
    }

    fprintf(stderr, "can't contact %s: %s\n", argv[1], strerror(err));
    exit(1);
}
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.