Linux進程間通信——使用流套接字
前面說到的進程間的通信,所通信的進程都是在同一臺計算機上的,而使用socket進行通信的進程可以是同一臺計算機的進程,也是可以是通過網絡連接起來的不同計算機上的進程。通常我們使用socket進行網絡編程,這里將會簡單地講述如何使用socket進行簡單的網絡編程。
一、什么是socket
socket,即套接字是一種通信機制,憑借這種機制,客戶/服務器(即要進行通信的進程)系統(tǒng)的開發(fā)工作既可以在本地單機上進行,也可以跨網絡進行。也就是說它可以讓不在同一臺計算機但通過網絡連接計算機上的進程進行通信。也因為這樣,套接字明確地將客戶端和服務器區(qū)分開來。
二、套接字的屬性
套接字的特性由3個屬性確定,它們分別是:域、類型和協(xié)議。
1、套接字的域
它指定套接字通信中使用的網絡介質,最常見的套接字域是AF_INET,它指的是Internet網絡。當客戶使用套接字進行跨網絡的連接時,它就需要用到服務器計算機的IP地址和端口來指定聯(lián)網機器上的某個特定服務,所以在使用socket作為通信的終點,服務器應用程序必須在開始通信之前綁定一個端口,服務器在指定的端口等待客戶的連接。另一個域AF_UNIX表示UNIX文件系統(tǒng),它就是文件輸入/輸出,而它的地址就是文件名。
2、套接字類型
因特網提供了兩種通信機制:流(stream)和數據報(datagram),因而套接字的類型也就分為流套接字和數據報套接字。這里主要講流套接字。
流套接字由類型SOCK_STREAM指定,它們是在AF_INET域中通過TCP/IP連接實現(xiàn),同時也是AF_UNIX中常用的套接字類型。流套接字提供的是一個有序、可靠、雙向字節(jié)流的連接,因此發(fā)送的數據可以確保不會丟失、重復或亂序到達,而且它還有一定的出錯后重新發(fā)送的機制。
與流套接字相對的是由類型SOCK_DGRAM指定的數據報套接字,它不需要建立連接和維持一個連接,它們在AF_INET中通常是通過UDP/IP協(xié)議實現(xiàn)的。它對可以發(fā)送的數據的長度有限制,數據報作為一個單獨的網絡消息被傳輸,它可能會丟失、復制或錯亂到達,UDP不是一個可靠的協(xié)議,但是它的速度比較高,因為它并一需要總是要建立和維持一個連接。
3、套接字協(xié)議
只要底層的傳輸機制允許不止一個協(xié)議來提供要求的套接字類型,我們就可以為套接字選擇一個特定的協(xié)議。通常只需要使用默認值。
三、套接字地址
每個套接字都有其自己的地址格式,對于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進行進程通信的進程采用的客戶/服務器系統(tǒng)是如何工作的呢?
1、服務器端
首先服務器應用程序用系統(tǒng)調用socket來創(chuàng)建一個套接安,它是系統(tǒng)分配給該服務器進程的類似文件描述符的資源,它不能與其他的進程共享。
接下來,服務器進程會給套接字起個名字,我們使用系統(tǒng)調用bind來給套接字命名。然后服務器進程就開始等待客戶連接到這個套接字。
然后,系統(tǒng)調用listen來創(chuàng)建一個隊列并將其用于存放來自客戶的進入連接。
最后,服務器通過系統(tǒng)調用accept來接受客戶的連接。它會創(chuàng)建一個與原有的命名套接不同的新套接字,這個套接字只用于與這個特定客戶端進行通信,而命名套接字(即原先的套接字)則被保留下來繼續(xù)處理來自其他客戶的連接。
2、客戶端
基于socket的客戶端比服務器端簡單,同樣,客戶應用程序首先調用socket來創(chuàng)建一個未命名的套接字,然后將服務器的命名套接字作為一個地址來調用connect與服務器建立連接。
一旦連接建立,我們就可以像使用底層的文件描述符那樣用套接字來實現(xiàn)雙向數據的通信。
五、流式socket的接口及作用
socket的接口函數聲明在頭文件sys/types.h和sys/socket.h中。
1、創(chuàng)建套接字——socket系統(tǒng)調用
該函數用來創(chuàng)建一個套接字,并返回一個描述符,該描述符可以用來訪問該套接字,它的原型如下:
int socket(int domain, int type, int protocol);
函數中的三個參數分別對應前面所說的三個套接字屬性。protocol參數設置為0表示使用默認協(xié)議。
2、命名(綁定)套接字——bind系統(tǒng)調用
該函數把通過socket調用創(chuàng)建的套接字命名,從而讓它可以被其他進程使用。對于AF_UNIX,調用該函數后套接字就會關聯(lián)到一個文件系統(tǒng)路徑名,對于AF_INET,則會關聯(lián)到一個IP端口號。函數原型如下:
int bind( int socket, const struct sockaddr *address, size_t address_len);
成功時返回0,失敗時返回-1;
3、創(chuàng)建套接字隊列——listen系統(tǒng)調用
該函數用來創(chuàng)建一個隊列來保存未處理的請求。成功時返回0,失敗時返回-1,其原型如下:
int listen(int socket, int backlog);
backlog用于指定隊列的長度,等待處理的進入連接的個數最多不能超過這個數字,否則往后的連接將被拒絕,導致客戶的連接請求失敗。調用后,程序一直這個IP端口,如果有連接請求,就把它加入到這個隊列中。
4、接受連接——accept系統(tǒng)調用
該系統(tǒng)調用用來等待客戶建立對該套接字的連接。accept系統(tǒng)調用只有當客戶程序試圖連接到由socket參數指定的套接字上時才返回,也就是說,如果套接字隊列中沒有未處理的連接,accept將阻塞直到有客戶建立連接為止。accept函數將創(chuàng)建一個新套接字來與該客戶進行通信,并且返回新套接字的描述符,新套接字的類型和服務器套接字類型是一樣的。它的原型如下:
int accept(int socket, struct sockaddr *address, size_t *address_len);
address為連接客戶端的地址,參數address_len指定客戶結構的長度,如果客戶地址的長度超過這個值,它將會截斷。
5、請求連接——connect系統(tǒng)調用
該系統(tǒng)調用用來讓客戶程序通過在一個未命名套接字和服務器套接字之間建立連接的方法來連接到服務器。它的原型如下:
int connect(int socket, const struct sockaddr *address, size_t address_len);
參數socket指定的套接字連接到參數addres指定的服務器套接字。成功時返回0,失敗時返回-1.
6、關閉socket——close系統(tǒng)調用
該系統(tǒng)調用用來終止服務器和客戶上的套接字連接,我們應該總是在連接的兩端(服務器和客戶)關閉套接字。
六、進程使用流式socket進行通信
下面用多個客戶程序和一個服務器程序來展示進程間如何利用套接字進行通信。
sockserver.c是一個服務器程序,它首先創(chuàng)建套接字,然后綁定一個端口再套接字,忽略子進程的停止消息等,然后它進入循環(huán),一直循環(huán)檢查是否有客戶連接到服務器,如果有,則調用fork創(chuàng)建一個子進程來處理請求。利用read系統(tǒng)調用來讀取客戶端發(fā)來的信息,利用write系統(tǒng)調用來向客戶端發(fā)送信息。這個服務器的工作非常簡單,就是把客戶發(fā)過來的字符+1,再發(fā)送回給客戶。
sockclient.c是一個客戶程序,它同樣要先創(chuàng)建套接,然后連接到指定IP端口服務器,如果連接成功,就用write來發(fā)送信息給服務器,再用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; //創(chuàng)建流套接字 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)); //創(chuàng)建套接字隊列,套接字 listen(server_sockfd, 5); //忽略子進程停止或退出信號 signal(SIGCHLD, SIG_IGN); while(1) { char ch = '\0'; client_len = sizeof(client_addr); printf("Server waiting\n"); //接受連接,創(chuàng)建新的套接字 client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_len); if(fork() == 0) { //子進程中,讀取客戶端發(fā)過來的信息,處理信息,再發(fā)送給客戶端 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'; //創(chuàng)建流套接字 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); } //發(fā)送請求給服務器 write(sockfd, &ch, 1); //從服務器獲取數據 read(sockfd, &ch, 1); printf("char form server = %c\n", ch); close(sockfd); exit(0); }
運行結果如下:
在本例子中,我們啟動了一個服務器程序和三個客戶程序,從運行的結果來看,客戶端發(fā)送給服務器程序的所有請求都得到了處理,即把A變成了B。對于服務器和客戶程序之間使用的read和write系統(tǒng)調用跟使用命名管道時阻塞的read、write系統(tǒng)調用一樣。例如客戶程序調用read時,如果服務器程序沒有向指定的客戶程序的socket中寫入信息,則read調用會一直阻塞。
七、流式套接字給我印象
給我的感覺是流式套接字很像命名管道,但是它卻可以使不在同一臺計算機而通過網絡連接的不同計算機上的進程進行通信,功能真是非常的強大。
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
相關文章
crontab實現(xiàn)每隔多少天執(zhí)行一次腳本的兩種方法
相信大家在工作中,經常會遇到定時執(zhí)行腳本的功能要求,或某個命令的情況。那么下面這篇文章主要給大家介紹了關于crontab實現(xiàn)每隔多少天執(zhí)行一次腳本的相關資料,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-08-08通過shell腳本循環(huán)進入目錄執(zhí)行命令的方法
今天小編就為大家分享一篇通過shell腳本循環(huán)進入目錄執(zhí)行命令的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-06-06Shell腳本入門之編寫格式與執(zhí)行方式的實現(xiàn)
這篇文章主要介紹了Shell腳本入門之編寫格式與執(zhí)行方式的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12Linux Shell 數組的創(chuàng)建及使用技巧
這篇文章主要介紹了Linux Shell 數組的創(chuàng)建及使用技巧,本文講解了數組定義、數組讀取與賦值以及特殊使用,需要的朋友可以參考下2015-07-07Linux?du命令實現(xiàn)根據文件或者文件夾大小排序輸出
Linux是一個強大的操作系統(tǒng),廣泛用于服務器和個人計算機,本文主要來和大家聊聊如何利用du命令實現(xiàn)根據文件或者文件夾大小排序輸出,感興趣的可以了解下2023-09-09