ซ็อกเก็ตเชื่อมต่อ () กับผูก ()


122

ทั้งสองconnect()และbind()ระบบเรียก 'เชื่อมโยง' ตัวอธิบายไฟล์ซ็อกเก็ตไปยังที่อยู่ (โดยทั่วไปคือการรวมกันของ ip / พอร์ต) ต้นแบบของพวกเขามีดังนี้: -

int connect(int sockfd, const struct sockaddr *addr,
               socklen_t addrlen);

และ

int bind(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);

2 สายต่างกันอย่างไร? ควรใช้connect()เมื่อใดและเมื่อbind()ใด

โดยเฉพาะในโค้ดไคลเอ็นต์เซิร์ฟเวอร์ตัวอย่างบางตัวพบว่าไคลเอ็นต์กำลังใช้งานconnect()และเซิร์ฟเวอร์กำลังใช้การbind()โทร เหตุผลไม่ชัดเจนสำหรับฉัน


19
ในประโยคเดียว: การผูกคือกับที่อยู่ในพื้นที่การเชื่อมต่อคือที่อยู่ระยะไกล
SHR

คำตอบ:


232

เพื่อให้เกิดความเข้าใจที่ดีขึ้นมาดูกันว่าการเชื่อมโยงและการเชื่อมต่อเข้ามาในภาพ

นอกเหนือจากการวางตำแหน่งของสองสายตามที่ Sourav ชี้แจงแล้ว

bind () เชื่อมโยงซ็อกเก็ตกับที่อยู่ในเครื่อง [นั่นคือสาเหตุที่ฝั่งเซิร์ฟเวอร์ผูกเพื่อให้ไคลเอนต์สามารถใช้ที่อยู่นั้นเพื่อเชื่อมต่อกับเซิร์ฟเวอร์ได้] connect () ใช้เพื่อเชื่อมต่อกับที่อยู่ [เซิร์ฟเวอร์] ระยะไกลนั่นคือสาเหตุที่ฝั่งไคลเอ็นต์ ใช้เชื่อมต่อ [อ่านว่า: เชื่อมต่อกับเซิร์ฟเวอร์]

เราไม่สามารถใช้แทนกันได้ (แม้ว่าเราจะมีไคลเอนต์ / เซิร์ฟเวอร์บนเครื่องเดียวกันก็ตาม) เนื่องจากบทบาทเฉพาะและการนำไปใช้งานที่เกี่ยวข้อง

ฉันจะแนะนำให้เชื่อมโยงการโทรเหล่านี้ TCP / IP handshake

ใส่คำอธิบายภาพที่นี่

ดังนั้นใครจะส่ง SYN มาที่นี่มันจะเชื่อมต่อ () ในขณะที่ bind () ใช้สำหรับกำหนดจุดสิ้นสุดการสื่อสาร

หวังว่านี่จะช่วยได้ !!


1
ขอบคุณครับ ด้วยแผนภาพทุกอย่างไม่สามารถเข้าใจได้อย่างรวดเร็ว คุณบอกได้ไหมว่าความแตกต่างที่นี่คืออะไรถ้าเราใช้ udp?
เม.ย.

8
ยอมรับ () <br> ควรย้ายด้านล่าง <br> บล็อกจนกว่าจะเชื่อมต่อจากไคลเอนต์
tschodt

ฉันคิดว่าโหนดทั้งหมดในเครือข่ายในเครือข่าย p2p ควรใช้การผูกฉันถูกต้องหรือไม่
kapil

46

หนึ่งซับ: bind()ที่อยู่ของตัวเองconnect()ไปยังที่อยู่ระยะไกล

อ้างจากเพจคนของ bind()

bind () กำหนดแอดเดรสที่ระบุโดย addr ให้กับซ็อกเก็ตที่อ้างถึงโดย file descriptor sockfd addrlen ระบุขนาดเป็นไบต์ของโครงสร้างแอดเดรสที่แอดเดรสชี้ไป ตามเนื้อผ้าการดำเนินการนี้เรียกว่า "การกำหนดชื่อให้กับซ็อกเก็ต"

และจากสิ่งเดียวกันสำหรับ connect()

การเรียกระบบ connect () เชื่อมต่อซ็อกเก็ตที่อ้างถึงโดย file descriptor sockfd กับแอดเดรสที่ระบุโดย addr

เพื่อชี้แจง

  • bind()เชื่อมโยงซ็อกเก็ตกับที่อยู่ภายในเครื่อง [นั่นคือเหตุผลที่ฝั่งเซิร์ฟเวอร์bindเพื่อให้ไคลเอนต์สามารถใช้ที่อยู่นั้นเพื่อเชื่อมต่อกับเซิร์ฟเวอร์]
  • connect() ใช้เพื่อเชื่อมต่อกับที่อยู่ [เซิร์ฟเวอร์] ระยะไกลด้วยเหตุนี้จึงใช้ฝั่งไคลเอ็นต์เชื่อมต่อ [อ่านว่า: เชื่อมต่อกับเซิร์ฟเวอร์]

ถ้าทั้งเซิร์ฟเวอร์และไคลเอนต์ทำงานบนเครื่องเดียวกันจะใช้แทนกันได้หรือไม่?
Siddhartha Ghosh

1
@SiddharthaGhosh ไม่บางทีไคลเอนต์และเซิร์ฟเวอร์อยู่ในเครื่องเดียวกัน แต่ก็ยังเป็นกระบวนการที่แตกต่างกันใช่ไหม? ทั้ง API ให้บริการดักแด้ของตัวเอง พวกเขาไม่เคยinterchangeable
Sourav Ghosh

local และ remote มีความหมายว่าอย่างไรในบริบทนี้
Siddhartha Ghosh

@SiddharthaGhosh local-> กระบวนการเองremote-> กระบวนการอื่น ๆ
Sourav Ghosh

@SouravGhosh ดังนั้นนี่หมายความว่าฉันไม่สามารถระบุพอร์ตที่จะผูกกับฝั่งไคลเอ็นต์ได้?
Hengqi Chen

12

ผูกบอกกระบวนการทำงานเพื่ออ้างสิทธิ์พอร์ต กล่าวคือควรผูกตัวเองเข้ากับพอร์ต 80 และรับฟังคำขอที่ไม่ได้เข้าร่วม ด้วยการผูกกระบวนการของคุณจะกลายเป็นเซิร์ฟเวอร์ เมื่อคุณใช้การเชื่อมต่อคุณจะบอกกระบวนการของคุณเพื่อเชื่อมต่อกับพอร์ตที่ใช้งานอยู่ กระบวนการของคุณกลายเป็นลูกค้า ความแตกต่างเป็นสิ่งสำคัญ: bind ต้องการพอร์ตที่ไม่ได้ใช้งาน (เพื่อให้สามารถอ้างสิทธิ์และกลายเป็นเซิร์ฟเวอร์ได้) และ connect ต้องการพอร์ตที่ใช้งานอยู่แล้ว (เพื่อให้สามารถเชื่อมต่อและคุยกับเซิร์ฟเวอร์ได้)


9

จาก Wikipedia http://en.wikipedia.org/wiki/Berkeley_sockets#bind.28.29

เชื่อมต่อ ():

การเรียกระบบ connect () เชื่อมต่อซ็อกเก็ตซึ่งระบุโดยตัวอธิบายไฟล์กับโฮสต์ระยะไกลที่ระบุโดยแอดเดรสของโฮสต์นั้นในรายการอาร์กิวเมนต์

ซ็อกเก็ตบางประเภทไม่มีการเชื่อมต่อซ็อกเก็ตโปรโตคอลดาตาแกรมที่ผู้ใช้ส่วนใหญ่มักใช้ สำหรับซ็อกเก็ตเหล่านี้การเชื่อมต่อจะมีความหมายพิเศษ: เป้าหมายเริ่มต้นสำหรับการส่งและรับข้อมูลจะถูกตั้งค่าเป็นที่อยู่ที่กำหนดทำให้สามารถใช้ฟังก์ชันต่างๆเช่น send () และ recv () บนซ็อกเก็ตที่ไม่มีการเชื่อมต่อ

connect () ส่งคืนจำนวนเต็มแทนรหัสข้อผิดพลาด: 0 หมายถึงความสำเร็จในขณะที่ -1 แสดงถึงข้อผิดพลาด

ผูก():

bind () กำหนดซ็อกเก็ตให้กับแอดเดรส เมื่อสร้างซ็อกเก็ตโดยใช้ซ็อกเก็ต () จะได้รับเฉพาะตระกูลโปรโตคอล แต่ไม่ได้กำหนดแอดเดรส การเชื่อมโยงกับแอดเดรสนี้ต้องดำเนินการด้วยการเรียกระบบ bind () ก่อนที่ซ็อกเก็ตจะสามารถยอมรับการเชื่อมต่อกับโฮสต์อื่น ผูก () รับสามอาร์กิวเมนต์:

sockfd ตัวอธิบายที่แสดงถึงซ็อกเก็ตเพื่อทำการผูก my_addr ตัวชี้ไปยังโครงสร้าง sockaddr ที่แสดงที่อยู่ที่จะผูก addrlen คือฟิลด์ socklen_t ที่ระบุขนาดของโครงสร้าง sockaddr Bind () ส่งคืน 0 เมื่อสำเร็จและ -1 หากเกิดข้อผิดพลาด

ตัวอย่าง: 1. ) การใช้ Connect

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main(){
  int clientSocket;
  char buffer[1024];
  struct sockaddr_in serverAddr;
  socklen_t addr_size;

  /*---- Create the socket. The three arguments are: ----*/
  /* 1) Internet domain 2) Stream socket 3) Default protocol (TCP in this case) */
  clientSocket = socket(PF_INET, SOCK_STREAM, 0);

  /*---- Configure settings of the server address struct ----*/
  /* Address family = Internet */
  serverAddr.sin_family = AF_INET;
  /* Set port number, using htons function to use proper byte order */
  serverAddr.sin_port = htons(7891);
  /* Set the IP address to desired host to connect to */
  serverAddr.sin_addr.s_addr = inet_addr("192.168.1.17");
  /* Set all bits of the padding field to 0 */
  memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);  

  /*---- Connect the socket to the server using the address struct ----*/
  addr_size = sizeof serverAddr;
  connect(clientSocket, (struct sockaddr *) &serverAddr, addr_size);

  /*---- Read the message from the server into the buffer ----*/
  recv(clientSocket, buffer, 1024, 0);

  /*---- Print the received message ----*/
  printf("Data received: %s",buffer);   

  return 0;
}

2. ) ตัวอย่างการผูก:

int main()
{
    struct sockaddr_in source, destination = {};  //two sockets declared as previously
    int sock = 0;
    int datalen = 0;
    int pkt = 0;

    uint8_t *send_buffer, *recv_buffer;

    struct sockaddr_storage fromAddr;   // same as the previous entity struct sockaddr_storage serverStorage;
    unsigned int addrlen;  //in the previous example socklen_t addr_size;
    struct timeval tv;
    tv.tv_sec = 3;  /* 3 Seconds Time-out */
    tv.tv_usec = 0;

    /* creating the socket */         
    if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) 
        printf("Failed to create socket\n");

    /*set the socket options*/
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval));

    /*Inititalize source to zero*/
    memset(&source, 0, sizeof(source));       //source is an instance of sockaddr_in. Initialization to zero
    /*Inititalize destinaton to zero*/
    memset(&destination, 0, sizeof(destination));


    /*---- Configure settings of the source address struct, WHERE THE PACKET IS COMING FROM ----*/
    /* Address family = Internet */
    source.sin_family = AF_INET;    
    /* Set IP address to localhost */   
    source.sin_addr.s_addr = INADDR_ANY;  //INADDR_ANY = 0.0.0.0
    /* Set port number, using htons function to use proper byte order */
    source.sin_port = htons(7005); 
    /* Set all bits of the padding field to 0 */
    memset(source.sin_zero, '\0', sizeof source.sin_zero); //optional


    /*bind socket to the source WHERE THE PACKET IS COMING FROM*/
    if (bind(sock, (struct sockaddr *) &source, sizeof(source)) < 0) 
        printf("Failed to bind socket");

    /* setting the destination, i.e our OWN IP ADDRESS AND PORT */
    destination.sin_family = AF_INET;                 
    destination.sin_addr.s_addr = inet_addr("127.0.0.1");  
    destination.sin_port = htons(7005); 

    //Creating a Buffer;
    send_buffer=(uint8_t *) malloc(350);
    recv_buffer=(uint8_t *) malloc(250);

    addrlen=sizeof(fromAddr);

    memset((void *) recv_buffer, 0, 250);
    memset((void *) send_buffer, 0, 350);

    sendto(sock, send_buffer, 20, 0,(struct sockaddr *) &destination, sizeof(destination));

    pkt=recvfrom(sock, recv_buffer, 98,0,(struct sockaddr *)&destination, &addrlen);
    if(pkt > 0)
        printf("%u bytes received\n", pkt);
    }

ฉันหวังว่าจะชี้แจงความแตกต่าง

โปรดทราบว่าประเภทซ็อกเก็ตที่คุณประกาศจะขึ้นอยู่กับสิ่งที่คุณต้องการซึ่งเป็นสิ่งสำคัญอย่างยิ่ง


9

ฉันคิดว่ามันจะช่วยให้ความเข้าใจของคุณถ้าคุณคิดconnect()และlisten()เป็นคู่มากกว่าและconnect() bind()เหตุผลของเรื่องนี้คือการที่คุณสามารถโทรหรืองดbind()ก่อนที่ทั้งสองแม้ว่าจะไม่ค่อยมีความคิดที่ดีที่จะเรียกมันว่าก่อนหรือไม่ที่จะเรียกมันก่อนconnect()listen()

หากช่วยคิดในแง่ของเซิร์ฟเวอร์และไคลเอนต์สิ่งนี้listen()เป็นจุดเด่นของอดีตและอย่างconnect()หลัง bind()สามารถพบ - หรือไม่พบ - บนอย่างใดอย่างหนึ่ง

หากเราสมมติว่าเซิร์ฟเวอร์และไคลเอนต์ของเราอยู่คนละเครื่องก็จะเข้าใจฟังก์ชันต่างๆได้ง่ายขึ้น

bind()ทำหน้าที่ในเครื่องซึ่งหมายความว่าจะผูกจุดสิ้นสุดของการเชื่อมต่อบนเครื่องที่เรียกใช้กับที่อยู่ที่ร้องขอและกำหนดพอร์ตที่ร้องขอให้กับคุณ โดยไม่คำนึงว่าเครื่องนั้นจะเป็นไคลเอนต์หรือเซิร์ฟเวอร์ connect()เริ่มต้นการเชื่อมต่อกับเซิร์ฟเวอร์กล่าวคือเชื่อมต่อกับที่อยู่และพอร์ตที่ร้องขอบนเซิร์ฟเวอร์จากไคลเอนต์ เซิร์ฟเวอร์ที่เกือบจะแน่นอนจะได้เรียกว่าbind()ก่อนที่จะมีในการสั่งซื้อสำหรับคุณที่จะสามารถที่จะรู้ที่อยู่และพอร์ตเพื่อเชื่อมต่อกับการใช้listen()connect()

หากคุณไม่โทรbind()พอร์ตและที่อยู่จะถูกกำหนดโดยปริยายและผูกไว้บนเครื่องท้องถิ่นให้คุณเมื่อคุณโทรไปที่connect()(ไคลเอนต์) หรือlisten()(เซิร์ฟเวอร์) อย่างไรก็ตามนั่นเป็นผลข้างเคียงของทั้งสองอย่างไม่ใช่จุดประสงค์ พอร์ตที่กำหนดในลักษณะนี้เป็นแบบชั่วคราว

จุดสำคัญที่นี่คือไคลเอนต์ไม่จำเป็นต้องถูกผูกมัดเนื่องจากไคลเอนต์เชื่อมต่อกับเซิร์ฟเวอร์ดังนั้นเซิร์ฟเวอร์จะทราบที่อยู่และพอร์ตของไคลเอนต์แม้ว่าคุณจะใช้พอร์ตชั่วคราวแทนที่จะเชื่อมโยงกับบางสิ่งที่เฉพาะเจาะจง ในทางกลับกันแม้ว่าเซิร์ฟเวอร์จะโทรได้listen()โดยไม่ต้องโทรbind()แต่ในสถานการณ์นั้นพวกเขาจะต้องค้นหาพอร์ตชั่วคราวที่กำหนดไว้และสื่อสารกับไคลเอนต์ที่ต้องการเชื่อมต่อ

ฉันคิดว่าเมื่อคุณพูดถึงconnect()ว่าคุณสนใจ TCP แต่สิ่งนี้ก็ส่งต่อไปยัง UDP ด้วยเช่นกันโดยที่ไม่โทรbind()มาก่อนsendto()(UDP คือการเชื่อมต่อน้อยลง) ยังทำให้พอร์ตและที่อยู่ถูกกำหนดและผูกมัดโดยปริยาย ฟังก์ชันหนึ่งที่คุณไม่สามารถเรียกได้โดยไม่ต้องผูกคือrecvfrom()ซึ่งจะส่งกลับข้อผิดพลาดเนื่องจากหากไม่มีพอร์ตที่กำหนดและที่อยู่ที่ถูกผูกไว้จะไม่มีสิ่งใดที่จะได้รับ (หรือมากเกินไปขึ้นอยู่กับว่าคุณตีความว่าไม่มีการเชื่อมโยงอย่างไร)


1

นานเกินไป; อย่าอ่าน: ความแตกต่างคือการตั้งค่าต้นทาง (ในเครื่อง) หรือที่อยู่ / พอร์ตปลายทาง ในระยะสั้นbind()ตั้งต้นทางและconnect()กำหนดปลายทาง ไม่ว่า TCP หรือ UDP

bind()

bind()ตั้งค่าที่อยู่ท้องถิ่น (แหล่งที่มา) ของซ็อกเก็ต นี่คือที่อยู่ที่รับแพ็คเก็ต แพ็กเก็ตที่ส่งโดยซ็อกเก็ตจะถือเป็นที่อยู่ต้นทางดังนั้นโฮสต์อื่นจะรู้ว่าจะส่งแพ็กเก็ตกลับไปที่ใด

หากไม่ต้องการรับที่อยู่แหล่งซ็อกเก็ตก็ไร้ประโยชน์ โปรโตคอลเช่น TCP ต้องการการเปิดใช้งานการรับเพื่อที่จะส่งได้อย่างถูกต้องเนื่องจากโฮสต์ปลายทางส่งการยืนยันกลับเมื่อมีแพ็กเก็ตอย่างน้อยหนึ่งแพ็กเก็ตมาถึง (เช่นการรับทราบ)

connect()

  • TCP มีสถานะ "เชื่อมต่อ" connect()เรียกใช้รหัส TCP เพื่อพยายามสร้างการเชื่อมต่อกับอีกด้านหนึ่ง
  • UDP ไม่มีสถานะ "เชื่อมต่อ" connect()กำหนดเฉพาะแอดเดรสเริ่มต้นที่จะส่งแพ็กเก็ตเมื่อไม่มีการระบุแอดเดรส เมื่อconnect()ใดที่ไม่ใช้sendto()หรือsendmsg()ต้องใช้ที่อยู่ปลายทาง

เมื่อconnect()มีการเรียกใช้ฟังก์ชัน send และไม่มีการผูกแอดเดรส Linux จะผูกซ็อกเก็ตเข้ากับพอร์ตแบบสุ่มโดยอัตโนมัติ สำหรับรายละเอียดทางเทคนิคโปรดดูที่inet_autobind()ซอร์สโค้ดเคอร์เนลของ Linux

บันทึกด้านข้าง

  • listen() เป็น TCP เท่านั้น
  • ในตระกูลAF_INET ที่อยู่ต้นทางหรือปลายทาง ( struct sockaddr_in) ของซ็อกเก็ตประกอบด้วยที่อยู่ IP (ดูส่วนหัว IP ) และพอร์ต TCP หรือ UDP (ดูที่ส่วนหัวTCPและUDP )
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.