Socket編程

一 基本概念

socket, 又稱爲"套接字"或者"插座". 是操做系統提供的一種進程間通訊機制.目前大多用於不一樣網絡設備之間的通訊. socket 位於應用層與傳輸層之間, 經過傳遞給 socket 不一樣的參數, socket 最終選擇不同的協議(TCP/UDP等), 也就是說 socket 其實傳輸層協議簇的軟件抽象.html

圖片來自於網絡linux


1.1 流程概述

在網絡應用中, 通訊的兩個進程之間主要用的是客戶端/服務器 (C/S)模式, 即客戶向服務器發出服務請求,服務器接收到請求後,提供相應的服務. socket 最開始設計於 Unix, Unix/Linux 設計哲學是 "一切皆文件", 所以,咱們能夠像文件操做(open/read/write/close)同樣來操做 socket.git

下圖展現展現 C/S 之間如何使用 socketgithub

對於 TCP 服務端來講:編程

  • 建立 socket, 建立一個通訊斷點描述符, 但在進程中爲未打開狀態;
  • 綁定(bind) socket, 將 socket 綁定到某個地址以及端口上;
  • 監聽(listen) socket, 用於表示該 socket 爲一個被動連接(passive socket), 能夠理解爲 服務器的socket,須要客戶端來主動鏈接了;
  • 接收客戶端的請求(accept), 服務端經過該方法接收客戶端的鏈接請求;
  • 讀(read)/寫(write)
  • 關閉 socket

對於客戶端來講就比較的簡單: 建立一個socket, 而後鏈接服務器(connect), 完成對應的數據操做(讀寫), 完成後關閉(close);swift

二 socket 相關接口

2.1 建立 socket()

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

socket 建立返回一個 socket 描述符, 跟文件描述符相似,後面的讀寫都須要依賴於這個描述符.服務器

2.1.1 參數-協議族(domain)

協議族決定了使用的 socket 地址類型;網絡

  • Unix 域 socket: AF_UNIXAF_LOCAL, 使用地址類型爲: struct sockaddr_un
struct sockaddr_un {
   sa_family_t sun_family;               /* AF_UNIX */
   char        sun_path[108];            /* Pathname */
};
  • AF_INET, ipv4, 使用地址類型爲: struct sockaddr_in
struct sockaddr_in {
   sa_family_t    sin_family; /* address family: AF_INET */
   in_port_t      sin_port;   /* port in network byte order */
   struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
   uint32_t       s_addr;     /* address in network byte order */
};
  • AF_INET6, ipv6, 使用地址類型爲: sockaddr_in6
struct sockaddr_in6 {
   sa_family_t     sin6_family;   /* AF_INET6 */
   in_port_t       sin6_port;     /* port number */
   uint32_t        sin6_flowinfo; /* IPv6 flow information */
   struct in6_addr sin6_addr;     /* IPv6 address */
   uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */
};

struct in6_addr {
   unsigned char   s6_addr[16];   /* IPv6 address */
};
  • 其餘類型(AF_X25 / AF_AX25 / AF_NETLINK 等),其餘類型目前尚未涉及到,不作過多討論,感興趣能夠訪問手冊進行研究;

2.1.2 參數-類型(type)

表示 socket 通訊的類型dom

  • SOCK_STREAM: 可靠的面向流服務或流套接字 (TCP)
  • SOCK_DGRAM: 數據報文服務或者數據報文套接字 (UDP)
  • SOCK_SEQPACKET:可靠的連續數據包服務
  • SOCK_RAW: 網絡層之上自行指定運輸層協議頭,即原始套接字

2.1.3 參數-協議類型(protocol)

指定實際使用的傳輸協議, 傳 0 表示根據前面的 domain 和 type 選擇協議;socket

2.2 其餘函數略過

其餘的函數沒有什麼特別須要注意的地方,直接參考下面例子以及手冊就能看懂


三 示例程序-本地進程間通訊

當給 socket() 的協議族傳入(AF_UNIXAF_LOCAL)時,可表示Unix域socket, 用於本地進程間通訊.

其中地址類型中的 sun_path是一個值得注意的地方,根據文檔所描述,有三種類型

+ pathname: 文件類型,這種文件須要服務端與客戶端對文件都有操做權限, 程序執行過程當中,能夠看到對應的文件.
+ unnamed
+ abstract: 抽象類型, 這種類型與 pathname 區別在於,給 sun_path 的第一個值爲 \0;

3.1 客戶端程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>

static const char *SERVERNAME = "@servername";
int main(int argc, char const *argv[])
{
    int sockfd;
    struct sockaddr_un serverAddr;
    socklen_t len;
    char buffer[1024];

    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

    if (sockfd < 0)
    {
        perror("init fail");
        exit(1);
    }

    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sun_family = AF_UNIX;
    strncpy(serverAddr.sun_path, SERVERNAME, sizeof(serverAddr.sun_path));  

    serverAddr.sun_path[0] = '\0';
    len = offsetof(struct sockaddr_un, sun_path) + sizeof(SERVERNAME);

    if (connect(sockfd, (struct sockaddr *)&serverAddr, len) < 0)
    {
        perror("connect fail");
        exit(1);
    }

    while (fgets(buffer, sizeof(buffer), stdin) != NULL)
    {
        write(sockfd, buffer, strlen(buffer));
    }

    close(sockfd);

    return 0;
}

3.2 服務端程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>

static const char *SERVERNAME = "@servername";

int main(int argc, char const *argv[])
{
    int sockfd;
    struct sockaddr_un serverAddr;
    struct sockaddr_un clientAddr;
    socklen_t clientLen;
    socklen_t serverLen;

    char buffer[1024];
    int connfd;

    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    {
        perror("init fail");
        exit(1);
    }
    
    // 2. 設置對應地址,而後進行 connect
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sun_family = AF_UNIX;
    strncpy(serverAddr.sun_path, SERVERNAME, sizeof(serverAddr.sun_path));  

    // abstract & calculate length
    serverAddr.sun_path[0] = '\0';
    serverLen = offsetof(struct sockaddr_un, sun_path) + sizeof(SERVERNAME);

    if (bind(sockfd, (struct sockaddr *)&serverAddr, serverLen) < 0)
    {
        perror("bind fail");
        exit(1);
    }

    if (listen(sockfd, 20) < 0)
    {
        perror("listen fail");
        exit(1);
    }

    while (1)
    {
        // 阻塞到有客戶端連接
        if ((connfd = accept(sockfd, (struct sockaddr *)&clientAddr, &clientLen))  < 0) 
        {
            printf("accept fail \r\n");
            continue;
        }
        printf("accept success \r\n");

        while(1) 
        {  
            memset(buffer, 0, sizeof(buffer));
            
            int n = read(connfd, buffer, sizeof(buffer));  
            if (n < 0) {  
                perror("read error");  
                break;  
            } else if(n == 0) {  
                printf("EOF\n");  
                break;  
            }  
            
            printf("received: %s", buffer);  
        }  
        close(connfd);
    }

    close(sockfd);

    return 0;
}

四 實例程序-IPV4(6)網絡通訊

4.1 客戶端程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>


int main(int argc, char const *argv[])
{
    // ipv4
    int sockfd;
    struct sockaddr_in addr_in;
    char buffer[1000];
    
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("init socket fail");
        exit(1);
    }

    bzero(&addr_in, sizeof(addr_in));    
    addr_in.sin_family = AF_INET;
    addr_in.sin_port = htons(9898);
    addr_in.sin_addr.s_addr = inet_addr("103.101.153.8");

    if ((connect(sockfd, (struct sockaddr *)&addr_in, sizeof(addr_in)))<0)
    {
        perror("connect fail");
        exit(1);
    }

    while (fgets(buffer, sizeof(buffer), stdin))
    {
        write(sockfd, buffer, strlen(buffer));
    }

    close(sockfd);

    return 0;
}

4.2 服務端程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h> 


int main(int argc, char const *argv[])
{
    int serfd, clifd;
    struct sockaddr_in seraddr_in, cliaddr_in;
    socklen_t clilen;
    char buffer[1000];

    if ((serfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("init socket fail");
        exit(1);
    }

    bzero(&seraddr_in, sizeof(seraddr_in));
    seraddr_in.sin_family = AF_INET;
    seraddr_in.sin_port = htons(9898);
    seraddr_in.sin_addr.s_addr = inet_addr("103.101.153.8");

    if (bind(serfd, (struct sockaddr *)&seraddr_in, sizeof(seraddr_in)) < 0)
    {
        perror("bind fail");
        exit(1);
    }

    if (listen(serfd, 20) < 0)
    {
        perror("listen fail");
        exit(1);
    }

    while (1)
    {
        if ((clifd = accept(serfd, (struct sockaddr *)&cliaddr_in, &clilen)) < 0)
        {
            perror("accept fail");
            continue;
        }

        while (1)
        {
            memset(buffer, 0, sizeof(buffer));
            int len = read(clifd, buffer, sizeof(buffer));

            if (len < 0 )
            {
                perror("read fail");
                break;
            }

            if (len == 0)
            {
                perror("EOF");
                break;
            }

            printf("receive: %s", buffer);
        }

        close(clifd);
    }

    close(serfd);

    return 0;
}

對比本地與網絡通訊,就是選擇的協議族以及對應的地址不同而已.

參考連接:

相關文章
相關標籤/搜索