socket套接字編程(1)——基本函數


TCP交互流程:

服務器:1. 創建socket;2. 綁定socket和端口號;3. 監聽端口號;4. 接收來自客戶端的連接請求;5. 從socket中讀取字符;6. 關閉socket。

客戶端:1. 創建socket;2. 連接指定計算機的端口;3. 向socket中寫入信息;4. 關閉socket。

創建socket:

socket函數

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

__family是協議域,也稱協議族。常見的有AF_INET(ipv4)。

__type指定socket類型。SOCK_STREAM即TCP協議,SOCK_DGRAM即UDP協議。

__protocol指定協議。

該函數返回的socket描述字存在於協議族空間中,但是並沒有一個具體的地址。如果想要給它賦予一個地址,就必須調用bind()函數,否則系統就在調用connect()和listen()時自動隨機分配一個端口。

這里注意:type和protocol並不能隨意組合。當protocol為0時,會自動選擇type類型對應的默認協議。

創建socket的樣例代碼如下:

    //創建TCP套接字
    //AF_INET:網絡連接,ipv4
    //SOCK_STREAM:TCP連接
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd<0) {
        std::cout<<"create socket error!"<<std::endl;
        return 0;
    }
    std::cout<<"create socket: "<<fd<<std::endl;

綁定socket和端口號:

bind函數

int bind (int, const struct sockaddr *__my_addr, socklen_t __addrlen);

第一個參數是socket描述字。(我不理解為啥這兒沒有參數名)

__my_addr是指向要綁定給該socket的協議地址。這個地址結構根據socket創建時的地址協議族(family)的不同而不同。

__addrlen對應的是地址的長度。

如果該函數執行成功,就返回0,否則為SOCKET_ERROR。

    //命名套接字
    struct sockaddr_in myaddr;
    memset((void *)&myaddr, 0, sizeof(myaddr));
    //關於htonl和htons,參考以下網頁:ntohs, ntohl, htons,htonl的比較和詳解
    //https://blog.csdn.net/haoxiaodao/article/details/73162663
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    myaddr.sin_port = htons(6666);
    if (bind(fd, (struct sockaddr*)&myaddr, sizeof(myaddr)) < 0) {
        std::cout<<"name socket error!"<<std::endl;
        return 0;
    }
    std::cout<<"name socket"<<std::endl;

監聽端口號:

作為一個服務器,在調用socket()和bind()之后就會調用listen()來監聽這個socket。為了能夠在套接字上接受進入的連接,服務器程序必須創建一個隊列來保存未處理的請求。

listen函數

int listen (int, int __n);

第一個參數即socket描述子

__n為隊列的大小。

    //創建監聽隊列
    if (listen(fd, 5) < 0) {
        std::cout<<"listen failed"<<std::endl;
        return 0;
    }

接收來自客戶端的連接請求:

當TCP服務器監聽到了連接請求之后,就會調用accept()函數接收請求,這樣連接就建立好了。

accept函數

int accept (int, struct sockaddr *__peer, socklen_t *);

第一個是socket描述子,第二個是用來接收的客戶端地址,第三個是地址的大小。注意第三個是指針類型,所以要事先構造好大小的變量,然后傳地址進去。

另外,《后台開發核心技術與應用實踐》中的例子,第二個和第三個都傳的NULL。我的理解是,如果不需要接收這兩個量,就可以傳一個空值進去。

accept函數會返回一個新的socket描述子,這個新的描述子代表了服務端和客戶端的連接。后面可以用於讀取數據以及關閉連接。

   //等待並接受連接
    const int MAXBUF = 4096;
    char buff[MAXBUF];
    struct sockaddr_in client_addr;
    int client_addr_len = sizeof(client_addr);
    int client_fd;
    while (1) {
        client_fd = accept(fd, (struct sockaddr*)&client_addr, &client_addr_len);
        if (client_fd < 0) {
            std::cout<<"connect error"<<std::endl;
            continue;
        }
        //接收數據
        //關閉套接字
    }

從socket中讀取字符:

服務器與客戶端建立好連接之后,就可以調用網絡I/O進行讀寫操作了。網絡I/O操作有下面幾組:

read()/write()

recv()/send()

readv()/writev()

recvmsg()/sendmsg()

recvfrom()/sendto()

具體的區別待補充,這里只舉一個例子。

_ssize_t read (int __fd, void *__buf, size_t __nbyte);

__fd是剛才獲得的服務器與客戶端建立連接的socket描述子

__buf是緩沖區指針

__nbyte是緩沖區大小

        //接收數據
        int nbytes = read(client_fd, buff, MAXBUF);
        std::cout<<"get infomation: "<<buff<<std::endl;

關閉socket:

完成讀寫操作就要關閉相應的socket描述子,可以類比與文件完成讀寫操作之后也要關閉一樣。

close函數

int     close (int __fildes);

只有一個參數,就是socket描述子。

close會把該socket標記為關閉,然后立即返回到調用進程。該描述子不能再由調用進程使用,即,不能再作為read或write的第一個參數。

但是,這里需要注意的是,close操作只是使相應socket描述子的引用-1,只有當引用計數為0時,才會出發TCP發送終止連接請求。

 

以上是服務器端的基本代碼,總的代碼如下:

#include <sys/socket.h>
#include <iostream>
#include <cygwin/in.h>
#include <cstring>
#include <unistd.h>

int main() {
    std::cout<<"running server"<<std::endl;
    //創建TCP套接字
    //AF_INET:網絡連接,ipv4
    //SOCK_STREAM:TCP連接
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd<0) {
        std::cout<<"create socket error!"<<std::endl;
        return 0;
    }
    std::cout<<"create socket: "<<fd<<std::endl;

    //命名套接字
    struct sockaddr_in myaddr;
    memset((void *)&myaddr, 0, sizeof(myaddr));
    //關於htonl和htons,參考以下網頁:ntohs, ntohl, htons,htonl的比較和詳解
    //https://blog.csdn.net/haoxiaodao/article/details/73162663
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    myaddr.sin_port = htons(6666);
    if (bind(fd, (struct sockaddr*)&myaddr, sizeof(myaddr)) < 0) {
        std::cout<<"name socket error!"<<std::endl;
        return 0;
    }
    std::cout<<"name socket"<<std::endl;

    //創建監聽隊列
    if (listen(fd, 5) < 0) {
        std::cout<<"listen failed"<<std::endl;
        return 0;
    }
    std::cout<<"=============listening, port = 6666"<<std::endl;

    //等待並接受連接
    const int MAXBUF = 4096;
    char buff[MAXBUF];
    struct sockaddr_in client_addr;
    int client_addr_len = sizeof(client_addr);
    int client_fd;
    while (1) {
        client_fd = accept(fd, (struct sockaddr*)&client_addr, &client_addr_len);
        if (client_fd < 0) {
            std::cout<<"connect error"<<std::endl;
            continue;
        }
        //接收數據
        int nbytes = read(client_fd, buff, MAXBUF);
        std::cout<<"get infomation: "<<buff<<std::endl;
        //關閉套接字
        close(client_fd);
    }
    close(fd);
    return 0;
}

相比與服務器端,客戶端的區別主要在於連接指定計算機的端口和向socket中寫入信息。

連接制定計算機的端口:

connect函數

int connect (int, const struct sockaddr *, socklen_t);

第一個參數是socket描述子,第二個是目標服務器的地址,第三個是地址struct的大小。

關於目標服務器地址的構造,需要利用函數inet_pton(),代碼如下:

    const char* server = "127.0.0.1";
    struct sockaddr_in server_addr;
    memset((char*)&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(6666);
    inet_pton(AF_INET, server, &server_addr.sin_addr);

構造完目標服務器的地址,就可以調用connect()函數了。

    if (connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        std::cout<<"connect failed"<<std::endl;
        return 0;
    }

向socket中寫入信息:

跟上面接收信息對應,使用了write()函數

    const int MAXBUF = 4096;
    char buffer[MAXBUF] = "hello TCP";
    int nbytes = write(fd, buffer, 10);

 

以上就是客戶端與服務器端相比不同的部分,客戶端的基本代碼如下:

#include <sys/socket.h>
#include <iostream>
#include <cstring>
#include <cygwin/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        std::cout<<"socket error"<<std::endl;
        return 0;
    }

    const char* server = "127.0.0.1";
    struct sockaddr_in server_addr;
    memset((char*)&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(6666);
    inet_pton(AF_INET, server, &server_addr.sin_addr);

    if (connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        std::cout<<"connect failed"<<std::endl;
        return 0;
    }

    const int MAXBUF = 4096;
    char buffer[MAXBUF] = "hello TCP";
    int nbytes = write(fd, buffer, 10);

    close(fd);
}

 

參考資料:

1. TCP套接字編程入門 https://blog.csdn.net/lihao21/article/details/64624796?locationNum=7&fps=1

2. 《后台開發核心技術與應用實踐》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM