socket接口詳解

1. socket概述

socket是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層複雜的操作抽象爲幾個簡單的接口供應用層調用已實現進程在網絡中通信。

socket起源於UNIX,在Unix一切皆文件哲學的思想下,socket是一種"打開—讀/寫—關閉"模式的實現,服務器和客戶端各自維護一個"文件",在建立連接打開後,可以向自己文件寫入內容供對方讀取或者讀取對方內容,通訊結束時關閉文件。

2.接口詳解

socket():創建socket

bind():綁定socket到本地地址和端口,通常由服務端調用

listen():TCP專用,開啓監聽模式

accept():TCP專用,服務器等待客戶端連接,一般是阻塞態

connect():TCP專用,客戶端主動連接服務器

send():TCP專用,發送數據

recv():TCP專用,接收數據

sendto():UDP專用,發送數據到指定的IP地址和端口

recvfrom():UDP專用,接收數據,返回數據遠端的IP地址和端口

closesocket():關閉socket

2.1 socket()

原型:int socket (int domain, int type, int protocol)

功能描述:初始化創建socket對象,通常是第一個調用的socket函數。 成功時,返回非負數的socket描述符;失敗是返回-1。socket描述符是一個指向內部數據結構的指針,它指向描述符表入口。調用socket()函數時,socket執行體將建立一個socket,實際上"建立一個socket"意味着爲一個socket數據結構分配存儲空間。socket執行體爲你管理描述符表。

參數解釋

domain -- 指明使用的協議族。常用的協議族有,AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通信中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作爲地址。

type -- 指明socket類型,有3種:

                                                 SOCK_STREAM -- TCP類型,保證數據順序及可靠性;

                                                 SOCK_DGRAM --  UDP類型,不保證數據接收的順序,非可靠連接;

                                                 SOCK_RAW -- 原始類型,允許對底層協議如IP或ICMP進行直接訪問,不太常用。

protocol -- 通常賦值"0",由系統自動選擇。

2.2 bind()

原型:int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen)

功能描述:將創建的socket綁定到指定的IP地址和端口上,通常是第二個調用的socket接口。返回值:0 -- 成功,-1 -- 出錯。當socket函數返回一個描述符時,只是存在於其協議族的空間中,並沒有分配一個具體的協議地址(這裏指IPv4/IPv6和端口號的組合),bind函數可以將一組固定的地址綁定到sockfd上。

通常服務器在啓動的時候都會綁定一個衆所周知的協議地址,用於提供服務,客戶就可以通過它來接連服務器;而客戶端可以指定IP或端口也可以都不指定,未分配則系統自動分配。這就是爲什麼通常服務器端在listen之前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。

注意

(1) 如果有多個可用的連接(多個IP),內核會根據優先級選擇一個IP作爲源IP使用。

(2) 如果socket使用bind綁定到特定的IP和port,則無論是TCP還是UDP,都會從指定的IP和port發送數據。

參數解釋

sockfd -- socket()函數返回的描述符;

myaddr -- 指明要綁定的本地IP和端口號,使用網絡字節序,即大端模式(詳見3.1)。

addrlen -- 常被設置爲sizeof(struct sockaddr)。

可以利用下邊的賦值語句,自動綁定本地IP地址和隨機端口:

my_addr.sin_port = 0;    /* 系統隨機選擇一個未被使用的端口號 */ 
my_addr.sin_addr.s_addr = INADDR_ANY;   /* 填入本機IP地址 */ 

另外要注意的是,當調用函數時,一般不要將端口號置爲小於1024的值,因爲1~1024是保留端口號,你可以使用大於1024中任何一個沒有被佔用的端口號。

2.3 listen()

原型:int listen(int sockfd, int backlog)

功能描述:listen()函數僅被TCP類型的服務器程序調用,實現監聽服務,它實現2件事情:

“1. 當socket()創建1個socket時,被假設爲主動式套接字,也就是說它是一個將調用connect()發起連接請求的客戶端套接字;函數listen()將套接口轉換爲被動式套接字,指示內核接受向此套接字的連接請求,調用此係統調用後tcp 狀態機由close轉換到listen。
   2.第2個參數指定了內核爲此套接字排隊的最大連接個數。”

listen()成功時返回0,錯誤時返回-1。

參數解釋

sockfd -- socket()函數返回的描述符;

backlog -- 指定內核爲此套接字維護的最大連接個數,包括“未完成連接隊列--未完成3次握手”、“已完成連接隊列--已完成3次握手,建立連接”。大多數系統缺省值爲20。

2.4 accept()

原型: int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen)

功能描述:accept()函數僅被TCP類型的服務器程序調用,從已完成連接隊列返回下一個建立成功的連接,如果已完成連接隊列爲空,線程進入阻塞態睡眠狀態。成功時返回套接字描述符,錯誤時返回-1。

如果accpet()執行成功,返回由內核自動生成的一個全新socket描述符,用它引用與客戶端的TCP連接。通常我們把accept()第一個參數成爲監聽套接字(listening socket),把accept()功能返回值成爲已連接套接字(connected socket)。一個服務器通常只有1個監聽套接字,監聽客戶端的連接請求;服務器內核爲每一個客戶端的TCP連接維護1個已連接套接字,用它實現數據雙向通信。

參數解釋

sockfd -- socket()函數返回的描述符;

addr -- 輸出一個的sockaddr_in變量地址,該變量用來存放發起連接請求的客戶端的協議地址;

addrten -- 作爲輸入時指明緩衝器的長度,作爲輸出時指明addr的實際長度。

2.5 connetct()

原型: int connect(int sockfd, struct sockaddr *serv_addr, int addrlen)

功能描述:connect()通常由TCP類型客戶端調用,用來與服務器建立一個TCP連接,實際是發起3次握手過程,連接成功返回0,連接失敗返回1。

注意

(1) 可以在UDP連接使用使用connect(),作用是在UDP套接字中記住目的地址和目的端口。
(2) UDP套接字使用connect後,如果數據報不是connect中指定的地址和端口,將被丟棄。沒有調用connect的UDP套接字,將接收所有到達這個端口的UDP數據報,而不區分源端口和地址。

參數解釋

sockfd -- 本地客戶端額socket描述符;

serv_addr -- 服務器協議地址;

addrlen -- 地址緩衝區的長度。

2.6 send()

原型:int send(int sockfd, const void *msg, int len, int flags)

功能描述:TCP類型的數據發送。

每個TCP套接口都有一個發送緩衝區,它的大小可以用SO_SNDBUF這個選項來改變。調用send函數的過程,實際是內核將用戶數據拷貝至TCP套接口的發送緩衝區的過程:若len大於發送緩衝區大小,則返回-1;否則,查看緩衝區剩餘空間是否容納得下要發送的len長度,若不夠,則拷貝一部分,並返回拷貝長度(指的是非阻塞send,若爲阻塞send,則一定等待所有數據拷貝至緩衝區才返回,因此阻塞send返回值必定與len相等);若緩衝區滿,則等待發送,有剩餘空間後拷貝至緩衝區;若在拷貝過程出現錯誤,則返回-1。關於錯誤的原因,查看errno的值。

如果send在等待協議發送數據時出現網絡斷開的情況,則會返回-1。注意:send成功返回並不代表對方已接收到數據,如果後續的協議傳輸過程中出現網絡錯誤,下一個send便會返回-1發送錯誤。TCP給對方的數據必須在對方給予確認時,方可刪除發送緩衝區的數據。否則,會一直緩存在緩衝區直至發送成功(TCP可靠數據傳輸決定的)。

參數解釋

sockfd -- 發送端套接字描述符(非監聽描述符)。

msg -- 待發送數據的緩衝區。

len -- 待發送數據的字節長度。

flags -- 一般情況下置爲0。

2.7 recv()

原型:int recv(int sockfd, void *buf, int len, unsigned int flags)

功能描述:TCP類型的數據接收。

recv()從接收緩衝區拷貝數據。成功時,返回拷貝的字節數,失敗返回-1。阻塞模式下,recv/recvfrom將會阻塞到緩衝區裏至少有一個字節(TCP)/至少有一個完整的UDP數據報才返回,沒有數據時處於休眠狀態。若非阻塞,則立即返回,有數據則返回拷貝的數據大小,否則返回錯誤-1。

參數解釋

sockefd -- 接收端套接字描述符(非監聽描述符);

buf -- 接收緩衝區的基地址;

len -- 以字節計算的接收緩衝區長度;

flags -- 一般情況下置爲0。

2.8 sendto()

原型:int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *dst_addr, int addrlen)

功能描述:用於非可靠連接(UDP)的數據發送,因爲UDP方式未建立連接socket,因此需要制定目的協議地址。

當本地與不同目的地址通信時,只需指定目的地址,可使用同一個UDP套接口描述符sockfd,而TCP要預先建立連接,每個連接都會產生不同的套接口描述符,體現在:客戶端要使用不同的fd進行connect,服務端每次accept產生不同的fd。

因爲UDP沒有真正的發送緩衝區,因爲是不可靠連接,不必保存應用進程的數據拷貝,應用進程中的數據在沿協議棧向下傳遞時,以某種形式拷貝到內核緩衝區,當數據鏈路層把數據傳出後就把內核緩衝區中數據拷貝刪除。因此它不需要一個發送緩衝區。寫UDP套接口的sendto/write返回表示應用程序的數據或數據分片已經進入鏈路層的輸出隊列,如果輸出隊列沒有足夠的空間存放數據,將返回錯誤ENOBUFS.

參數解釋

sockfd -- 發送端套接字描述符(非監聽描述符);

msg -- 待發送數據的緩衝區;

len -- 待發送數據的字節長度;

flags -- 一般情況下置爲0;

dst_addr -- 數據發送的目的地址;

addrlen -- 地址長度。

2.9 recvfrom()

原型:int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, int*fromlen)

功能描述:用於非可靠連接(UDP)的數據接收。

參數解釋

sockfd -- 接收端套接字描述;

buf -- 用於接收數據的應用緩衝區地址;

len -- 指名緩衝區大小;

flags -- 通常爲0;

src_addr -- 數據來源端的地址;

fromlen -- 作爲輸入時,fromlen常置爲sizeof(struct sockaddr);當輸出時,fromlen包含實際存入buf中的數據字節數。

3.TCP例程

給出1個TCP類型socket通信的源代碼,運行於windows平臺。

3.1服務器端源碼

 

複製代碼

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>

#pragma comment(lib,"ws2_32.lib")

int main(int argc, char* argv[])
{
    //初始化WSA
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA wsaData;
    if(WSAStartup(sockVersion, &wsaData)!=0)
    {
        return 0;
    }

    //創建套接字
    SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(slisten == INVALID_SOCKET)
    {
        printf("socket error !");
        return 0;
    }

    //綁定IP和端口
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(8888);
    sin.sin_addr.S_un.S_addr = INADDR_ANY; 
    if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        printf("bind error !");
    }

    //開始監聽
    if(listen(slisten, 5) == SOCKET_ERROR)
    {
        printf("listen error !");
        return 0;
    }

    //循環接收數據
    SOCKET sClient;
    sockaddr_in remoteAddr;
    int nAddrlen = sizeof(remoteAddr);
    char revData[255]; 
    while (true)
    {
        printf("等待連接...\n");
        sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
        if(sClient == INVALID_SOCKET)
        {
            printf("accept error !");
            continue;
        }
        printf("接受到一個連接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
        
        //接收數據
        int ret = recv(sClient, revData, 255, 0);        
        if(ret > 0)
        {
            revData[ret] = 0x00;
            printf(revData);
        }

        //發送數據
        char * sendData = "你好,TCP客戶端!\n";
        send(sClient, sendData, strlen(sendData), 0);
        closesocket(sClient);
    }
    
    closesocket(slisten);
    WSACleanup();
    return 0;
}

複製代碼

 

3.2 客戶端源碼

複製代碼

#include "stdafx.h"
#include <WINSOCK2.H>
#include <STDIO.H>

#pragma  comment(lib,"ws2_32.lib")


int main(int argc, char* argv[])
{
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA data; 
    if(WSAStartup(sockVersion, &data) != 0)
    {
        return 0;
    }

    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sclient == INVALID_SOCKET)
    {
        printf("invalid socket !");
        return 0;
    }

    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(8888);
    serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 
    if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {
        printf("connect error !");
        closesocket(sclient);
        return 0;
    }
    char * sendData = "你好,TCP服務端,我是客戶端!\n";
    send(sclient, sendData, strlen(sendData), 0);

    char recData[255];
    int ret = recv(sclient, recData, 255, 0);
    if(ret > 0)
    {
        recData[ret] = 0x00;
        printf(recData);
    }
    closesocket(sclient);
    WSACleanup();
    return 0;
}

複製代碼

4.UDP例程

4.1 服務器端源碼

 

複製代碼

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib") 

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    WORD sockVersion = MAKEWORD(2,2);
    if(WSAStartup(sockVersion, &wsaData) != 0)
    {
        return 0;
    }

    SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 
    if(serSocket == INVALID_SOCKET)
    {
        printf("socket error !");
        return 0;
    }

    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(8888);
    serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
    if(bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {
        printf("bind error !");
        closesocket(serSocket);
        return 0;
    }
    
    sockaddr_in remoteAddr;
    int nAddrLen = sizeof(remoteAddr); 
    while (true)
    {
        char recvData[255];  
        int ret = recvfrom(serSocket, recvData, 255, 0, (sockaddr *)&remoteAddr, &nAddrLen);
        if (ret > 0)
        {
            recvData[ret] = 0x00;
            printf("接受到一個連接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
            printf(recvData);            
        }

        char * sendData = "一個來自服務端的UDP數據包\n";
        sendto(serSocket, sendData, strlen(sendData), 0, (sockaddr *)&remoteAddr, nAddrLen);    

    }
    closesocket(serSocket); 
    WSACleanup();
    return 0;
}

複製代碼

 

4.2 客戶端源碼

複製代碼

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib") 

int main(int argc, char* argv[])
{
    WORD socketVersion = MAKEWORD(2,2);
    WSADATA wsaData; 
    if(WSAStartup(socketVersion, &wsaData) != 0)
    {
        return 0;
    }
    SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(8888);
    sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    int len = sizeof(sin);
    
    char * sendData = "來自客戶端的數據包.\n";
    sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)&sin, len);

    char recvData[255];     
    int ret = recvfrom(sclient, recvData, 255, 0, (sockaddr *)&sin, &len);
    if(ret > 0)
    {
        recvData[ret] = 0x00;
        printf(recvData);
    }

    closesocket(sclient);
    WSACleanup();
    return 0;
}

複製代碼

5. 參考資料

1. 常用socket函數詳解

2. 網絡編程socket基本API詳解

3. VC++ socket通信實例詳解

轉自:https://www.cnblogs.com/yuqiao/p/5786427.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章