網絡編程bind函數詳解(轉載)

注:該文轉載自https://blog.csdn.net/zpznba/article/details/90763798ios

 

bind 函數如何選擇綁定地址面試

咱們知道bind函數通常用在服務器代碼中:shell

 

struct sockaddr_in bindaddr;windows

bindaddr.sin_family = AF_INET;api

bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);服務器

bindaddr.sin_port = htons(3000);網絡

if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1)app

{socket

std::cout << "bind listen socket error." << std::endl;ide

return -1;

}

其中 bind 的地址咱們使用了一個宏叫 INADDR_ANY ,關於這個宏的解釋以下:

 

If an application does not care what local address is assigned, specify the constant value INADDR_ANY for an IPv4 local address or the constant value in6addr_any for an IPv6 local address in the sa_data member of the name parameter. This allows the underlying service provider to use any appropriate network address, potentially simplifying application programming in the presence of multihomed hosts (that is, hosts that have more than one network interface and address).

意譯一下:

 

若是應用程序不關心bind綁定的ip地址,可使用INADDR_ANY(若是是IPv6,則對應in6addr_any),這樣底層的(協議棧)服務會自動選擇一個合適的ip地址,這樣使在一個有多個網卡機器上選擇ip地址問題變得簡單。

也就是說 INADDR_ANY 至關於地址 0.0.0.0。可能讀者仍是不太明白我想表達什麼。這裏我舉個例子,假設咱們在一臺機器上開發一個服務器程序,使用 bind 函數時,咱們有多個ip 地址能夠選擇。首先,這臺機器對外訪問的ip地址是 120.55.94.78,這臺機器在當前局域網的地址是 192.168.1.104;同時這臺機器有本地迴環地址127.0.0.1。

 

若是你指向本機上能夠訪問,那麼你 bind 函數中的地址就可使用127.0.0.1; 若是你的服務只想被局域網內部機器訪問,bind 函數的地址可使用192.168.1.104;若是但願這個服務能夠被公網訪問,你就可使用地址**0.0.0.0 ** 或 INADDR_ANY。

 

bind 函數端口號問題

 

網絡通訊程序的基本邏輯是客戶端鏈接服務器,即從客戶端的地址:端口鏈接到服務器地址:端口上,以 4.2 小節中的示例程序爲例,服務器端的端口號使用 3000,那客戶端鏈接時的端口號是多少呢?TCP 通訊雙方中通常服務器端端口號是固定的,而客戶端端口號是鏈接發起時由操做系統隨機分配的(不會分配已經被佔用的端口)。端口號是一個 C short 類型的值,其範圍是0~65535,知道這點很重要,因此咱們在編寫壓力測試程序時,因爲端口數量的限制,在某臺機器上網卡地址不變的狀況下壓力測試程序理論上最多隻能發起六萬五千多個鏈接。注意我說的是理論上,在實際狀況下,因爲當時的操做系統不少端口可能已經被佔用,實際可使用的端口比這個更少,例如,通常規定端口號在1024如下的端口是保留端口,不建議用戶程序使用。而對於 Windows 系統,MSDN 甚至明確地說:

 

On Windows Vista and later, the dynamic client port range is a value between 49152 and 65535. This is a change from Windows Server 2003 and earlier where the dynamic client port range was a value between 1025 and 5000.

Vista 及之後的Windows,可用的動態端口範圍是49152~65535,而 Windows Server及更早的系統,能夠的動態端口範圍是1025~5000。(你能夠經過修改註冊表來改變這一設置,參考網址:https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-bind)

若是將 bind 函數中的端口號設置成0,那麼操做系統會隨機給程序分配一個可用的偵聽端口,固然服務器程序通常不會這麼作,由於服務器程序是要對外服務的,必須讓客戶端知道確切的ip地址和端口號。

 

不少人以爲只有服務器程序能夠調用 bind 函數綁定一個端口號,其實否則,在一些特殊的應用中,咱們須要客戶端程序以指定的端口號去鏈接服務器,此時咱們就能夠在客戶端程序中調用 bind 函數綁定一個具體的端口。

 

咱們用代碼來實際驗證一下上路所說的,爲了能看到鏈接狀態,咱們將客戶端和服務器關閉socket的代碼註釋掉,這樣鏈接會保持一段時間。

 

情形一:客戶端代碼不綁定端口

服務器代碼以下:

 

/**

 * TCP服務器通訊基本流程

 */

#include <sys/types.h> 

#include <sys/socket.h>

#include <arpa/inet.h>

#include <unistd.h>

#include <iostream>

#include <string.h>

#include <vector>

 

int main(int argc, char* argv[])

{

    //1.建立一個偵聽socket

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

    if (listenfd == -1)

    {

        std::cout << "create listen socket error." << std::endl;

        return -1;

    }

 

    //2.初始化服務器地址

    struct sockaddr_in bindaddr;

    bindaddr.sin_family = AF_INET;

    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    bindaddr.sin_port = htons(3000);

    if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1)

    {

        std::cout << "bind listen socket error." << std::endl;

        return -1;

    }

 

//3.啓動偵聽

    if (listen(listenfd, SOMAXCONN) == -1)

    {

        std::cout << "listen error." << std::endl;

        return -1;

    }

 

//記錄全部客戶端鏈接的容器

std::vector<int> clientfds;

    while (true)

    {

        struct sockaddr_in clientaddr;

        socklen_t clientaddrlen = sizeof(clientaddr);

//4. 接受客戶端鏈接

        int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);

        if (clientfd != -1)

        {         

char recvBuf[32] = {0};

//5. 從客戶端接受數據

int ret = recv(clientfd, recvBuf, 32, 0);

if (ret > 0) 

{

std::cout << "recv data from client, data: " << recvBuf << std::endl;

//6. 將收到的數據原封不動地發給客戶端

ret = send(clientfd, recvBuf, strlen(recvBuf), 0);

if (ret != strlen(recvBuf))

std::cout << "send data error." << std::endl;

 

std::cout << "send data to client successfully, data: " << recvBuf << std::endl;

else 

{

std::cout << "recv data error." << std::endl;

}

 

//close(clientfd);

clientfds.push_back(clientfd);

        }

    }

 

//7.關閉偵聽socket

close(listenfd);

 

    return 0;

}

修改後的客戶端代碼以下:

 

/**

 * TCP客戶端通訊基本流程

 */

#include <sys/types.h> 

#include <sys/socket.h>

#include <arpa/inet.h>

#include <unistd.h>

#include <iostream>

#include <string.h>

 

#define SERVER_ADDRESS "127.0.0.1"

#define SERVER_PORT     3000

#define SEND_DATA       "helloworld"

 

int main(int argc, char* argv[])

{

    //1.建立一個socket

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

    if (clientfd == -1)

    {

        std::cout << "create client socket error." << std::endl;

        return -1;

    }

 

    //2.鏈接服務器

    struct sockaddr_in serveraddr;

    serveraddr.sin_family = AF_INET;

    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);

    serveraddr.sin_port = htons(SERVER_PORT);

    if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1)

    {

        std::cout << "connect socket error." << std::endl;

        return -1;

    }

 

//3. 向服務器發送數據

int ret = send(clientfd, SEND_DATA, strlen(SEND_DATA), 0);

if (ret != strlen(SEND_DATA))

{

std::cout << "send data error." << std::endl;

return -1;

}

 

std::cout << "send data successfully, data: " << SEND_DATA << std::endl;

 

//4. 從客戶端收取數據

char recvBuf[32] = {0};

ret = recv(clientfd, recvBuf, 32, 0);

if (ret > 0) 

{

std::cout << "recv data successfully, data: " << recvBuf << std::endl;

else 

{

std::cout << "recv data error, data: " << recvBuf << std::endl;

}

 

//5. 關閉socket

//close(clientfd);

//這裏僅僅是爲了讓客戶端程序不退出

while (true) 

{

sleep(3);

}

 

    return 0;

}

將程序編譯好後(編譯方法和上文同樣),咱們先啓動server,再啓動三個客戶端。而後經過 lsof 命令查看當前機器上的 TCP 鏈接信息,爲了更清楚地顯示結果,已經將不相關的鏈接信息去掉了,結果以下所示:

 

[root@localhost ~]# lsof -i -Pn

COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME

server   1445 root    3u  IPv4  21568      0t0  TCP *:3000 (LISTEN)

server   1445 root    4u  IPv4  21569      0t0  TCP 127.0.0.1:3000->127.0.0.1:40818 (ESTABLISHED)

server   1445 root    5u  IPv4  21570      0t0  TCP 127.0.0.1:3000->127.0.0.1:40820 (ESTABLISHED)

server   1445 root    6u  IPv4  21038      0t0  TCP 127.0.0.1:3000->127.0.0.1:40822 (ESTABLISHED)

client   1447 root    3u  IPv4  21037      0t0  TCP 127.0.0.1:40818->127.0.0.1:3000 (ESTABLISHED)

client   1448 root    3u  IPv4  21571      0t0  TCP 127.0.0.1:40820->127.0.0.1:3000 (ESTABLISHED)

client   1449 root    3u  IPv4  21572      0t0  TCP 127.0.0.1:40822->127.0.0.1:3000 (ESTABLISHED)

上面的結果顯示,server 進程(進程 ID 是 1445)在 3000 端口開啓偵聽,有三個 client 進程(進程 ID 分別是144七、144八、1449)分別經過端口號 4081八、40820、40822 連到 server 進程上的,做爲客戶端的一方,端口號是系統隨機分配的。

 

情形二:客戶端綁定端口號 0

 

服務器端代碼保持不變,咱們修改下客戶端代碼:

 

/**

 * TCP客戶端通訊基本流程

 */

#include <sys/types.h> 

#include <sys/socket.h>

#include <arpa/inet.h>

#include <unistd.h>

#include <iostream>

#include <string.h>

 

#define SERVER_ADDRESS "127.0.0.1"

#define SERVER_PORT     3000

#define SEND_DATA       "helloworld"

 

int main(int argc, char* argv[])

{

    //1.建立一個socket

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

    if (clientfd == -1)

    {

        std::cout << "create client socket error." << std::endl;

        return -1;

    }

 

    struct sockaddr_in bindaddr;

    bindaddr.sin_family = AF_INET;

    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);

//將socket綁定到0號端口上去

    bindaddr.sin_port = htons(0);

    if (bind(clientfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1)

    {

        std::cout << "bind socket error." << std::endl;

        return -1;

    }

 

    //2.鏈接服務器

    struct sockaddr_in serveraddr;

    serveraddr.sin_family = AF_INET;

    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);

    serveraddr.sin_port = htons(SERVER_PORT);

    if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1)

    {

        std::cout << "connect socket error." << std::endl;

        return -1;

    }

 

//3. 向服務器發送數據

int ret = send(clientfd, SEND_DATA, strlen(SEND_DATA), 0);

if (ret != strlen(SEND_DATA))

{

std::cout << "send data error." << std::endl;

return -1;

}

 

std::cout << "send data successfully, data: " << SEND_DATA << std::endl;

 

//4. 從客戶端收取數據

char recvBuf[32] = {0};

ret = recv(clientfd, recvBuf, 32, 0);

if (ret > 0) 

{

std::cout << "recv data successfully, data: " << recvBuf << std::endl;

else 

{

std::cout << "recv data error, data: " << recvBuf << std::endl;

}

 

//5. 關閉socket

//close(clientfd);

//這裏僅僅是爲了讓客戶端程序不退出

while (true) 

{

sleep(3);

}

 

    return 0;

}

咱們再次編譯客戶端程序,並啓動三個 client 進程,而後用 lsof 命令查看機器上的 TCP 鏈接狀況,結果以下所示:

 

[root@localhost ~]# lsof -i -Pn

COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME

server   1593 root    3u  IPv4  21807      0t0  TCP *:3000 (LISTEN)

server   1593 root    4u  IPv4  21808      0t0  TCP 127.0.0.1:3000->127.0.0.1:44220 (ESTABLISHED)

server   1593 root    5u  IPv4  19311      0t0  TCP 127.0.0.1:3000->127.0.0.1:38990 (ESTABLISHED)

server   1593 root    6u  IPv4  21234      0t0  TCP 127.0.0.1:3000->127.0.0.1:42365 (ESTABLISHED)

client   1595 root    3u  IPv4  22626      0t0  TCP 127.0.0.1:44220->127.0.0.1:3000 (ESTABLISHED)

client   1611 root    3u  IPv4  21835      0t0  TCP 127.0.0.1:38990->127.0.0.1:3000 (ESTABLISHED)

client   1627 root    3u  IPv4  21239      0t0  TCP 127.0.0.1:42365->127.0.0.1:3000 (ESTABLISHED)

經過上面的結果,咱們發現三個 client 進程使用的端口號仍然是系統隨機分配的,也就是說綁定 0 號端口和沒有綁定效果是同樣的。

 

情形三:客戶端綁定一個固定端口

 

咱們這裏使用 20000 端口,固然讀者能夠根據本身的喜愛選擇,只要保證所選擇的端口號當前沒有被其餘程序佔用便可,服務器代碼保持不變,客戶端綁定代碼中的端口號從 0 改爲 20000。這裏爲了節省篇幅,只貼出修改處的代碼:

 

struct sockaddr_in bindaddr;

bindaddr.sin_family = AF_INET;

bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);

//將socket綁定到20000號端口上去

bindaddr.sin_port = htons(20000);

if (bind(clientfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1)

{

    std::cout << "bind socket error." << std::endl;

    return -1;

}

再次從新編譯程序,先啓動一個客戶端後,咱們看到此時的 TCP 鏈接狀態:

 

[root@localhost testsocket]# lsof -i -Pn

COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME

server   1676 root    3u  IPv4  21933      0t0  TCP *:3000 (LISTEN)

server   1676 root    4u  IPv4  21934      0t0  TCP 127.0.0.1:3000->127.0.0.1:20000 (ESTABLISHED)

client   1678 root    3u  IPv4  21336      0t0  TCP 127.0.0.1:20000->127.0.0.1:3000 (ESTABLISHED)

經過上面的結果,咱們發現 client 進程確實使用 20000 號端口鏈接到 server 進程上去了。這個時候若是咱們再開啓一個 client 進程,咱們猜測因爲端口號 20000 已經被佔用,新啓動的 client 會因爲調用 bind 函數出錯而退出,咱們實際驗證一下:

 

[root@localhost testsocket]# ./client 

bind socket error.

[root@localhost testsocket]# 

結果確實和咱們預想的同樣。

 

在技術面試的時候,有時候面試官會問 TCP 網絡通訊的客戶端程序中的 socket 是否能夠調用 bind 函數,相信讀到這裏,聰明的讀者已經有答案了。

 

另外,Linux 的 nc 命令有個 -p 選項(字母 p 是小寫),這個選項的做用就是 nc 在模擬客戶端程序時,可使用指定端口號鏈接到服務器程序上去,實現原理相信讀者也明白了。咱們仍是以上面的服務器程序爲例,這個咱們不用咱們的 client 程序,改用 nc 命令來模擬客戶端。在 shell 終端輸入:

 

[root@localhost testsocket]# nc -v -p 9999 127.0.0.1 3000

Ncat: Version 6.40 ( http://nmap.org/ncat )

Ncat: Connected to 127.0.0.1:3000.

My name is zhangxf

My name is zhangxf

-v 選項表示輸出 nc 命令鏈接的詳細信息,這裏鏈接成功之後,會輸出「Ncat: Connected to 127.0.0.1:3000.」 提示已經鏈接到服務器的 3000 端口上去了。

 

-p 選項的參數值是 9999 表示,咱們要求 nc 命令本地以端口號 9999 鏈接服務器,注意不要與端口號 3000 混淆,3000 是服務器的偵聽端口號,也就是咱們的鏈接的目標端口號,9999 是咱們客戶端使用的端口號。咱們用 lsof 命令來驗證一下咱們的 nc 命令是否確實以 9999 端口號鏈接到 server 進程上去了。

 

[root@localhost testsocket]# lsof -i -Pn

COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME

server   1676 root    3u  IPv4  21933      0t0  TCP *:3000 (LISTEN)

server   1676 root    7u  IPv4  22405      0t0  TCP 127.0.0.1:3000->127.0.0.1:9999 (ESTABLISHED)

nc       2005 root    3u  IPv4  22408      0t0  TCP 127.0.0.1:9999->127.0.0.1:3000 (ESTABLISHED)

結果確實如咱們指望的一致。

 

固然,咱們用 nc 命令鏈接上 server 進程之後,咱們還給服務器發了一條消息"My name is zhangxf",server 程序收到消息後把這條消息原封不動地返還給咱們,如下是 server 端運行結果:

 

[root@localhost testsocket]# ./server   

recv data from client, data: My name is zhangxf

 

send data to client successfully, data: My name is zhangxf

--------------------- 

做者:zpznba 

來源:CSDN 

原文:https://blog.csdn.net/zpznba/article/details/90763798 

版權聲明:本文爲博主原創文章,轉載請附上博文連接!

bind 函數如何選擇綁定地址咱們知道bind函數通常用在服務器代碼中:struct sockaddr_in bindaddr;bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);bindaddr.sin_port = htons(3000);if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1){std::cout << "bind listen socket error." << std::endl;return -1;}其中 bind 的地址咱們使用了一個宏叫 INADDR_ANY ,關於這個宏的解釋以下:If an application does not care what local address is assigned, specify the constant value INADDR_ANY for an IPv4 local address or the constant value in6addr_any for an IPv6 local address in the sa_data member of the name parameter. This allows the underlying service provider to use any appropriate network address, potentially simplifying application programming in the presence of multihomed hosts (that is, hosts that have more than one network interface and address).意譯一下:若是應用程序不關心bind綁定的ip地址,可使用INADDR_ANY(若是是IPv6,則對應in6addr_any),這樣底層的(協議棧)服務會自動選擇一個合適的ip地址,這樣使在一個有多個網卡機器上選擇ip地址問題變得簡單。也就是說 INADDR_ANY 至關於地址 0.0.0.0。可能讀者仍是不太明白我想表達什麼。這裏我舉個例子,假設咱們在一臺機器上開發一個服務器程序,使用 bind 函數時,咱們有多個ip 地址能夠選擇。首先,這臺機器對外訪問的ip地址是 120.55.94.78,這臺機器在當前局域網的地址是 192.168.1.104;同時這臺機器有本地迴環地址127.0.0.1。若是你指向本機上能夠訪問,那麼你 bind 函數中的地址就可使用127.0.0.1; 若是你的服務只想被局域網內部機器訪問,bind 函數的地址可使用192.168.1.104;若是但願這個服務能夠被公網訪問,你就可使用地址**0.0.0.0 ** 或 INADDR_ANY。bind 函數端口號問題網絡通訊程序的基本邏輯是客戶端鏈接服務器,即從客戶端的地址:端口鏈接到服務器地址:端口上,以 4.2 小節中的示例程序爲例,服務器端的端口號使用 3000,那客戶端鏈接時的端口號是多少呢?TCP 通訊雙方中通常服務器端端口號是固定的,而客戶端端口號是鏈接發起時由操做系統隨機分配的(不會分配已經被佔用的端口)。端口號是一個 C short 類型的值,其範圍是0~65535,知道這點很重要,因此咱們在編寫壓力測試程序時,因爲端口數量的限制,在某臺機器上網卡地址不變的狀況下壓力測試程序理論上最多隻能發起六萬五千多個鏈接。注意我說的是理論上,在實際狀況下,因爲當時的操做系統不少端口可能已經被佔用,實際可使用的端口比這個更少,例如,通常規定端口號在1024如下的端口是保留端口,不建議用戶程序使用。而對於 Windows 系統,MSDN 甚至明確地說:On Windows Vista and later, the dynamic client port range is a value between 49152 and 65535. This is a change from Windows Server 2003 and earlier where the dynamic client port range was a value between 1025 and 5000.Vista 及之後的Windows,可用的動態端口範圍是49152~65535,而 Windows Server及更早的系統,能夠的動態端口範圍是1025~5000。(你能夠經過修改註冊表來改變這一設置,參考網址:https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-bind)若是將 bind 函數中的端口號設置成0,那麼操做系統會隨機給程序分配一個可用的偵聽端口,固然服務器程序通常不會這麼作,由於服務器程序是要對外服務的,必須讓客戶端知道確切的ip地址和端口號。不少人以爲只有服務器程序能夠調用 bind 函數綁定一個端口號,其實否則,在一些特殊的應用中,咱們須要客戶端程序以指定的端口號去鏈接服務器,此時咱們就能夠在客戶端程序中調用 bind 函數綁定一個具體的端口。咱們用代碼來實際驗證一下上路所說的,爲了能看到鏈接狀態,咱們將客戶端和服務器關閉socket的代碼註釋掉,這樣鏈接會保持一段時間。情形一:客戶端代碼不綁定端口服務器代碼以下:/** * TCP服務器通訊基本流程 */#include <sys/types.h> #include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>#include <iostream>#include <string.h>#include <vector> int main(int argc, char* argv[]){    //1.建立一個偵聽socket    int listenfd = socket(AF_INET, SOCK_STREAM, 0);    if (listenfd == -1)    {        std::cout << "create listen socket error." << std::endl;        return -1;    }     //2.初始化服務器地址    struct sockaddr_in bindaddr;    bindaddr.sin_family = AF_INET;    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);    bindaddr.sin_port = htons(3000);    if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1)    {        std::cout << "bind listen socket error." << std::endl;        return -1;    } //3.啓動偵聽    if (listen(listenfd, SOMAXCONN) == -1)    {        std::cout << "listen error." << std::endl;        return -1;    }//記錄全部客戶端鏈接的容器std::vector<int> clientfds;    while (true)    {        struct sockaddr_in clientaddr;        socklen_t clientaddrlen = sizeof(clientaddr);//4. 接受客戶端鏈接        int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);        if (clientfd != -1)        {         char recvBuf[32] = {0};//5. 從客戶端接受數據int ret = recv(clientfd, recvBuf, 32, 0);if (ret > 0) {std::cout << "recv data from client, data: " << recvBuf << std::endl;//6. 將收到的數據原封不動地發給客戶端ret = send(clientfd, recvBuf, strlen(recvBuf), 0);if (ret != strlen(recvBuf))std::cout << "send data error." << std::endl;std::cout << "send data to client successfully, data: " << recvBuf << std::endl;} else {std::cout << "recv data error." << std::endl;}//close(clientfd);clientfds.push_back(clientfd);        }    }//7.關閉偵聽socketclose(listenfd);     return 0;}修改後的客戶端代碼以下:/** * TCP客戶端通訊基本流程 */#include <sys/types.h> #include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>#include <iostream>#include <string.h> #define SERVER_ADDRESS "127.0.0.1"#define SERVER_PORT     3000#define SEND_DATA       "helloworld" int main(int argc, char* argv[]){    //1.建立一個socket    int clientfd = socket(AF_INET, SOCK_STREAM, 0);    if (clientfd == -1)    {        std::cout << "create client socket error." << std::endl;        return -1;    }     //2.鏈接服務器    struct sockaddr_in serveraddr;    serveraddr.sin_family = AF_INET;    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);    serveraddr.sin_port = htons(SERVER_PORT);    if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1)    {        std::cout << "connect socket error." << std::endl;        return -1;    } //3. 向服務器發送數據int ret = send(clientfd, SEND_DATA, strlen(SEND_DATA), 0);if (ret != strlen(SEND_DATA)){std::cout << "send data error." << std::endl;return -1;}std::cout << "send data successfully, data: " << SEND_DATA << std::endl;//4. 從客戶端收取數據char recvBuf[32] = {0};ret = recv(clientfd, recvBuf, 32, 0);if (ret > 0) {std::cout << "recv data successfully, data: " << recvBuf << std::endl;} else {std::cout << "recv data error, data: " << recvBuf << std::endl;}//5. 關閉socket//close(clientfd);//這裏僅僅是爲了讓客戶端程序不退出while (true) {sleep(3);}     return 0;}將程序編譯好後(編譯方法和上文同樣),咱們先啓動server,再啓動三個客戶端。而後經過 lsof 命令查看當前機器上的 TCP 鏈接信息,爲了更清楚地顯示結果,已經將不相關的鏈接信息去掉了,結果以下所示:[root@localhost ~]# lsof -i -PnCOMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAMEserver   1445 root    3u  IPv4  21568      0t0  TCP *:3000 (LISTEN)server   1445 root    4u  IPv4  21569      0t0  TCP 127.0.0.1:3000->127.0.0.1:40818 (ESTABLISHED)server   1445 root    5u  IPv4  21570      0t0  TCP 127.0.0.1:3000->127.0.0.1:40820 (ESTABLISHED)server   1445 root    6u  IPv4  21038      0t0  TCP 127.0.0.1:3000->127.0.0.1:40822 (ESTABLISHED)client   1447 root    3u  IPv4  21037      0t0  TCP 127.0.0.1:40818->127.0.0.1:3000 (ESTABLISHED)client   1448 root    3u  IPv4  21571      0t0  TCP 127.0.0.1:40820->127.0.0.1:3000 (ESTABLISHED)client   1449 root    3u  IPv4  21572      0t0  TCP 127.0.0.1:40822->127.0.0.1:3000 (ESTABLISHED)上面的結果顯示,server 進程(進程 ID 是 1445)在 3000 端口開啓偵聽,有三個 client 進程(進程 ID 分別是144七、144八、1449)分別經過端口號 4081八、40820、40822 連到 server 進程上的,做爲客戶端的一方,端口號是系統隨機分配的。情形二:客戶端綁定端口號 0服務器端代碼保持不變,咱們修改下客戶端代碼:/** * TCP客戶端通訊基本流程 */#include <sys/types.h> #include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>#include <iostream>#include <string.h> #define SERVER_ADDRESS "127.0.0.1"#define SERVER_PORT     3000#define SEND_DATA       "helloworld" int main(int argc, char* argv[]){    //1.建立一個socket    int clientfd = socket(AF_INET, SOCK_STREAM, 0);    if (clientfd == -1)    {        std::cout << "create client socket error." << std::endl;        return -1;    }    struct sockaddr_in bindaddr;    bindaddr.sin_family = AF_INET;    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);//將socket綁定到0號端口上去    bindaddr.sin_port = htons(0);    if (bind(clientfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1)    {        std::cout << "bind socket error." << std::endl;        return -1;    }     //2.鏈接服務器    struct sockaddr_in serveraddr;    serveraddr.sin_family = AF_INET;    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);    serveraddr.sin_port = htons(SERVER_PORT);    if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1)    {        std::cout << "connect socket error." << std::endl;        return -1;    } //3. 向服務器發送數據int ret = send(clientfd, SEND_DATA, strlen(SEND_DATA), 0);if (ret != strlen(SEND_DATA)){std::cout << "send data error." << std::endl;return -1;}std::cout << "send data successfully, data: " << SEND_DATA << std::endl;//4. 從客戶端收取數據char recvBuf[32] = {0};ret = recv(clientfd, recvBuf, 32, 0);if (ret > 0) {std::cout << "recv data successfully, data: " << recvBuf << std::endl;} else {std::cout << "recv data error, data: " << recvBuf << std::endl;}//5. 關閉socket//close(clientfd);//這裏僅僅是爲了讓客戶端程序不退出while (true) {sleep(3);}     return 0;}咱們再次編譯客戶端程序,並啓動三個 client 進程,而後用 lsof 命令查看機器上的 TCP 鏈接狀況,結果以下所示:[root@localhost ~]# lsof -i -PnCOMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAMEserver   1593 root    3u  IPv4  21807      0t0  TCP *:3000 (LISTEN)server   1593 root    4u  IPv4  21808      0t0  TCP 127.0.0.1:3000->127.0.0.1:44220 (ESTABLISHED)server   1593 root    5u  IPv4  19311      0t0  TCP 127.0.0.1:3000->127.0.0.1:38990 (ESTABLISHED)server   1593 root    6u  IPv4  21234      0t0  TCP 127.0.0.1:3000->127.0.0.1:42365 (ESTABLISHED)client   1595 root    3u  IPv4  22626      0t0  TCP 127.0.0.1:44220->127.0.0.1:3000 (ESTABLISHED)client   1611 root    3u  IPv4  21835      0t0  TCP 127.0.0.1:38990->127.0.0.1:3000 (ESTABLISHED)client   1627 root    3u  IPv4  21239      0t0  TCP 127.0.0.1:42365->127.0.0.1:3000 (ESTABLISHED)經過上面的結果,咱們發現三個 client 進程使用的端口號仍然是系統隨機分配的,也就是說綁定 0 號端口和沒有綁定效果是同樣的。情形三:客戶端綁定一個固定端口咱們這裏使用 20000 端口,固然讀者能夠根據本身的喜愛選擇,只要保證所選擇的端口號當前沒有被其餘程序佔用便可,服務器代碼保持不變,客戶端綁定代碼中的端口號從 0 改爲 20000。這裏爲了節省篇幅,只貼出修改處的代碼:struct sockaddr_in bindaddr;bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);//將socket綁定到20000號端口上去bindaddr.sin_port = htons(20000);if (bind(clientfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1){    std::cout << "bind socket error." << std::endl;    return -1;}再次從新編譯程序,先啓動一個客戶端後,咱們看到此時的 TCP 鏈接狀態:[root@localhost testsocket]# lsof -i -PnCOMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAMEserver   1676 root    3u  IPv4  21933      0t0  TCP *:3000 (LISTEN)server   1676 root    4u  IPv4  21934      0t0  TCP 127.0.0.1:3000->127.0.0.1:20000 (ESTABLISHED)client   1678 root    3u  IPv4  21336      0t0  TCP 127.0.0.1:20000->127.0.0.1:3000 (ESTABLISHED)經過上面的結果,咱們發現 client 進程確實使用 20000 號端口鏈接到 server 進程上去了。這個時候若是咱們再開啓一個 client 進程,咱們猜測因爲端口號 20000 已經被佔用,新啓動的 client 會因爲調用 bind 函數出錯而退出,咱們實際驗證一下:[root@localhost testsocket]# ./client bind socket error.[root@localhost testsocket]# 結果確實和咱們預想的同樣。在技術面試的時候,有時候面試官會問 TCP 網絡通訊的客戶端程序中的 socket 是否能夠調用 bind 函數,相信讀到這裏,聰明的讀者已經有答案了。另外,Linux 的 nc 命令有個 -p 選項(字母 p 是小寫),這個選項的做用就是 nc 在模擬客戶端程序時,可使用指定端口號鏈接到服務器程序上去,實現原理相信讀者也明白了。咱們仍是以上面的服務器程序爲例,這個咱們不用咱們的 client 程序,改用 nc 命令來模擬客戶端。在 shell 終端輸入:[root@localhost testsocket]# nc -v -p 9999 127.0.0.1 3000Ncat: Version 6.40 ( http://nmap.org/ncat )Ncat: Connected to 127.0.0.1:3000.My name is zhangxfMy name is zhangxf-v 選項表示輸出 nc 命令鏈接的詳細信息,這裏鏈接成功之後,會輸出「Ncat: Connected to 127.0.0.1:3000.」 提示已經鏈接到服務器的 3000 端口上去了。-p 選項的參數值是 9999 表示,咱們要求 nc 命令本地以端口號 9999 鏈接服務器,注意不要與端口號 3000 混淆,3000 是服務器的偵聽端口號,也就是咱們的鏈接的目標端口號,9999 是咱們客戶端使用的端口號。咱們用 lsof 命令來驗證一下咱們的 nc 命令是否確實以 9999 端口號鏈接到 server 進程上去了。[root@localhost testsocket]# lsof -i -PnCOMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAMEserver   1676 root    3u  IPv4  21933      0t0  TCP *:3000 (LISTEN)server   1676 root    7u  IPv4  22405      0t0  TCP 127.0.0.1:3000->127.0.0.1:9999 (ESTABLISHED)nc       2005 root    3u  IPv4  22408      0t0  TCP 127.0.0.1:9999->127.0.0.1:3000 (ESTABLISHED)結果確實如咱們指望的一致。固然,咱們用 nc 命令鏈接上 server 進程之後,咱們還給服務器發了一條消息"My name is zhangxf",server 程序收到消息後把這條消息原封不動地返還給咱們,如下是 server 端運行結果:[root@localhost testsocket]# ./server   recv data from client, data: My name is zhangxf send data to client successfully, data: My name is zhangxf--------------------- 做者:zpznba 來源:CSDN 原文:https://blog.csdn.net/zpznba/article/details/90763798 版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索