Linux下Socket通訊(TCP實現)

近期在作的項目中,涉及到了進程間數據傳輸,系統的本來實現是經過管道,可是原有的實現中兩個進程是在同一臺機器,並且兩個進程的關係爲父子關係,而咱們要作的是將其中一個進程移植到服務器上,所以兩個進程要分開,因此管道必然是不可行的方案,而對於其它的進程通訊方式,FIFO,消息隊列,信號量和共享內存,顯然也是不可行的。所以採起了經過socket的通訊方式,即網絡套接字,用來作數據的傳輸。接下來,將對本身對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);

建立一個socket

int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

這樣,咱們便建立了一個socket,對於socket接收的參數都有什麼意義呢?從上面,咱們能夠知道socket是對於底層網絡通訊的一個封裝,而對於底層的網絡通訊也是具有多種類型的。而這些參數則是經過組合來表示各種通訊的特徵,從而創建正確的套接字。服務器

  • domain:通訊的特性,每一個域有本身的地址表示格式,AF打頭,表示地址族(Address family)

domain
type:套接字的類型,進一步肯定通訊特徵。
type
protocol:表示爲給定域和套接字類型選擇默認協議,當對同一域和套接字類型支持多個協議時,能夠經過該字段來選擇一個特定協議,一般默認爲0.上面設置的socket類型,在執行的時候也會有默認的協議類型提供,好比SOCK_STREAM就TCP協議。
protocol
從上面的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);
  • sockfd:綁定了地址的socket文件描述符。
  • backlog:服務器負載,提示系統進程所要入隊的未完成請求數量。
    經過listen咱們獲得了鏈接請求,接下來,就是創建鏈接,經過函數accept
#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)
  • sockfd:accept返回的socket文件描述符。
  • buf:發送數據,
  • bytes:發送數據大小
  • 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);

option
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;
}
  • client
#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知識進行了一個回顧。

相關文章
相關標籤/搜索