1. 程式人生 > >套接字通信

套接字通信

理解 jsb 留下 結果 不同 iss pyw lin lag

Linux進程間通信——使用流套接字

前面說到的進程間的通信,所通信的進程都是在同一臺計算機上的,而使用 socket進行通信的進程可以是同一臺計算機的進程,也是可以是通過網絡連接起來的不同計算機上的進程。通常我們使用socket進行網絡編程,這裏將 會簡單地講述如何使用socket進行簡單的網絡編程。 一、什麽是socket socket,即套接字是一種通信機制,憑借這種機制,客戶/服務器(即要進 行通信的進程)系統的開發工作既可以在本地單機上進行,也可以跨網絡進行。也就是說它可以讓不在同一臺計算機但通過網絡連接計算機上的進程進行通信。也因 為這樣,套接字明確地將客戶端和服務器區分開來。
二、套接字的屬性 套接字的特性由3個屬性確定,它們分別是:域、類型和協議。 1、套接字的域 它指定套接字通信中使用的網絡介質,最常見的套接字域是AF_INET,它指 的是Internet網絡。當客戶使用套接字進行跨網絡的連接時,它就需要用到服務器計算機的IP地址和端口來指定一臺聯網機器上的某個特定服務,所以在 使用socket作為通信的終點,服務器應用程序必須在開始通信之前綁定一個端口,服務器在指定的端口等待客戶的連接。另一個域AF_UNIX表示 UNIX文件系統,它就是文件輸入/輸出,而它的地址就是文件名。 2、套接字類型 因特網提供了兩種通信機制:流(stream)和數據報(datagram),因而套接字的類型也就分為流套接字和數據報套接字。這裏主要講流套接字。
流套接字由類型SOCK_STREAM指定,它們是在AF_INET域中通過 TCP/IP連接實現,同時也是AF_UNIX中常用的套接字類型。流套接字提供的是一個有序、可靠、雙向字節流的連接,因此發送的數據可以確保不會丟 失、重復或亂序到達,而且它還有一定的出錯後重新發送的機制。 與流套接字相對的是由類型SOCK_DGRAM指定的數據報套接字,它不需要 建立連接和維持一個連接,它們在AF_INET中通常是通過UDP/IP協議實現的。它對可以發送的數據的長度有限制,數據報作為一個單獨的網絡消息被傳 輸,它可能會丟失、復制或錯亂到達,UDP不是一個可靠的協議,但是它的速度比較高,因為它並一需要總是要建立和維持一個連接。
3、套接字協議 只要底層的傳輸機制允許不止一個協議來提供要求的套接字類型,我們就可以為套接字選擇一個特定的協議。通常只需要使用默認值。 三、套接字地址 每個套接字都有其自己的地址格式,對於AF_UNIX域套接字來說,它的地址由結構sockaddr_un來描述,該結構定義在頭文件sys/un.h中,它的定義如下:
struct sockaddr_un{
    sa_family_t sun_family;//AF_UNIX,它是一個短整型
    char        sum_path[];//路徑名
};

對於AF_INET域套接字來說,它的地址結構由sockaddr_in來描述,它至少包括以下幾個成員:

struct sockaddr_in{
    short int            sin_family;//AF_INET
    unsigned short int    sin_port;//端口號
    struct in_addr        sin_addr;//IP地址
};

而in_addr被定義為:

struct in_addr{
    unsigned long int s_addr;
};
四、基於流套接字的客戶/服務器的工作流程 使用socket進行進程通信的進程采用的客戶/服務器系統是如何工作的呢? 1、服務器端 首先服務器應用程序用系統調用socket來創建一個套接安,它是系統分配給該服務器進程的類似文件描述符的資源,它不能與其他的進程共享。 接下來,服務器進程會給套接字起個名字,我們使用系統調用bind來給套接字命名。然後服務器進程就開始等待客戶連接到這個套接字。 然後,系統調用listen來創建一個隊列並將其用於存放來自客戶的進入連接。 最後,服務器通過系統調用accept來接受客戶的連接。它會創建一個與原有的命名套接不同的新套接字,這個套接字只用於與這個特定客戶端進行通信,而命名套接字(即原先的套接字)則被保留下來繼續處理來自其他客戶的連接。 2、客戶端 基於socket的客戶端比服務器端簡單,同樣,客戶應用程序首先調用socket來創建一個未命名的套接字,然後將服務器的命名套接字作為一個地址來調用connect與服務器建立連接。 一旦連接建立,我們就可以像使用底層的文件描述符那樣用套接字來實現雙向數據的通信。 五、流式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、創建套接字隊列(監聽)——listen系統調用 該函數用來創建一個隊列來保存未處理的請求。成功時返回0,失敗時返回-1,其原型如下:
int listen(int socket, int backlog);
backlog用於指定隊列的長度,等待處理的進入連接的個數最多不能超過這個數字,否則往後的連接將被拒絕,導致客戶的連接請求失敗。調用後,程序一直會監聽這個IP端口,如果有連接請求,就把它加入到這個隊列中。 4、接受連接——accept系統調用 該系統調用用來等待客戶建立對該套接字的連接。accept系統調用只有當客 戶程序試圖連接到由socket參數指定的套接字上時才返回,也就是說,如果套接字隊列中沒有未處理的連接,accept將阻塞直到有客戶建立連接為止。 accept函數將創建一個新套接字來與該客戶進行通信,並且返回新套接字的描述符,新套接字的類型和服務器監聽套接字類型是一樣的。它的原型如下:
int accept(int socket, struct sockaddr *address, size_t *address_len);
address為連接客戶端的地址,參數address_len指定客戶結構的長度,如果客戶地址的長度超過這個值,它將會截斷。 5、請求連接——connect系統調用 該系統調用用來讓客戶程序通過在一個未命名套接字和服務器監聽套接字之間建立連接的方法來連接到服務器。它的原型如下:
int connect(int socket, const struct sockaddr *address, size_t address_len);
參數socket指定的套接字連接到參數addres指定的服務器套接字。成功時返回0,失敗時返回-1. 6、關閉socket——close系統調用 該系統調用用來終止服務器和客戶上的套接字連接,我們應該總是在連接的兩端(服務器和客戶)關閉套接字。 六、進程使用流式socket進行通信 下面用多個客戶程序和一個服務器程序來展示進程間如何利用套接字進行通信。 sockserver.c是一個服務器程序,它首先創建套接字,然後綁定一個 端口再監聽套接字,忽略子進程的停止消息等,然後它進入循環,一直循環檢查是否有客戶連接到服務器,如果有,則調用fork創建一個子進程來處理請求。利 用read系統調用來讀取客戶端發來的信息,利用write系統調用來向客戶端發送信息。這個服務器的工作非常簡單,就是把客戶發過來的字符+1,再發送 回給客戶。 sockclient.c是一個客戶程序,它同樣要先創建套接,然後連接到指定IP端口服務器,如果連接成功,就用write來發送信息給服務器,再用read獲取服務器處理後的信息,再輸出。 服務器sockserver.c的源代碼如下: 技術分享圖片
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int server_sockfd = -1;
    int client_sockfd = -1;
    int client_len = 0;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    //創建流套接字
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
   //設置服務器接收的連接地址和監聽的端口
    server_addr.sin_family = AF_INET;//指定網絡套接字
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//接受所有IP地址的連接
    server_addr.sin_port = htons(9736);//綁定到9736端口
    //綁定(命名)套接字
    bind(server_sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    //創建套接字隊列,監聽套接字
    listen(server_sockfd, 5);
    //忽略子進程停止或退出信號
    signal(SIGCHLD, SIG_IGN);
    
    while(1)
    {
        char ch = \0;
        client_len = sizeof(client_addr);
        printf("Server waiting\n");
        //接受連接,創建新的套接字
        client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_len);

        if(fork() == 0)
        {
            //子進程中,讀取客戶端發過來的信息,處理信息,再發送給客戶端
            read(client_sockfd, &ch, 1);
            sleep(5);
            ch++;
            write(client_sockfd, &ch, 1);
            close(client_sockfd);
            exit(0);
        }
        else
        {
            //父進程中,關閉套接字
            close(client_sockfd);
        }
    }
}
技術分享圖片

客戶sockclient.c的源代碼如下:

技術分享圖片
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int sockfd = -1;
    int len = 0;
    struct sockaddr_in address;
    int result;
    char ch = A;
    //創建流套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    //設置要連接的服務器的信息
    address.sin_family = AF_INET;//使用網絡套接字
    address.sin_addr.s_addr = inet_addr("127.0.0.1");//服務器地址
    address.sin_port = htons(9736);//服務器所監聽的端口
    len = sizeof(address);
    //連接到服務器
    result = connect(sockfd, (struct sockaddr*)&address, len);

    if(result == -1)
    {
        perror("ops:client\n");
        exit(1);
    }
    //發送請求給服務器
    write(sockfd, &ch, 1);
    //從服務器獲取數據
    read(sockfd, &ch, 1);
    printf("char form server = %c\n", ch);
    close(sockfd);
    exit(0);
}
技術分享圖片 運行結果如下: 技術分享圖片 技術分享圖片 在本例子中,我們啟動了一個服務器程序和三個客戶程序,從運行的結果來看,客 戶端發送給服務器程序的所有請求都得到了處理,即把A變成了B。對於服務器和客戶程序之間使用的read和write系統調用跟使用命名管道時阻塞的 read、write系統調用一樣。例如客戶程序調用read時,如果服務器程序沒有向指定的客戶程序的socket中寫入信息,則read調用會一直阻 塞。 七、流式套接字給我印象 給我的感覺是流式套接字很像命名管道,但是它卻可以使不在同一臺計算機而通過網絡連接的不同計算機上的進程進行通信,功能真是非常的強大。

Linux進程間通信——使用數據報套接字

前一篇文章,Linux進程間通信——使用流套接字介紹了一些有關socket(套接字)的一些基本內容,並講解了流套接字的使用,這篇文章將會給大家講講,數據報套接字的使用。 一、簡單回顧——什麽是數據報套接字 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的源代碼如下:

技術分享圖片
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>

int main(int agrc, char *argv[])
{
    struct sockaddr_in server_addr;
    int server_len = 0;
    int sockfd = -1;
    int result = 0;
    char c = A;
    //取第一個參數的第一個字符
    if(agrc > 1)
        c = argv[1][0];
    //創建數據報套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    //設置服務器IP端口
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_addr.sin_port = htons(9739);
    server_len = sizeof(server_addr);
    //向服務器發送數據
    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); 
}
技術分享圖片 運行結果如下: 先運行服務器程序,如下: 技術分享圖片 技術分享圖片 再運行三個客戶端:如下: 技術分享圖片 技術分享圖片 在本例子中,我們啟動了一個服務器程序和三個客戶程序,從運行的結果來看,客戶端發送給服務器程序的所有請求都得到了處理,即把大寫字母變成了小寫。recvfrom調用是阻塞的調用,即只有當接收到數據才會返回。 五、數據報套接字與流套接字的比較 1、從使用的便利和效率來講 我們可以看到使用數據報套接字的確是比使用流套接字簡單,而且快速。 因為使用流套接字的程序,客戶程序需要調用connect來創建一個到服務器 程序的連接,並需要維持這個連接,服務器程序也需要調用listen來創建一個隊列來保存未處理的請求,當有數據到達時,服務器也不需要調用accept 來接受連接並創建一個新socket描述符來處理請求。 再來看看使用數據報套接字的程序,服務器程序與客戶程序所使用的系統調用大致 相同,服務器程序只比客戶程序多使用了一個bind調用。基於數據報套接字的程序,只需要使用sendto調用來向指定IP端口的程序發送信息,使用 recvfrom調用從指向的IP端口接收信息即可。因為它並不需要建立一個連接,接受連接等,所以省去了很多的功夫。 2、從使用場合來講 我們知道流套接字是基於TCP/IP協議的,它是一種安全的協議,提供的是一 個有序、可靠、雙向字節流的連接,發送的數據可以確保不會丟失、重復或亂序到達,而且它還有一定的出錯後重新發送的機制。所以它比較適合用來發送信息量大 的數據文件,或對數據完整性要求較高的文件,如壓縮文件、視頻文件等 而數據報套接字是基於UDP/IP協議實現的。它對可以發送的數據的長度有限 制,數據報作為一個單獨的網絡消息被傳輸,它可能會丟失、復制或錯亂到達,UDP不是一個可靠的協議,但是它的速度比較高。所以它比較適合發送一些對實時 性要求較高,但是對安全性和完整性要求不太高的數據。如我們熟悉的聊天信息,即使有一點的丟失也不會造成理解上的大的問題。

套接字通信