2019年1月11日 星期五

[C] Socket程式 (Server端)

應用程式可以透過Socket來進行連線與傳輸資料。
以下說明Socket的用法。(主要是Server端)

使用到的函式庫:
    #include <sys/socket> 
    (此函式庫Windows作業系統不會有喔)

Server端首先要創建socket,並綁定在某個port,等待聆聽client連線。
( 基本上順序是這樣:socket() -> bind() -> listen() -> accept() ,創建 socket 之後將socket bind port,開始listen 監聽port有沒訊息,accept 會讓程式block住直到收到訊息)


重要函式


1. int socket( int domain, int type, int protocol):創建socket
    - domain:常用的有三種
        1. AF_UNIX / AF_LOCAL:用在本機process間的傳輸
        2. AF_INET:兩台主機透過網路進行資料傳輸 (IPv4)
        3. AF_INET6: 兩台主機透過網路進行資料傳輸 (IPv6)
    - type :socket的傳輸手段
        1. SOCK_STREAM:TCP
        2. SOCK_DGRAM:UDP
    - protocol:設定socket的協定標準,一般都會設為0
    - return值:成功返回 socket file descriptor,失敗回傳-1

2. int bind( int sockfd, struct sockaddr *addr, socklen_t addrlen):綁定socket到某地址與port
    - sockfd:socket file descriptor
    - addr:地址資訊
    - addrlen:輸入addr大小
    - return值:成功返回 0,失敗回傳-1

3. int listen( int sockfd, int backlog):通知OS此socket可以接受連線
    - sockfd:socket file descriptor
    - backlog:設定佇列(incoming queue)中所允許的連線數目
    - return值:成功返回 0,失敗回傳-1

4. int accept( int sockfd, struct sockaddr *addr, socklen_t *addrlen ):取得等待中的連線,會回傳新的sockfd服務這個連線用,原sockfd繼續在listen
    - sockfd:socket file descriptor
    - addr:儲存client位址資訊
    - addrlen:設定為addr的資料結構大小
    - return值:成功返回新連線的 socket descriptor,錯誤回傳-1



如何設定地址給socket綁定用


1. 使用 getaddrinfo():  (較新的方法)

    需要 # include <netdb.h>

    int getaddrinfo( const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res)
        - node:要連線的主機名稱或IP address
        - service:port number(ex:"80")或者特定服務名稱(可在UNIX系統上的IANA Port List或/etc/services檔案中找到,像是http, ftp, telnet...)
        - hints:已經填好位址相關資訊的 struct addrinfo
        - res:結果會回傳到res,res是一個指向 addrinfo 鏈結串列的指標
        - return值:成功時回傳0。若傳回值非零,可將傳回值傳遞給gai_strerror()可取得我們可讀的字串


    struct addrinfo {
        int ai_flags;                                // additional options, ex: AI_PASSIVE, AI_CANONNAME 等。
        int ai_family;                             // AF_INET, AF_INET6, AF_UNSPEC
        int ai_socktype;                         // SOCK_STREAM, SOCK_DGRAM
        int ai_protocol;                          // 用 0 當作 "any"
        size_t ai_addrlen;                      // ai_addr 的大小,單位是 byte
        struct sockaddr *ai_addr;          // struct sockaddr_in 或 _in6
        char *ai_canonname;                // 典型的 hostname
        struct addrinfo *ai_next;           // 鏈結串列、下個節點
    };

struct addrinfo hints, *res;
int sockfd;

memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;   // 無關協議
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;   // 自動填上本機的IP

getaddrinfo(NULL, "3490", &hints, &res);   // PORT這裡設定3490

// 建立 socket //(這邊是簡化版,照理應該要查看 "res" 鏈結串列的每個成員,並進行錯誤檢查!)
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
bind(sockfd, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);  // 綁定好要記得釋放res空間

2. 使用 sockaddr_in
struct sockaddr_in myaddr;
int s;

myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(3490);

// 可以指定一個 IP address:
inet_pton(AF_INET, "127.0.0.1", &(myaddr.sin_addr));
// 或者可以讓系統自動選一個 IP address:
myaddr.sin_addr.s_addr = INADDR_ANY;

s = socket(PF_INET, SOCK_STREAM, 0);
bind(s, (struct sockaddr*)&myaddr, sizeof myaddr);


Sever端Socket程式範例


當server收到client端連線會印出client端的ip位址
#define PORT "3490"
#define BACKLOG 10

int sockfd, new_fd;
struct addrinfo hints, *servinfo, *p;
struct sockaddr_storage their_addr;
char s[INET6_ADDRSTRLEN];
int rv;

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;    // 連上localhost

if ( (rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0 ){
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
    return 1;
}

// 以迴圈找出全部的結果,並綁定(bind)到第一個能用的結果
for(p = servinfo; p != NULL; p = p->ai_next) {
    if ((sockfd = socket(p->ai_family, p->ai_socktype,p->ai_protocol)) == -1) {
        perror("server: socket");
        continue;
    }
    if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
        close(sockfd);
        perror("server: bind");
        continue;
    }
    break;
}
if (p == NULL) {
    fprintf(stderr, "server: failed to bind\n");
    return 2;
}

freeaddrinfo(servinfo);

if(listen(sockfd, BACKLOG) == -1) {
    perror("listen");
    exit(1);
}
printf("server: waiting for connection...\n");

while(1){
    socklen_t sin_size = sizeof(their_addr);
    new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size);
    if(new_fd==-1){
        perror("accept");
        continue;
    }
    printf("server accept.\n");

    inet_ntop(their_addr.ss_family,
              get_in_addr((struct sockaddr *)&their_addr),
              s, sizeof(s));
    printf("server: got connection from %s\n", s);
        
    // newfd is a new socket descriptor for the new connection. 
    // listenfd is still listening for new connections.
    close(new_fd);
}

轉換 struct sockaddr 成 struct sockaddr_in 或 struct sockaddr_in6:
void *get_in_addr(struct sockaddr *sa){
    if (sa->sa_family == AF_INET) {
     return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}




各種 Address Struct


1. struct sockaddr {
        unsigned short sa_family;      // address family, AF_xxx
        char sa_data[14];                   // 14 bytes of protocol address
    };       (16 bytes)

2. struct sockaddr_in {
        short int sin_family;                     // Address family, AF_INET
        unsigned short int sin_port;         // Port number
        struct in_addr sin_addr;               // Internet address (in_addr : 4 byte)
        unsigned char sin_zero[8];         
    }        (16 bytes)                         

3. struct in_addr {
        uint32_t s_addr;         // that's a 32-bit int
    };    (4 bytes)
   
4. struct sockaddr_in6 {
        u_int16_t sin6_family;                 // address family, AF_INET6
        u_int16_t sin6_port;                     // port number, Network Byte Order
        u_int32_t sin6_flowinfo;              // IPv6 flow 資訊
        struct in6_addr sin6_addr;            // IPv6 address
        u_int32_t sin6_scope_id;             // Scope ID
    };       (28 bytes)

5. struct in6_addr {
        unsigned char s6_addr[16];         // IPv6 address
    };      (16 bytes)

6. struct sockaddr_storage {
        sa_family_t ss_family; // address family
        // 這裡都是填充物(padding),依實作而定
        char __ss_pad1[_SS_PAD1SIZE];
        int64_t __ss_align;
        char __ss_pad2[_SS_PAD2SIZE];
    };      (128 bytes)



參考資料

    1. http://beej-zhtw.netdpi.net/


沒有留言:

張貼留言