本地socket unix domain socket

socket API原本是爲網絡通訊設計的,但後來在socket的框架上發展出一種IPC機制,就是UNIXDomain Socket。雖然網絡socket也可用於同一臺主機的進程間通訊(通過loopback地址127.0.0.1),但是UNIX Domain Socket用於IPC更有效率:不需要經過網絡協議棧,不需要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另一個進程。這是因爲,IPC機制本質上是可靠的通訊,而網絡協議是爲不可靠的通訊設計的。UNIX Domain Socket也提供面向流和麪向數據包兩種API接口,類似於TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不會丟失也不會順序錯亂。

UNIX Domain Socket是全雙工的,API接口語義豐富,相比其它IPC機制有明顯的優越性,目前已成爲使用最廣泛的IPC機制,比如X Window服務器和GUI程序之間就是通過UNIX Domain Socket通訊的。

使用UNIX Domain Socket的過程和網絡socket十分相似,也要先調用socket()創建一個socket文件描述符,address family指定爲AF_UNIX,type可以選擇SOCK_DGRAM或SOCK_STREAM,protocol參數仍然指定爲0即可。

UNIX Domain Socket與網絡socket編程最明顯的不同在於地址格式不同,用結構體sockaddr_un表示,網絡編程的socket地址是IP地址加端口號,而UNIX Domain Socket的地址是一個socket類型的文件在文件系統中的路徑,這個socket文件由bind()調用創建,如果調用bind()時該文件已存在,則bind()錯誤返回。


今天我們介紹如何編寫Linux下的TCP程序,關於UDP程序可以參考這裏:

http://blog.csdn.net/htttw/article/details/7519971

本文絕大部分是參考《Linux程序設計(第4版)》的第15章套接字


服務器端的步驟如下:

1. socket:      建立一個socket

2. bind:          將這個socket綁定在某個文件上(AF_UNIX)或某個端口上(AF_INET),我們會分別介紹這兩種。

3. listen:        開始監聽

4. accept:      如果監聽到客戶端連接,則調用accept接收這個連接並同時新建一個socket來和客戶進行通信

5. read/write:讀取或發送數據到客戶端

6. close:        通信完成後關閉socket



客戶端的步驟如下:

1. socket:      建立一個socket

2. connect:   主動連接服務器端的某個文件(AF_UNIX)或某個端口(AF_INET)

3. read/write:如果服務器同意連接(accept),則讀取或發送數據到服務器端

4. close:        通信完成後關閉socket




上面是整個流程,我們先給出一個例子,具體分析會在之後給出。例子實現的功能是客戶端發送一個字符到服務器,服務器將這個字符+1後送回客戶端,客戶端再把它打印出來

Makefile:

[plain] view plaincopy
  1. all: tcp_client.c tcp_server.c  
  2.     gcc -g -Wall -o tcp_client tcp_client.c  
  3.     gcc -g -Wall -o tcp_server tcp_server.c  
  4.   
  5. clean:  
  6.     rm -rf *.o tcp_client tcp_server  


tcp_server.c:

  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. #include <sys/un.h>  
  4. #include <unistd.h>  
  5. #include <stdlib.h>  
  6. #include <stdio.h>  
  7.   
  8. int main()  
  9. {  
  10.   /* delete the socket file */  
  11.   unlink("server_socket");  
  12.     
  13.   /* create a socket */  
  14.   int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  
  15.     
  16.   struct sockaddr_un server_addr;  
  17.   server_addr.sun_family = AF_UNIX;  
  18.   strcpy(server_addr.sun_path, "server_socket");  
  19.     
  20.   /* bind with the local file */  
  21.   bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
  22.     
  23.   /* listen */  
  24.   listen(server_sockfd, 5);  
  25.     
  26.   char ch;  
  27.   int client_sockfd;  
  28.   struct sockaddr_un client_addr;  
  29.   socklen_t len = sizeof(client_addr);  
  30.   while(1)  
  31.   {  
  32.     printf("server waiting:\n");  
  33.       
  34.     /* accept a connection */  
  35.     client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);  
  36.       
  37.     /* exchange data */  
  38.     read(client_sockfd, &ch, 1);  
  39.     printf("get char from client: %c\n", ch);  
  40.     ++ch;  
  41.     write(client_sockfd, &ch, 1);  
  42.       
  43.     /* close the socket */  
  44.     close(client_sockfd);  
  45.   }  
  46.     
  47.   return 0;  
  48. }  


tcp_client.c:

  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. #include <sys/un.h>  
  4. #include <unistd.h>  
  5. #include <stdlib.h>  
  6. #include <stdio.h>  
  7.   
  8. int main()  
  9. {  
  10.   /* create a socket */  
  11.   int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  
  12.     
  13.   struct sockaddr_un address;  
  14.   address.sun_family = AF_UNIX;  
  15.   strcpy(address.sun_path, "server_socket");  
  16.     
  17.   /* connect to the server */  
  18.   int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));  
  19.   if(result == -1)  
  20.   {  
  21.     perror("connect failed: ");  
  22.     exit(1);  
  23.   }  
  24.     
  25.   /* exchange data */  
  26.   char ch = 'A';  
  27.   write(sockfd, &ch, 1);  
  28.   read(sockfd, &ch, 1);  
  29.   printf("get char from server: %c\n", ch);  
  30.     
  31.   /* close the socket */  
  32.   close(sockfd);  
  33.     
  34.   return 0;  
  35. }  




如果我們首先運行tcp_client,會提示沒有這個文件:


因爲我們是以AF_UNIX方式進行通信的,這種方式是通過文件來將服務器和客戶端連接起來的,因此我們應該先運行tcp_server,創建這個文件,默認情況下,這個文件會創建在當前目錄下,並且第一個s表示它是一個socket文件



程序運行的結果如下圖:




下面我們詳細講解:

1.

我們調用socket函數創建一個socket:

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

domain:指定socket所屬的域,常用的是AF_UNIX或AF_INET

AF_UNIX表示以文件方式創建socket,AF_INET表示以端口方式創建socket(我們會在後面詳細講解AF_INET)

type:指定socket的類型,可以是SOCK_STREAM或SOCK_DGRAM

SOCK_STREAM表示創建一個有序的,可靠的,面向連接的socket,因此如果我們要使用TCP,就應該指定爲SOCK_STREAM

SOCK_DGRAM表示創建一個不可靠的,無連接的socket,因此如果我們要使用UDP,就應該指定爲SOCK_DGRAM

protocol:指定socket的協議類型,我們一般指定爲0表示由第一第二兩個參數自動選擇。

socket()函數返回新創建的socket,出錯則返回-1


2.

地址格式:

常用的有兩種socket域:AF_UNIX或AF_INET,因此就有兩種地址格式:sockaddr_un和sockaddr_in,分別定義如下:

  1. struct sockaddr_un  
  2. {  
  3.   sa_family_t sun_family;  /* AF_UNIX */  
  4.   char sun_path[];         /* pathname */  
  5. }  
  6.   
  7.   
  8. struct sockaddr_in  
  9. {  
  10.   short int sin_family;          /* AF_INET */  
  11.   unsigned short int sin_port;   /* port number */  
  12.   struct in_addr sin_addr;       /* internet address */  
  13. }  

其中in_addr正是用來描述一個ip地址的:

  1. struct in_addr  
  2. {  
  3.   unsigned long int s_addr;  
  4. }  


從上面的定義我們可以看出,sun_path存放socket的本地文件名,sin_addr存放socket的ip地址,sin_port存放socket的端口號


3.

創建完一個socket後,我們需要使用bind將其綁定:

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

如果我們使用AF_UNIX來創建socket,相應的地址格式是sockaddr_un,而如果我們使用AF_INET來創建socket,相應的地址格式是sockaddr_in,因此我們需要將其強制轉換爲sockaddr這一通用的地址格式類型,而sockaddr_un中的sun_family和sockaddr_in中的sin_family分別說明了它的地址格式類型,因此bind()函數就知道它的真實的地址格式。第三個參數address_len則指明瞭真實的地址格式的長度。

bind()函數正確返回0,出錯返回-1



4.

接下來我們需要開始監聽了:

int listen(int socket, int backlog)

backlog:等待連接的最大個數,如果超過了這個數值,則後續的請求連接將被拒絕

listen()函數正確返回0,出錯返回-1



5.

接受連接:

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

同樣,第二個參數也是一個通用地址格式類型,這意味着我們需要進行強制類型轉化

這裏需要注意的是,address是一個傳出參數,它保存着接受連接的客戶端的地址,如果我們不需要,將address置爲NULL即可。

address_len:我們期望的地址結構的長度,注意,這是一個傳入和傳出參數,傳入時指定我們期望的地址結構的長度,如果多於這個值,則會被截斷,而當accept()函數返回時,address_len會被設置爲客戶端連接的地址結構的實際長度。

另外如果沒有客戶端連接時,accept()函數會阻塞

accept()函數成功時返回新創建的socket描述符,出錯時返回-1




6.

客戶端通過connect()函數與服務器連接:

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

對於第二個參數,我們同樣需要強制類型轉換

address_len指明瞭地址結構的長度

connect()函數成功時返回0,出錯時返回-1



7.

雙方都建立連接後,就可以使用常規的read/write函數來傳遞數據了



8.

通信完成後,我們需要關閉socket:

int close(int fd)

close是一個通用函數(和read,write一樣),不僅可以關閉文件描述符,還可以關閉socket描述符

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