1. 程式人生 > 實用技巧 >Linux下socket程式設計基礎1-初探

Linux下socket程式設計基礎1-初探

Linux下socket套接字基礎


int socket(int __domain, int __type, int __protocol);

  • domain:是協議域,這個引數決定了socket的地址型別,常用的是AF_INET(ipv4地址)和16位埠號組合
  • type:是指定socket型別,常用的socket型別,SOCK_STREAM(基於TCP),SOCK_DGRAM(基於UDP)
  • protocol: 指定協議,常用IPPROTO_TCP、IPPTOTO_UDP等

呼叫socket()返回的描述字存在於協議族空間中,但是沒有一個具體的地址,所以需要呼叫下面的bind()函式設定具體地址,否則當呼叫connect()、listen()時這個埠系統會隨機分配。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  • sockfd:就是socket的描述字,socket()的返回值
  • *addr :
    一個指向sockaddr結構體的指標,指向要繫結給sockfd的協議地址,這個結構體隨著地址結構的改變而呼叫不同的結構體
  1. ipv4:
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 */
};
  1. ipv6:
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 */ 
};
  • addlen: 對應的是地址的長度

int listen(int sockfd, int backlog);

服務端通過listen()將socket轉變為被動型別,等待客戶的連線請求

  • sockfd:     第一個引數即為要監聽的socket描述字.
  • backlog:   第二個引數為相應socket可以排隊的最大連線個數。socket()函式建立的socket預設是一個主動型別的,listen函式將socket變為被動型別的,等待客戶的連線請求。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

客戶端通過呼叫connect()來建立於伺服器的連線

  • sockfd: 第一個引數是客戶端的socket描述字
  • *addr : 第二個引數是伺服器的socket地址
  • addrlen :第三個引數是伺服器socket地址的長度

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

TCP伺服器端依次呼叫socket()、bind()、listen()之後,就會監聽指定的socket地址了。TCP客戶端依次呼叫socket()、connect()之後就想TCP伺服器傳送了一個連線請求。TCP伺服器監聽到這個請求之後,就會呼叫accept()函式取接收請求,這樣連線就建立好了。之後就可以開始網路I/O操作了,即類同於普通檔案的讀寫I/O操作。

  • sockfd : 伺服器socket描述字
  • *addr : 返回客戶端的socket地址
  • *addrlen : 客戶端的socket地址長度
  • 返回值: 已連線的socket描述字

注意:伺服器通常只需要建立一個socket套接字,這個套接字在伺服器生命週期內一直存在。而客戶端連線成功服務端後,核心會建立一個新的已連線的socket套接字,當伺服器對此客戶端完成服務之後,這個socket描述字就會被關閉。


recvmsg()和sendmsg()函式(最通用的I/O函式)

用這兩個函式可替代

  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvfrom()/sendto()
#include <sys/socket.h>
#include <sys/uio.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

struct msghdr {
    void          *msg_name;        // protocol address
    socklen_t      msg_namelen;     // size of protocol address
    struct iovec  *msg_iov;         // scatter/gather array
    int            msg_iovlen;      // elements in msg_iov
    void          *msg_control;     // ancillary data (cmsghdr struct)
    socklen_t      msg_controllen;  // length of ancillary data
    int            msg_flags;       // flags returned by recvmsg()
};

struct iovec {//依賴於標頭檔案sys/uio.h
    void    *iov_base;      /* starting address of buffer */
    size_t  iov_len;        /* size of buffer */
}
  • msg_name和msg_namelen
    這兩個成員用於套接字未連線的場合(如未連線 UDP 套接字)。
    如果用於TCP的話,msg_name置為空指標,namelen置為0。

  • msg_iov和msg_iovlen
    這兩個成員指定輸入輸出緩衝區陣列以及陣列的多少,結合struct iovec來看,msg_iov是個二維陣列

  • msg_control和msg_controllen
    這兩個成員指定可選的輔助資料的位置和大小

  • msg_flags
    對於sendmsg,這個引數可以忽略掉。
    對於recvmsg,它本身的引數flags會被複制到msg_flags成員,由核心使用這個值驅動接收處理過程。

recvmsg 返回的 7 個標誌如下:

  • MSG_BCAST:本標誌隨 BSD/OS 引入,相對較新。它的返回條件是本資料包作為鏈路層廣播收取或者其目的 IP 地址是一個廣播地址。與 IP_RECVD-STADDR 套接字選項相比,本標誌是用於判定一個 UPD 資料包是否發往某個廣播地址的更好方法。
  • MSG_MCAST:本標誌隨 BSD/OS 引入,相對較新。它的返回條件是本資料報作為鏈路層多播收取。
  • MSG_TRUNC:本標誌的返回條件是本資料報被截斷,也就是說,核心預備返回的資料超過程序事先分配的空間(所有 iov_len 成員之和)。
  • MSG_CTRUNC:本標誌的返回條件是本資料報的輔助資料被截斷,也就是說,核心預備返回的輔助資料超過程序事先分配的空間(msg_controllen)。
  • MSG_EOR:本標誌的返回條件是返回資料結束一個邏輯記錄。TCP 不使用本標誌,因為它是一個位元組流協議。
  • MSG_OOB:本標誌絕不為 TCP 帶外資料返回。它用於其他協議族(如 OSI 協議族)。
  • MSG_NOTIFICATION:本標誌由 SCTP 接收者返回,指示讀入的訊息是一個事先通知,而不是資料訊息。
示例程式碼
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>

using namespace std;

void My_server(void){
    //初始化傳送資料緩衝區
    char buf[128]={0};
    struct iovec buffer={
        .iov_base = buf,
        .iov_len = sizeof(buf)
    };
    struct msghdr Mymsg={
        .msg_name = nullptr,
        .msg_namelen =0,
        .msg_iov = &buffer,
        .msg_iovlen = 1,
        .msg_control = nullptr,
        .msg_controllen = 0,
        .msg_flags = 0
    };
    //建立套接字
    int Mysocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    //將套接字和IP、埠繫結
    struct sockaddr_in Server_Addr;
    memset(&Server_Addr,0,sizeof(Server_Addr));
    Server_Addr.sin_family=AF_INET; //設定使用ipv4地址
    Server_Addr.sin_addr.s_addr=inet_addr("127.0.0.1");//設定具體的ip地址(本地環回)
    Server_Addr.sin_port=htons(12345); //埠
    bind(Mysocket,(struct sockaddr *)&Server_Addr,sizeof(Server_Addr));//應用 地址和埠的設定

    //進入監聽狀態
    listen(Mysocket,20);
    //接收客戶端請求
    struct sockaddr_in Client_Addr;
    socklen_t client_addr_size = sizeof(Client_Addr);
    std::cout << "ready accept"<<endl;
    int client_sock = accept(Mysocket,(struct sockaddr *)&Client_Addr,&client_addr_size);//在此阻塞,等待客戶端請求
    std::cout << "received message"<<endl;
    //向客戶端傳送資料
    char str[] = "hello,world!";
    Mymsg.msg_iov->iov_base = str;

    sendmsg(client_sock,&Mymsg,0); //write(client_sock,str,sizeof(str));用write也是可以的

    close(client_sock);
    close(Mysocket);
}

void My_client(void){
    char buf[128] = {0};
    struct iovec buffer={
        .iov_base = buf,
        .iov_len = sizeof(buf)
    };
    struct msghdr Mymsg={
        .msg_name = nullptr,
        .msg_namelen = 0,
        .msg_iov = &buffer,
        .msg_iovlen = 1,
        .msg_control = nullptr,
        .msg_controllen = 0,
        .msg_flags = 0
    };

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

    struct sockaddr_in Server_Addr;
    memset(&Server_Addr,0,sizeof(Server_Addr));
    Server_Addr.sin_family=AF_INET;
    Server_Addr.sin_addr.s_addr=inet_addr("127.0.0.1");
    Server_Addr.sin_port=htons(12345);
    connect(Mysocket,(struct sockaddr*)&Server_Addr,sizeof(Server_Addr));

    std::cout << "ready accept message"<<endl;
    recvmsg(Mysocket,&Mymsg,0); //接收資訊
    printf("%s\n",Mymsg.msg_iov->iov_base);
    std::cout << "read message done"<<endl;

    close(Mysocket);
}

int main(int argc, char **argv){
    string select;
_Start:
    std::cout << "Select Mode Server or Client:(c/s)";
    cin >> select;
    std::cout << "select:" << select <<endl;
    if(!select.compare("s")){
        My_server();
    }else if(!select.compare("c")){
        My_client();
    }else{
        std::cout << "please input \"server\" or \"client\""<<endl;
        std::cout << "Please re - enter parameters at boot time"<<endl;
        goto _Start;
    }
    return 0;
}

/*
    建立套接字: socket()
    繫結本機埠: bind()
    建立連線: connect(),accept()
    偵聽埠: listen()
    資料傳輸: send(), recv()
    輸入/輸出多路複用: select()
    關閉套接字:   closesocket()
*/

Makefile太麻煩了,利用cmake編譯

/***CMakeLists.txt***/
cmake_minimum_required(VERSION 3.10.2)

PROJECT(mysocket)

add_executable(Mysocket Mysocket.cpp)