ความแตกต่างระหว่าง read () และ recv () และระหว่าง send () และ write () คืออะไร


200

อะไรคือความแตกต่างระหว่างread()และและrecv()และระหว่างsend()และwrite()ในการเขียนโปรแกรมซ็อกเก็ตในแง่ของการแสดงความเร็วและพฤติกรรมอื่น ๆ ?


3
#define write(...) send(##__VA_ARGS__, 0)คิดว่าการเขียนเป็นดำเนินการเช่นนี้
carefulnow1

คำตอบ:


129

ข้อแตกต่างคือrecv()/ send()ทำงานได้เฉพาะกับตัวอธิบายซ็อกเก็ตและให้คุณระบุตัวเลือกบางอย่างสำหรับการทำงานจริง ฟังก์ชั่นเหล่านั้นมีความพิเศษมากกว่าเล็กน้อย (เช่นคุณสามารถตั้งค่าสถานะให้ละเว้นได้SIGPIPEหรือส่งข้อความนอกวง ... )

ฟังก์ชั่นread()/ write()เป็นฟังก์ชั่นอธิบายไฟล์สากลที่ทำงานกับตัวอธิบายทั้งหมด


3
สิ่งนี้ไม่ถูกต้องมีความแตกต่างอีกประการหนึ่งในกรณีของดาตาแกรมที่มีความยาว 0 - หากดาตาแกรมที่มีความยาวเป็นศูนย์กำลังรอให้อ่าน (2) และ recv () ด้วยอาร์กิวเมนต์แฟล็กของศูนย์ให้พฤติกรรมที่แตกต่างกัน ในสถานการณ์เช่นนี้การอ่าน (2) จะไม่มีผลกระทบ (ดาตาแกรมยังคงค้างอยู่) ในขณะที่ recv () จะใช้ดาตาแกรมที่ค้างอยู่
Abhinav Gauniyal

2
@AbhinavGauniyal สิ่งนั้นจะให้พฤติกรรมที่แตกต่างกันอย่างไร หากมีดาตาแกรม 0 ไบต์ทั้งคู่recvและreadจะไม่ส่งข้อมูลไปยังผู้โทร แต่จะไม่มีข้อผิดพลาด สำหรับผู้โทรพฤติกรรมจะเหมือนกัน ผู้เรียกอาจไม่รู้อะไรเลยเกี่ยวกับดาตาแกรม (อาจไม่รู้ว่านี่คือซ็อกเก็ตและไม่ใช่ไฟล์มันอาจไม่รู้ว่านี่คือซ็อกเก็ตดาตาแกรมและไม่ใช่ซ็อกเก็ตสตรีม) การที่ดาตาแกรมยังคงค้างอยู่นั้นเป็นความรู้โดยนัยเกี่ยวกับการทำงานของ IP สแต็คในเมล็ดและผู้โทรไม่สามารถมองเห็นได้ จากมุมมองผู้โทรพวกเขาจะยังคงให้พฤติกรรมที่เท่าเทียมกัน
Mecki

2
@Mecki ที่ไม่เรียนรู้ที่แฝงสำหรับทุกคนพาฉันเช่น :)
Abhinav Gauniyal

1
@Mecki การไม่อ่านบล็อก 0 ไบต์ที่ประสบความสำเร็จหมายถึงอะไร ดาตาแกรมยังคงค้างอยู่หรือไม่? ตรงนั้นและที่เป็นกังวลฉัน: พฤติกรรมที่ดาต้าสามารถอยู่ระหว่างดำเนินการแม้ว่าอ่านเรียบร้อยแล้ว ฉันไม่แน่ใจว่าสถานการณ์สามารถเกิดขึ้นได้หรือไม่ซึ่งฉันต้องการเก็บไว้ในใจ
sehe

2
@sehe หากคุณกังวลทำไมคุณไม่ใช้recv? เหตุผลที่recvและsendสถานที่ที่แนะนำในตอนแรกคือความจริงที่ว่าแนวคิดดาตาแกรมทั้งหมดไม่สามารถแมปกับโลกของสตรีมได้ readและwriteปฏิบัติต่อทุกสิ่งเหมือนสตรีมข้อมูลไม่ว่าจะเป็นไพพ์ไฟล์อุปกรณ์ (เช่นพอร์ตอนุกรม) หรือซ็อกเก็ต แต่ซ็อกเก็ตเป็นเพียงสตรีมจริงหากใช้ TCP ถ้ามันใช้ UDP มันก็เหมือนอุปกรณ์บล็อค แต่ถ้าทั้งสองฝ่ายใช้มันเหมือนสตรีมมันจะทำงานเหมือนสตรีมและคุณไม่สามารถส่งแพ็กเก็ต UDP ที่ว่างโดยใช้การwriteโทรดังนั้นสถานการณ์นี้จะไม่เกิดขึ้น
Mecki

85

ต่อการโจมตีครั้งแรกใน Google

read () เทียบเท่ากับ recv () โดยมีพารามิเตอร์ค่าสถานะเป็น 0 ค่าอื่น ๆ สำหรับพารามิเตอร์ค่าสถานะจะเปลี่ยนพฤติกรรมของ recv () ในทำนองเดียวกันการเขียน () เทียบเท่ากับ send () กับค่าสถานะ == 0


31
นี่ไม่ใช่เรื่องราวทั้งหมด สามารถนำมาใช้เฉพาะในซ็อกเก็ตและจะผลิตข้อผิดพลาดถ้าคุณพยายามที่จะใช้ในการพูดrecv STDIN_FILENO
Joey Adams

77
ตอนนี้กระทู้นี้เป็นที่นิยมใน Google เป็นครั้งแรก Google ชอบ stackoverflow
Eloff

12

read()และwrite()เป็นเรื่องทั่วไปมากกว่าพวกมันจะทำงานร่วมกับ file descriptor ใด ๆ อย่างไรก็ตามพวกเขาจะไม่ทำงานบน Windows

คุณสามารถส่งตัวเลือกเพิ่มเติมไปที่send()และrecv()ดังนั้นคุณอาจต้องใช้ตัวเลือกเหล่านี้ในบางกรณี


7

ฉันเพิ่งสังเกตเห็นเมื่อเร็ว ๆ นี้ว่าเมื่อฉันใช้write()ซ็อกเก็ตใน Windows มันเกือบจะใช้งานได้ (FD ที่ส่งไปยังwrite()ไม่เหมือนกับที่ส่งให้send()ฉันใช้_open_osfhandle()เพื่อให้ FD ส่งผ่านไปwrite()) อย่างไรก็ตามมันไม่ทำงานเมื่อฉันพยายามส่งข้อมูลไบนารีที่มีอักขระ 10 write()อยู่ที่ไหนสักแห่งที่แทรกอักขระ 13 ไว้ก่อนหน้านี้ การเปลี่ยนsend()เป็นพารามิเตอร์ค่าสถานะเป็น 0 จะแก้ไขปัญหานั้นได้ read()อาจมีปัญหาย้อนกลับหาก 13-10 อยู่ติดกันในข้อมูลไบนารี แต่ฉันไม่ได้ทดสอบ แต่ที่ดูเหมือนจะเป็นอีกความแตกต่างระหว่างที่เป็นไปได้และsend()write()



6

สิ่งอื่นบน linux คือ:

sendไม่อนุญาตให้ทำงานบนไม่ใช่ซ็อกเก็ต fd ดังนั้นตัวอย่างเช่นการเขียนบนพอร์ต USB writeเป็นสิ่งที่จำเป็น


2

"ประสิทธิภาพและความเร็ว" คำพ้องความหมายประเภทนี้ใช่มั้ย

อย่างไรก็ตามการrecv()โทรจะใช้ธงที่read()ไม่ได้ทำให้มีประสิทธิภาพมากขึ้นหรืออย่างน้อยก็สะดวกกว่า นั่นคือความแตกต่างอย่างหนึ่ง ฉันไม่คิดว่าจะมีความแตกต่างด้านประสิทธิภาพที่สำคัญ แต่ไม่ได้ทำการทดสอบ


15
บางทีการไม่จัดการกับธงอาจจะสะดวกกว่า
semaj

2

บน Linux ฉันยังสังเกตเห็นว่า:

การหยุดชะงักของการเรียกของระบบและฟังก์ชั่นห้องสมุดโดย
ตัวจัดการสัญญาณหากตัวจัดการสัญญาณถูกเรียกใช้ในขณะที่การเรียกระบบหรือการเรียกฟังก์ชั่นห้องสมุดถูกบล็อกแล้วอย่างใดอย่างหนึ่ง:

  • การโทรจะเริ่มใหม่โดยอัตโนมัติหลังจากที่ตัวจัดการสัญญาณส่งกลับ หรือ

  • การโทรล้มเหลวโดยมีข้อผิดพลาด EINTR

... รายละเอียดแตกต่างกันไปตามระบบ UNIX; ด้านล่างรายละเอียดสำหรับ Linux

หากการเรียกที่ถูกบล็อกไปยังหนึ่งในอินเตอร์เฟสต่อไปนี้ถูกขัดจังหวะโดยตัวจัดการสัญญาณการโทรนั้นจะเริ่มต้นใหม่โดยอัตโนมัติหลังจากตัวจัดการสัญญาณส่งกลับหากตัวบ่งชี้ SA_RESTART ถูกใช้ มิฉะนั้นการโทรล้มเหลวโดยมีข้อผิดพลาด EINTR:

  • read (2), readv (2), write (2), writev (2) และ ioctl (2) โทรบนอุปกรณ์ "ช้า"

.....

อินเทอร์เฟซต่อไปนี้จะไม่รีสตาร์ทหลังจากถูกขัดจังหวะโดยตัวจัดการสัญญาณโดยไม่คำนึงถึงการใช้ SA_RESTART พวกเขามักจะล้มเหลวด้วยข้อผิดพลาด EINTR เมื่อถูกขัดจังหวะโดยตัวจัดการสัญญาณ:

  • "ป้อนข้อมูล" อินเตอร์เฟซซ็อกเก็ตเมื่อหมดเวลา (SO_RCVTIMEO) ได้รับการติดตั้งบนซ็อกเก็ตโดยใช้ setsockopt (2): ยอมรับ (2), recv (2), recvfrom (2), recvmmsg (2) (ยังมีที่ไม่เป็นโมฆะ อาร์กิวเมนต์หมดเวลา) และ recvmsg (2)

  • ส่วนต่อประสานซ็อกเก็ต "เอาท์พุท" เมื่อหมดเวลา (SO_RCVTIMEO) ได้รับการตั้งค่าบนซ็อกเก็ตโดยใช้ setsockopt (2): เชื่อมต่อ (2), ส่ง (2), ส่ง (2), sendto (2) และ sendmsg (2)

ตรวจสอบman 7 signalรายละเอียดเพิ่มเติม


การใช้งานที่เรียบง่ายจะใช้สัญญาณเพื่อหลีกเลี่ยงrecvfromการปิดกั้นอย่างไม่มีกำหนด

ตัวอย่างจาก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);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.