溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Linux進(jìn)程間通信方式之socket使用實例

發(fā)布時間:2020-09-05 19:38:51 來源:腳本之家 閱讀:209 作者:shanzhizi 欄目:服務(wù)器

套接字是一種通信機(jī)制,憑借這種機(jī)制,客戶/服務(wù)器系統(tǒng)的開發(fā)工作既可以在本地單機(jī)上進(jìn)行,也可以跨網(wǎng)絡(luò)進(jìn)行。
套接字的特性有三個屬性確定,它們是:域(domain),類型(type),和協(xié)議(protocol)。套接字還用地址作為它的名字。地址的格式隨域(又被稱為協(xié)議族,protocol family)的不同而不同。每個協(xié)議族又可以使用一個或多個地址族定義地址格式。

1.套接字的域

域指定套接字通信中使用的網(wǎng)絡(luò)介質(zhì)。最常見的套接字域是AF_INET,它是指Internet網(wǎng)絡(luò),許多Linux局域網(wǎng)使用的都是該網(wǎng)絡(luò),當(dāng)然,因特網(wǎng)自身用的也是它。其底層的協(xié)議——網(wǎng)際協(xié)議(IP)只有一個地址族,它使用一種特定的方式來指定網(wǎng)絡(luò)中的計算機(jī),即IP地址。

在計算機(jī)系統(tǒng)內(nèi)部,端口通過分配一個唯一的16位的整數(shù)來表示,在系統(tǒng)外部,則需要通過IP地址和端口號的組合來確定。

2.套接字類型

流套接字(在某些方面類似域標(biāo)準(zhǔn)的輸入/輸出流)提供的是一個有序,可靠,雙向字節(jié)流的連接。

流套接字由類型SOCK_STREAM指定,它們是在AF_INET域中通過TCP/IP連接實現(xiàn)的。他們也是AF_UNIX域中常見的套接字類型。

數(shù)據(jù)包套接字

與流套接字相反,由類型SOCK_DGRAM指定的數(shù)據(jù)包套接字不建立和維持一個連接。它對可以發(fā)送的數(shù)據(jù)包的長度有限制。數(shù)據(jù)報作為一個單獨(dú)的網(wǎng)絡(luò)消息被傳輸,它可能會丟失,復(fù)制或亂序到達(dá)。

數(shù)據(jù)報套接字實在AF_INET域中通過UDP/IP連接實現(xiàn),它提供的是一種無需的不可靠服務(wù)。

3.套接字協(xié)議

只要底層的傳輸機(jī)制允許不止一個協(xié)議來提供要求的套接字類型,我們就可以為套接字選擇一個特定的協(xié)議。

先上一個代碼

服務(wù)端:

//s_unix.c 
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/un.h>  
#define UNIX_DOMAIN "/tmp/UNIX.domain" 
int main(void) 
{ 
  socklen_t clt_addr_len; 
  int listen_fd; 
  int com_fd; 
  int ret; 
  int i; 
  static char recv_buf[1024];  
  int len; 
  struct sockaddr_un clt_addr; 
  struct sockaddr_un srv_addr; 
  listen_fd=socket(PF_UNIX,SOCK_STREAM,0); 
  if(listen_fd<0) 
  { 
    perror("cannot create communication socket"); 
    return 1; 
  }  
  //set server addr_param 
  srv_addr.sun_family=AF_UNIX; 
  strncpy(srv_addr.sun_path,UNIX_DOMAIN,sizeof(srv_addr.sun_path)-1); 
  unlink(UNIX_DOMAIN); 
  //bind sockfd & addr 
  ret=bind(listen_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr)); 
  if(ret==-1) 
  { 
    perror("cannot bind server socket"); 
    close(listen_fd); 
    unlink(UNIX_DOMAIN); 
    return 1; 
  } 
  //listen sockfd  
  ret=listen(listen_fd,1); 
  if(ret==-1) 
  { 
    perror("cannot listen the client connect request"); 
    close(listen_fd); 
    unlink(UNIX_DOMAIN); 
    return 1; 
  } 
  //have connect request use accept 
  len=sizeof(clt_addr); 
  com_fd=accept(listen_fd,(struct sockaddr*)&clt_addr,&len); 
  if(com_fd<0) 
  { 
    perror("cannot accept client connect request"); 
    close(listen_fd); 
    unlink(UNIX_DOMAIN); 
    return 1; 
  } 
  //read and printf sent client info 
  printf("/n=====info=====/n"); 
  for(i=0;i<4;i++) 
  { 
    memset(recv_buf,0,1024); 
    int num=read(com_fd,recv_buf,sizeof(recv_buf)); 
    printf("Message from client (%d)) :%s/n",num,recv_buf);  
  } 
  close(com_fd); 
  close(listen_fd); 
  unlink(UNIX_DOMAIN); 
  return 0; 
} 

客戶端:

//c_unix.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIX_DOMAIN "/tmp/UNIX.domain"
int main(void)
{
  int connect_fd;
  int ret;
  char snd_buf[1024];
  int i;
  static struct sockaddr_un srv_addr;
//creat unix socket
  connect_fd=socket(PF_UNIX,SOCK_STREAM,0);
  if(connect_fd<0)
  {
    perror("cannot create communication socket");
    return 1;
  }  
  srv_addr.sun_family=AF_UNIX;
  strcpy(srv_addr.sun_path,UNIX_DOMAIN);
//connect server
  ret=connect(connect_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
  if(ret==-1)
  {
    perror("cannot connect to the server");
    close(connect_fd);
    return 1;
  }
  memset(snd_buf,0,1024);
  strcpy(snd_buf,"message from client");
//send info server
  for(i=0;i<4;i++)
    write(connect_fd,snd_buf,sizeof(snd_buf));
  close(connect_fd);
  return 0;
}

使用套接字除了可以實現(xiàn)網(wǎng)絡(luò)間不同主機(jī)間的通信外,還可以實現(xiàn)同一主機(jī)的不同進(jìn)程間的通信,且建立的通信是雙向的通信。socket進(jìn)程通信與網(wǎng)絡(luò)通信使用的是統(tǒng)一套接口,只是地址結(jié)構(gòu)與某些參數(shù)不同。

一、創(chuàng)建socket流程

(1)創(chuàng)建socket,類型為AF_LOCAL或AF_UNIX,表示用于進(jìn)程通信:

創(chuàng)建套接字需要使用 socket 系統(tǒng)調(diào)用,其原型如下:

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

其中,domain 參數(shù)指定協(xié)議族,對于本地套接字來說,其值須被置為 AF_UNIX 枚舉值;type 參數(shù)指定套接字類型,protocol 參數(shù)指定具體協(xié)議;type 參數(shù)可被設(shè)置為 SOCK_STREAM(流式套接字)或 SOCK_DGRAM(數(shù)據(jù)報式套接字),protocol 字段應(yīng)被設(shè)置為 0;其返回值為生成的套接字描述符。

對于本地套接字來說,流式套接字(SOCK_STREAM)是一個有順序的、可靠的雙向字節(jié)流,相當(dāng)于在本地進(jìn)程之間建立起一條數(shù)據(jù)通道;數(shù)據(jù)報式套接字(SOCK_DGRAM)相當(dāng)于單純的發(fā)送消息,在進(jìn)程通信過程中,理論上可能會有信息丟失、復(fù)制或者不按先后次序到達(dá)的情況,但由于其在本地通信,不通過外界網(wǎng)絡(luò),這些情況出現(xiàn)的概率很小。

二、命名socket。

SOCK_STREAM 式本地套接字的通信雙方均需要具有本地地址,其中服務(wù)器端的本地地址需要明確指定,指定方法是使用 struct sockaddr_un 類型的變量。

struct sockaddr_un {
  sa_family_t   sun_family;   /* AF_UNIX */
  char  sun_path[UNIX_PATH_MAX];    /* 路徑名 */
};

這里面有一個很關(guān)鍵的東西,socket進(jìn)程通信命名方式有兩種。一是普通的命名,socket會根據(jù)此命名創(chuàng)建一個同名的socket文件,客戶端連接的時候通過讀取該socket文件連接到socket服務(wù)端。這種方式的弊端是服務(wù)端必須對socket文件的路徑具備寫權(quán)限,客戶端必須知道socket文件路徑,且必須對該路徑有讀權(quán)限。

另外一種命名方式是抽象命名空間,這種方式不需要創(chuàng)建socket文件,只需要命名一個全局名字,即可讓客戶端根據(jù)此名字進(jìn)行連接。后者的實現(xiàn)過程與前者的差別是,后者在對地址結(jié)構(gòu)成員sun_path數(shù)組賦值的時候,必須把第一個字節(jié)置0,即sun_path[0] = 0,下面用代碼說明:

第一種方式:

//name the server socket 
	server_addr.sun_family = AF_UNIX;
	strcpy(server_addr.sun_path,"/tmp/UNIX.domain");
	server_len = sizeof(struct sockaddr_un);
	client_len = server_len;

第二種方式:

#define SERVER_NAME @socket_server 
//name the socket 
  server_addr.sun_family = AF_UNIX; 
  strcpy(server_addr.sun_path, SERVER_NAME); 
  server_addr.sun_path[0]=0; 
  //server_len = sizeof(server_addr); 
  server_len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);

其中,offsetof函數(shù)在#include <stddef.h>頭文件中定義。因第二種方式的首字節(jié)置0,我們可以在命名字符串SERVER_NAME前添加一個占位字符串,例如:

#define SERVER_NAME @socket_server  

前面的@符號就表示占位符,不算為實際名稱。

提示:客戶端連接服務(wù)器的時候,必須與服務(wù)端的命名方式相同,即如果服務(wù)端是普通命名方式,客戶端的地址也必須是普通命名方式;如果服務(wù)端是抽象命名方式,客戶端的地址也必須是抽象命名方式。

三、綁定

SOCK_STREAM 式本地套接字的通信雙方均需要具有本地地址,其中服務(wù)器端的本地地址需要明確指定,指定方法是使用 struct sockaddr_un 類型的變量,將相應(yīng)字段賦值,再將其綁定在創(chuàng)建的服務(wù)器套接字上,綁定要使用 bind 系統(tǒng)調(diào)用,其原形如下:

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

其中 socket表示服務(wù)器端的套接字描述符,address 表示需要綁定的本地地址,是一個 struct sockaddr_un 類型的變量,address_len 表示該本地地址的字節(jié)長度。實現(xiàn)服務(wù)器端地址指定功能的代碼如下(假設(shè)服務(wù)器端已經(jīng)通過上文所述的 socket 系統(tǒng)調(diào)用創(chuàng)建了套接字,server_sockfd 為其套接字描述符):

struct sockaddr_un server_address;
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "Server Socket");
bind(server_sockfd, (struct sockaddr*)&server_address, sizeof(server_address));

客戶端的本地地址不用顯式指定,只需能連接到服務(wù)器端即可,因此,客戶端的 struct sockaddr_un 類型變量需要根據(jù)服務(wù)器的設(shè)置情況來設(shè)置,代碼如下(假設(shè)客戶端已經(jīng)通過上文所述的 socket 系統(tǒng)調(diào)用創(chuàng)建了套接字,client_sockfd 為其套接字描述符):

struct sockaddr_un client_address;
client_address.sun_family = AF_UNIX;
strcpy(client_address.sun_path, "Server Socket");

四、監(jiān)聽

服務(wù)器端套接字創(chuàng)建完畢并賦予本地地址值(名稱,本例中為Server Socket)后,需要進(jìn)行監(jiān)聽,等待客戶端連接并處理請求,監(jiān)聽使用 listen 系統(tǒng)調(diào)用,接受客戶端連接使用accept系統(tǒng)調(diào)用,它們的原形如下:

int listen(int socket, int backlog);
int accept(int socket, struct sockaddr *address, size_t *address_len);

其中 socket 表示服務(wù)器端的套接字描述符;backlog 表示排隊連接隊列的長度(若有多個客戶端同時連接,則需要進(jìn)行排隊);address 表示當(dāng)前連接客戶端的本地地址,該參數(shù)為輸出參數(shù),是客戶端傳遞過來的關(guān)于自身的信息;address_len 表示當(dāng)前連接客戶端本地地址的字節(jié)長度,這個參數(shù)既是輸入?yún)?shù),又是輸出參數(shù)。實現(xiàn)監(jiān)聽、接受和處理的代碼如下:

#define MAX_CONNECTION_NUMBER 10
int server_client_length, server_client_sockfd;
struct sockaddr_un server_client_address;
listen(server_sockfd, MAX_CONNECTION_NUMBER);
while(1)
{
  // ...... (some process code)
  server_client_length?。健izeof(server_client_address);
  server_client_sockfd = accept(server_sockfd, (struct sockaddr*)&server_client_address, &server_client_length);
  // ...... (some process code)
}

這里使用死循環(huán)的原因是服務(wù)器是一個不斷提供服務(wù)的實體,它需要不間斷的進(jìn)行監(jiān)聽、接受并處理連接,本例中,每個連接只能進(jìn)行串行處理,即一個連接處理完后,才能進(jìn)行后續(xù)連接的處理。如果想要多個連接并發(fā)處理,則需要創(chuàng)建線程,將每個連接交給相應(yīng)的線程并發(fā)處理。

客戶端套接字創(chuàng)建完畢并賦予本地地址值后,需要連接到服務(wù)器端進(jìn)行通信,讓服務(wù)器端為其提供處理服務(wù)。對于 SOCK_STREAM 類型的流式套接字,需要客戶端與服務(wù)器之間進(jìn)行連接方可使用。連接要使用 connect 系統(tǒng)調(diào)用,其原形為

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

其中socket為客戶端的套接字描述符,address表示當(dāng)前客戶端的本地地址,是一個 struct sockaddr_un 類型的變量,address_len 表示本地地址的字節(jié)長度。實現(xiàn)連接的代碼如下:

connect(client_sockfd, (struct sockaddr*)&client_address, sizeof(client_address));

無論客戶端還是服務(wù)器,都要和對方進(jìn)行數(shù)據(jù)上的交互,這種交互也正是我們進(jìn)程通信的主題。一個進(jìn)程扮演客戶端的角色,另外一個進(jìn)程扮演服務(wù)器的角色,兩個進(jìn)程之間相互發(fā)送接收數(shù)據(jù),這就是基于本地套接字的進(jìn)程通信。發(fā)送和接收數(shù)據(jù)要使用 write 和 read 系統(tǒng)調(diào)用,它們的原形為:

int read(int socket, char *buffer, size_t len);
int write(int socket, char *buffer, size_t len);

其中 socket 為套接字描述符;len 為需要發(fā)送或需要接收的數(shù)據(jù)長度;對于 read 系統(tǒng)調(diào)用,buffer 是用來存放接收數(shù)據(jù)的緩沖區(qū),即接收來的數(shù)據(jù)存入其中,是一個輸出參數(shù);對于 write 系統(tǒng)調(diào)用,buffer 用來存放需要發(fā)送出去的數(shù)據(jù),即 buffer 內(nèi)的數(shù)據(jù)被發(fā)送出去,是一個輸入?yún)?shù);返回值為已經(jīng)發(fā)送或接收的數(shù)據(jù)長度。例如客戶端要發(fā)送一個 "Hello" 字符串給服務(wù)器,則代碼如下:

char buffer[10] = "Hello";
write(client_sockfd, buffer, strlen(buffer));

交互完成后,需要將連接斷開以節(jié)省資源,使用close系統(tǒng)調(diào)用,其原形為:

int close(int socket);

不多說了,直接使用,大家一定都會,呵呵!

上面所述的每個系統(tǒng)調(diào)用都有 -1 返回值,在調(diào)用不成功時,它們均會返回 -1,這個特性可以使得我們用 if - else 或異常處理語句來處理錯誤,為我們提供了很大的方便。

SOCK_DGRAM 數(shù)據(jù)報式本地套接字的應(yīng)用場合很少,因為流式套接字在本地的連接時間可以忽略,所以效率并沒有提高,而且發(fā)送接收都需要攜帶對方的本地地址,因此很少甚至幾乎不使用。

與本地套接字相對應(yīng)的是網(wǎng)絡(luò)套接字,可以用于在網(wǎng)絡(luò)上傳送數(shù)據(jù),換言之,可實現(xiàn)不同機(jī)器上的進(jìn)程通信過程。在 TCP/IP 協(xié)議中,IP 地址的首字節(jié)為 127 即代表本地,因此本地套接字通信可以使用 IP 地址為 127.x.x.x 的網(wǎng)絡(luò)套接字來實現(xiàn)。

總結(jié)

以上就是本文關(guān)于Linux進(jìn)程間通信方式之socket使用實例的全部內(nèi)容,希望對大家有所幫助。歡迎參閱:淺談Linux進(jìn)程間通信方式及優(yōu)缺點、Linux下文件的切分與合并的簡單方法介紹、Linux中在防火墻中開啟80端口方法示例等,感謝朋友們對本站的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI