天天看點

Linux程序間通信——使用資料報套接字

一、簡單回顧——什麼是資料報套接字。

socket,即套接字是一種通信機制,憑借這種機制,客戶/伺服器(即要進行通信的程序)系統的開發工作既可以在本地單機上進行,也可以跨網絡進行。也就是說它可以讓不在同一台計算機但通過網絡連接配接計算機上的程序進行通信。也因為這樣,套接字明确地将用戶端和伺服器區分開來。

相對于流套接字,資料報套接字的使用更為簡單,它是由類型SOCK_DGRAM指定的,它不需要建立連接配接和維持一個連接配接,它們在AF_INET中通常是通過UDP/IP協定實作的。它對可以發送的資料的長度有限制,資料報作為一個單獨的網絡消息被傳輸,它可能會丢失、複制或錯亂到達,UDP不是一個可靠的協定,但是它的速度比較高,因為它并一需要總是要建立和維持一個連接配接。

二、基于流套接字的客戶/伺服器的工作流程

使用資料報socket進行程序通信的程序采用的客戶/伺服器系統是如何工作的呢?

1、伺服器端

與使用流套接字一樣,首先伺服器應用程式用系統調用socket來建立一個套接安,它是系統配置設定給該伺服器程序的類似檔案描述符的資源,它不能與其他的程序共享。

接下來,伺服器程序會給套接字起個名字(監聽),我們使用系統調用bind來給套接字命名。然後伺服器程序就開始等待客戶連接配接到這個套接字。

不同的是,然後系統調用recvfrom來接收來自客戶程式發送過來的資料。伺服器程式對資料進行相應的處理,再通過系統調用sendto把處理後的資料發送回客戶程式。

與流套接字程式相比:

1、在流套接字中的程式中,接收資料是通過系統調用read,而發送資料是通過系統調用write來實作,而在資料報套接字程式中,這是通過recvfrom和sendto調用來實作的。

2、使用資料報套接字的伺服器程式并不需要listen調用來建立一個隊列來存儲連接配接,也不需要accept調用來接收連接配接并建立一個新的socket描述符

2、用戶端

基于資料報socket的用戶端比伺服器端簡單,同樣,客戶應用程式首先調用socket來建立一個未命名的套接字,與伺服器一樣,客戶也是通過sendto和recvfrom來向伺服器發送資料和從伺服器程式接收資料。

使用資料報套接字的客戶程式并不需要使用connect系統調用來連接配接伺服器程式,它隻要在需要時向伺服器所監聽的IP端口發送資訊和接收從伺服器發送回來的資料即可。

三、資料報socket的接口及作用

socket的接口函數聲明在頭檔案sys/types.h和sys/socket.h中。

1、建立套接字——socket系統調用

該函數用來建立一個套接字,并傳回一個描述符,該描述符可以用來通路該套接字,它的原型如下:

int socket(int domain, int type, int protocol);  

函數中的三個參數分别對應前面所說的三個套接字屬性。protocol參數設定為0表示使用預設協定。

2、命名(綁定)套接字——bind系統調用

該函數把通過socket調用建立的套接字命名,進而讓它可以被其他程序使用。對于AF_UNIX,調用該函數後套接字就會關聯到一個檔案系統路徑名,對于AF_INET,則會關聯到一個IP端口号。函數原型如下:

int bind( int socket, const struct sockaddr *address, size_t address_len);  

成功時傳回0,失敗時傳回-1;

3、發送資料——sendto系統調用

該函數把緩沖區buffer中的資訊給送給指定的IP端口的程式,原型如下:

int sendto(int sockfd, void *buffer, size_t len, int flags, struct sockaddr *to, socklen_t tolen);  

buffer中儲存着将要發送的資料,len是buffer的長度,而flags在應用中通常被設定為0,to是要發送資料到的程式的IP端口,tolen是to參數的長度。

成功時傳回發送的資料的位元組數,失敗時傳回-1.

4、接收資料——recvfrom系統調用

該函數把發送給程式的資訊儲存在緩沖區buffer中,并記錄資料來源的程式IP端口,原型如下:

int recvfrom(int sockfd, void *buffer, size_t len,int flags, struct sockaddr *src_from, socklen_t *src_len);  

buffer用于儲存接收到的資料,len指定buffer的長度,而flags在應用中通常被設定0,src_from若不為空,則記錄資料來源程式的IP端口,若src_len不為空,則其長度資訊記錄在src_len所指向的變量中。

注意:預設情況下,recvfrom是一個阻塞的調用,即直到它接收到資料才會傳回。

5、關閉socket——close系統調用

該系統調用用來終止伺服器和客戶上的套接字連接配接,我們應該總是在連接配接的兩端(伺服器和客戶)關閉套接字。

四、程序使用資料報socket進行通信

下面用多個客戶程式執行個體和一個伺服器程式來示範多個程序如何通過使用資料報socket來進行通信。

sockserver2.c是一個伺服器程式,它接收客戶程式發來的資料,并建立一個子程序來處理客戶發送過來的資料,處理過程非常簡單,就是把大寫字母改為小寫。然後把處理後的資料(大寫字母對應的小寫字母)發送回給用戶端。

sockclient2.c是一個客戶程式,它向伺服器程式發送資料,并接收伺服器發送過來的處理後的資料(即小寫字母),然後把接收到的資料輸出到螢幕上。在運作客戶程式時,你可以為它提供一個字元作為參數,此時客戶程式将把些字元作為要處理的資料發送給伺服器,如果不提供一個參數,則預設發送字元A。

sockserver2.c的源代碼如下:

#include <unistd.h>  

#include <sys/types.h>  

#include <sys/socket.h>  

#include <netinet/in.h>  

#include <stdio.h>  

#include <stdlib.h>  

#include <signal.h>  

int main()  

{  

    int server_sockfd = -1;  

    int server_len = 0;  

    int client_len = 0;  

    char buffer[512];  

    int result = 0;  

    struct sockaddr_in server_addr;  

    struct sockaddr_in client_addr;  

    //建立資料報套接字  

    server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);  

    //設定監聽IP端口  

    server_addr.sin_family = AF_INET;  

    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  

    server_addr.sin_port = htons(9739);  

    server_len = sizeof(server_addr);  

    //綁定(命名)套接字  

    bind(server_sockfd, (struct sockaddr*)&server_addr, server_len);  

    //忽略子程序停止或退出信号  

    signal(SIGCHLD, SIG_IGN);  

    while(1)  

    {     

        //接收資料,用client_addr來儲存資料來源程式的IP端口  

        result = recvfrom(server_sockfd, buffer, sizeof(buffer), 0,   

                (struct sockaddr*)&client_addr, &client_len);  

        if(fork() == 0)  

        {  

            //利用子程序來處理資料  

            buffer[0] += 'a' - 'A';  

            sleep(5);  

            //發送處理後的資料  

            sendto(server_sockfd, buffer, sizeof(buffer),0 ,   

                (struct sockaddr*)&client_addr, client_len);  

            printf("%c\n", buffer[0]);  

            //注意,一定要關閉子程序,否則程式運作會不正常   

            exit(0);  

        }  

    }  

    //關閉套接字  

    close(server_sockfd);  

}  

sockclient2.c的源代碼如下:

int main(int agrc, char *argv[])  

    int sockfd = -1;  

    char c = 'A';  

    //取第一個參數的第一個字元  

    if(agrc > 1)  

        c = argv[1][0];  

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);  

    //設定伺服器IP端口  

    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  

    //向伺服器發送資料  

    sendto(sockfd, &c, sizeof(char), 0,   

        (struct sockaddr*)&server_addr, server_len);  

    //接收伺服器處理後發送過來的資料,由于不關心資料來源,是以把後兩個參數設為0  

    recvfrom(sockfd, &c, sizeof(char), 0, 0, 0);  

    printf("char from server = %c\n", c);  

    close(sockfd);  

    exit(0);   

運作結果如下:

先運作伺服器程式,如下:

Linux程式間通信——使用資料報套接字

再運作三個用戶端:如下:

Linux程式間通信——使用資料報套接字

在本例子中,我們啟動了一個伺服器程式和三個客戶程式,從運作的結果來看,用戶端發送給伺服器程式的所有請求都得到了處理,即把大寫字母變成了小寫。recvfrom調用是阻塞的調用,即隻有當接收到資料才會傳回。

五、資料報套接字與流套接字的比較

1、從使用的便利和效率來講

我們可以看到使用資料報套接字的确是比使用流套接字簡單,而且快速。

因為使用流套接字的程式,客戶程式需要調用connect來建立一個到伺服器程式的連接配接,并需要維持這個連接配接,伺服器程式也需要調用listen來建立一個隊列來儲存未處理的請求,當有資料到達時,伺服器也不需要調用accept來接受連接配接并建立一個新socket描述符來處理請求。

再來看看使用資料報套接字的程式,伺服器程式與客戶程式所使用的系統調用大緻相同,伺服器程式隻比客戶程式多使用了一個bind調用。基于資料報套接字的程式,隻需要使用sendto調用來向指定IP端口的程式發送資訊,使用recvfrom調用從指向的IP端口接收資訊即可。因為它并不需要建立一個連接配接,接受連接配接等,是以省去了很多的功夫。

2、從使用場合來講

我們知道流套接字是基于TCP/IP協定的,它是一種安全的協定,提供的是一個有序、可靠、雙向位元組流的連接配接,發送的資料可以確定不會丢失、重複或亂序到達,而且它還有一定的出錯後重新發送的機制。是以它比較适合用來發送資訊量大的資料檔案,或對資料完整性要求較高的檔案,如壓縮檔案、視訊檔案等

而資料報套接字是基于UDP/IP協定實作的。它對可以發送的資料的長度有限制,資料報作為一個單獨的網絡消息被傳輸,它可能會丢失、複制或錯亂到達,UDP不是一個可靠的協定,但是它的速度比較高。是以它比較适合發送一些對實時性要求較高,但是對安全性和完整性要求不太高的資料。如我們熟悉的聊天資訊,即使有一點的丢失也不會造成了解上的大的問題。

繼續閱讀