近期在作的項目中,涉及到了進程間數據傳輸,系統的本來實現是經過管道,可是原有的實現中兩個進程是在同一臺機器,並且兩個進程的關係爲父子關係,而咱們要作的是將其中一個進程移植到服務器上,所以兩個進程要分開,因此管道必然是不可行的方案,而對於其它的進程通訊方式,FIFO,消息隊列,信號量和共享內存,顯然也是不可行的。所以採起了經過socket的通訊方式,即網絡套接字,用來作數據的傳輸。接下來,將對本身對socket的學習一個整理,socket是什麼?socket的建立,綁定,發送,接收消息過程進行分析,同時附帶一個簡單的代碼實例。程序員
套接字是通訊端點的抽象,其英文socket,即爲插座,孔的意思。若是兩個機子要通訊,中間要經過一條線,這條線的兩端要鏈接通訊的雙方,這條線在每一臺機子上的接入點則爲socket,即爲插孔,因此在通訊前,咱們在通訊的兩端必需要創建好這個插孔,同時爲了保證通訊的正確,端和端之間的插孔必需要一一對應,這樣兩端即可以正確的進行通訊了,而這個插孔對應到咱們實際的操做系統中,就是socket文件,咱們再建立它以後,就會獲得一個操做系統返回的對於該文件的描述符,而後應用程序能夠經過使用套接字描述符訪問套接字,向其寫入輸入,讀出數據。
站在更貼近系統的層級去看,兩個機器間的通訊方式,無非是要經過運輸層的TCP/UDP,網絡層IP,所以socket本質是編程接口(API),對TCP/UDP/IP的封裝,TCP/UDP/IP也要提供可供程序員作網絡開發所用的接口,這就是Socket編程接口。
Socket的建立編程
#include <sys/socket.h> int socket (int domain, int type, int protocol);
int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
這樣,咱們便建立了一個socket,對於socket接收的參數都有什麼意義呢?從上面,咱們能夠知道socket是對於底層網絡通訊的一個封裝,而對於底層的網絡通訊也是具有多種類型的。而這些參數則是經過組合來表示各種通訊的特徵,從而創建正確的套接字。服務器
type:套接字的類型,進一步肯定通訊特徵。
protocol:表示爲給定域和套接字類型選擇默認協議,當對同一域和套接字類型支持多個協議時,能夠經過該字段來選擇一個特定協議,一般默認爲0.上面設置的socket類型,在執行的時候也會有默認的協議類型提供,好比SOCK_STREAM就TCP協議。
從上面的socket類型中,咱們看到有SOCK_RAW該種類型,SOCK_RAW套接字提供一個數據報接口。經過這個咱們能夠直接訪問下面的網絡層,繞過TCP/UDP,所以咱們能夠進行制定本身的傳輸層協議。
至此,咱們的socket已經建立出來了,當咱們再也不使用的時候,咱們能夠調用close函數來將其關閉,釋放該文件描述符,這樣即可以獲得從新的使用。
套接字通訊是雙向的,可是,咱們能夠採用shutdown函數來禁止一個套接字的I/O.網絡
#include<sys/socket.h> int shutdown(int sockfd, int how);
how能夠用來指定讀端口或者是寫端口,這樣咱們即可以關閉掉讀端或者寫端。架構
我麼已經建立好了Socket,接下來要作的就是經過socket進行通訊了,在兩個進程間進行通訊,首先,咱們要找到這些進程,找到進程,也就是可以有這些進程的惟一標示,有了這些標示,咱們才能夠肯定通訊的雙方,而後進行數據的傳輸,對於一個通訊進程的標示,所採起的方式是經過一個網絡地址,也就是IP地址,戰找到咱們要通訊的主機,而後經過端口號,找到相應的服務。網絡地址+端口號惟一標示了一個咱們要通訊的目標進程。框架
字節序是處理器架構的特性,用來指示像整數這種數據類型的內部如何排序,大端和小端,所以若是通訊雙方的處理器架構不一樣,則會致使字節序的不一致的問題出現。最底層的網絡協議指定了字節序,大端字節序,可是應用程序在處理數據時,則會遇到字節序不一致的問題。對此,系統提供了進行處理器字節序和網絡字節序之間實施轉換的函數。dom
#include <arpa/inet.h> uint32_t htonl(uint32_t hostint32)//主機字節轉化爲網絡字節序 uint16_t htons(uint16_t hostint16) uint32_t ntohl(uint32_t netint32)//網絡字節序轉化爲主機字節序 unint16_t ntohs(uint16_t netint16)
上面,咱們已經談到如何表示一個要通訊的進程,須要一個網絡地址和端口,而在系統中如何具體的標示這一特徵呢?根據以前socket的建立,咱們知道不一樣socket對應了不一樣的通訊特徵,而對於不一樣的通訊特徵,其地址表示上也有一些差異。
這裏咱們只看一下IPV4因特網域地址的表示結構。
struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr;}
sin_family: 通訊的的域,這裏爲AF_INET
sin_port:通訊的端口
sin_addr:網絡地址socket
咱們套接字已經建立好了,地址結構也已經瞭解了,接下來就是要將套接字和地址進行關聯,關聯的方法則是經過bind
函數。函數
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
建立地址學習
struct sockaddr_in server_sockaddr;server_sockaddr.sin_family = AF_INET;server_sockaddr.sin_port = htons(PORT);server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); socklen_t server_len = sizeof(server_sockaddr); bind(server_sockfd, (struct sockaddr*)&server_sockaddr, server_len)
經過bind函數,咱們實現了socket和地址的綁定。
創建鏈接
socket創建好了,地址也綁定好了,這個時候,咱們就能夠進行鏈接了,要有一方進行鏈接的創建,經過調用connect
函數。
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
sockfd:這個就是本地端socket描述符,若是咱們沒有賦值,系統會默認提供一個值。只有當服務器開啓,並正常運行,咱們的鏈接纔可以正常創建。
如何讓socket接收鏈接請求呢?在另外一端,咱們調用listen
方法來接收鏈接請求。
#include <sys/socket.h>int listen(int sockfd, int backlog);
#include <sys/socket.h> int accept(int sockfd, struct sockaddr *restric addr, socklen_t *restrict len);
調用accept函數的返回值是套接字文件描述符,該描述符鏈接到調用connect的客戶端。
一旦服務器調用了listen,所用的套接字就能接收鏈接請求,使用accept函數得到鏈接請求並創建鏈接。
使用accept函數得到鏈接請求並創建鏈接。
int accept(int sockfd, struct sockaddr *restrict addr, socklent_t *restrict len);
當調用accept函數會產生一個新的套接字,這個新的套接字和原始套接字有相同的套接類型。這個時候,咱們能夠傳入一個指向socket的指針和其大小,設置以後,調用了accept就會將客戶端的地址進行緩衝。
數據傳輸
鏈接已經創建好了,因爲socket自己都是文件描述符,所以接下來就能夠調用所read和write來經過套接字通訊。
對於面向鏈接的數據傳輸,咱們須要的兩個函數是send和recv。
#include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags)
對於不一樣的socket類型,系統提供了不一樣的發送方法。
#include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags)
具體參數和send相似。
socket選項設置
對於Socket,系統提供了更具體細緻化的一些配置選項,經過這些配置選項,咱們能夠進行進一步具體的配置。
#include <sys/socket.h> int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
sockfd:咱們要進行配置的socket
level:根據咱們選用的協議,配置相應的協議編號
option:選項即爲上表
最後參數是用來存放返回值
實現demo實例
server
#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #define PORT 22468 #define KEY 123 #define SIZE 1024 int main() { char buf[100]; memset(buf,0,100); int server_sockfd,client_sockfd; socklen_t server_len,client_len; struct sockaddr_in server_sockaddr,client_sockaddr; /*create a socket.type is AF_INET,sock_stream*/ server_sockfd = socket(AF_INET,SOCK_STREAM,0); server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); server_len = sizeof(server_sockaddr); int on; setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR,&on,sizeof(on)); /*bind a socket or rename a sockt*/ if(bind(server_sockfd, (struct sockaddr*)&server_sockaddr, server_len)==-1){ printf("bind error"); exit(1); } if(listen(server_sockfd, 5)==-1){ printf("listen error"); exit(1); } client_len = sizeof(client_sockaddr); pid_t ppid,pid; while(1) { if((client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_sockaddr, &client_len)) == -1){ printf("connect error"); exit(1); } else { printf("create connection successfully\n"); int error = send(client_sockfd, "You have conected the server", strlen("You have conected the server"), 0); printf("%d\n", error); } } return 0; }
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> #include <arpa/inet.h> #define SERVER_PORT 22468 #define MAXDATASIZE 100 #define SERVER_IP "Your IP" int main() { int sockfd, numbytes; char buf[MAXDATASIZE]; struct sockaddr_in server_addr; printf("\n======================client initialization======================\n"); if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); bzero(&(server_addr.sin_zero),sizeof(server_addr.sin_zero)); if (connect(sockfd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) == -1){ perror("connect error"); exit(1); } while(1) { bzero(buf,MAXDATASIZE); printf("\nBegin receive...\n"); if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1){ perror("recv"); exit(1); } else if (numbytes > 0) { int len, bytes_sent; buf[numbytes] = '\0'; printf("Received: %s\n",buf); printf("Send:"); char msg[100]; scanf("%s",msg); len = strlen(msg); //sent to the server if(send(sockfd, msg,len,0) == -1){ perror("send error"); } } else { printf("soket end!\n"); break; } } close(sockfd); return 0; }
最近也在看的一個RPC框架,thrift,定義好咱們的接口文件,而後能夠幫助咱們生成兩端的樁文件,並且實現原理上,也不過是經過底層的socket通訊作了包裝,執行相應的調用。socket通訊在大三的OS課上寫過,本文主要目的記錄本次學習,對於socket知識進行了一個回顧。