18-UDP的connect函數

1. 面向連接的UDP

 

在上一篇中遺留了一個問題:sendto函數產生的異步錯誤一般是不會返回給udp套接字的(主要是因爲udp是無連接的原因),如果這個錯誤要返回給udp套接字,那麼就需要調用connect函數。

是的,你沒看錯,udp也可以調用connect函數達到面向連接,但是這並不意味着udp也會產生三次握手的過程。我們調用connect函數最主要的目的只是記錄對端的ip地址和端口,並讓內核檢查是否存在錯誤(例如端口不可達等),然後再返回給調用connect的進程。

 

這裏我們來區分一下面向連接和麪向無連接的udp套接字的:

1. 對於面向無連接的udp套接字,也就是調用socket函數創建的套接字。而面向連接的udp套接字就是調用了connect函數。

 

2. 對於面向無連接的udp套接字,我們可以通過sendto函數向指定的目的ip地址和端口號發送數據,而對於面向連接的udp套接字來說,則是使用write或send函數向connect函數指定的目的ip地址和端口號發送數據。

需要注意的是,由於已連接的udp套接字調用了connect函數綁定了目的ip地址和端口,所以在調用sendto函數就不能指定目的地址了,即忽略第五,六參數(指定爲null)。同理,在接收數據時,因爲udp套接字只接收connect函數綁定的目的ip地址和端口的數據報,所以也不用調用recvfrom函數了,而是調用read或recv函數。

3. 面向連接的udp套接字會把錯誤返回給當前進程,未連接的套接字則不會返回錯誤,另外POSIX規定:未連接的udp套接字調用sendto函數發送數據時如果不指定目的地址會返回ENOTCONN錯誤,需要注意的是,對於不同的linux實現可能返回的錯誤也不盡相同的,例如4.4BSD實現會返回EDESTADDRREQ錯誤,而不是ENOTCONN錯誤。

 

 

修改udp客戶端程序:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>
#include <errno.h>


#define MAXLINE 1024
#define SERV_PORT 10001

int main(int argc, char *argv[])
{
        struct sockaddr_in servaddr;
        int sockfd, n;
        char buf[MAXLINE];

        sockfd = socket(AF_INET, SOCK_DGRAM, 0);

        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, "192.168.1.22", &servaddr.sin_addr);
        servaddr.sin_port = htons(SERV_PORT);

        //調用connect函數
        int ret = connect(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr));
        if(ret != 0){
                perror("connect error:");
        }

        while(fgets(buf, MAXLINE, stdin) != NULL){
                n = sendto(sockfd, buf, strlen(buf), 0, NULL, 0);
                if(errno == ENOTCONN){
                        perror("sendto error:");
                }
                n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
                if (n == -1)
                        perror("recvfrom error");
                write(STDOUT_FILENO, buf, n);
        }
        close(sockfd);
        return 0;
}

 

程序執行結果如下:

當udp客戶端調用connect函數後,通過tcpdump抓包我們可以看到客戶端和服務端雙方並沒有三次握手的過程。

 

此時停止服務端,再運行客戶端,我們發現udp客戶端的recvfrom函數返回了錯誤。

 

2. 多次調用connect

對於一個udp客戶端來說,是可能會有多次調用connect需求的,例如:

  1. 指定新的目的ip地址和端口
  2. 斷開套接字連接。

 

先說第一個需求,在tcp套接字中只能調用一次connect函數,但是在udp套接字中可以多次調用connect函數,例如當我們需要綁定新的目的ip地址和端口時就可以再次調用connect函數。

 

然後再說第二個需求,對於一個已連接的udp套接字來說,如果要斷開連接可以調用connect函數並指定套接字地址結構的地址族成員爲AF_UNSPEC,需要注意的是這會返回EAFNOSUPPORT錯誤,但是我們可以忽略這個錯誤,另外斷開套接字連接的方式在其他linux版本中存在差異,同樣的方式在某些linux系統中可能並不適用,爲了便於移植,通常會將一個套接字地址結構清零並設置地址族成員爲AF_UNSPEC,然後再調用connect斷開一個udp套接字連接。 

struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));

//設置套接字地址族成員爲AF_UNSPEC
servaddr.sin_family = AF_UNSPEC;
//然後調用connect斷開連接,爲了便於移植
connect(sockfd, &servaddr, sizeof(servaddr));

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章