歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux下的socket編程實踐(二)socket編程基本API簡介

Linux下的socket編程實踐(二)socket編程基本API簡介

日期:2017/3/1 12:24:11   编辑:關於Linux

Socket是什麼

socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現, socket即是一種特殊的文件,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉)。 說白了Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

Socket可以看成是用戶進程與內核網絡協議棧的接口(編程接口, 如下圖所示), 其不僅可以用於本機進程間通信,可以用於網絡上不同主機的進程間通信, 甚至還可以用於異構系統之間的通信。

\

如上圖TCP/IP協議棧已經屬於內核的一部分了,被實現好了,路由器工作於網絡層(Router),Application是需要我們去實現的。Socket可以看作是用戶進程和內核網絡協議棧的編程接口,可以把socket看作進程間通信的一種方式,和管道不同,他是全雙工的,可用於本機和不同主機之間的進程間通信,異構通信也是可以的。(例如手機和電腦,分別是ARM和X86架構)

Pv4套接口地址結構

IPv4套接口地址結構通常也稱為“網際套接字地址結構”,它以“sockaddr_in”命名,定義在頭文件

struct sockaddr_in  
{  
    uint8_t  sin_len;  
    sa_family_t  sin_family;  
    in_port_t   sin_port;   //2字節  
    struct in_addr  sin_addr;   //4字節  
    char sin_zero[8];   //8字節  
};  

成員說明:

sin_len:整個sockaddr_in結構體的長度,在4.3BSD-Reno版本之前的第一個成員是sin_family.

sin_family:指定該地址家族,對於IPv4來說必須設為AF_INET(Socket不僅可以用於TCP/IP還可以用於UNIX域協議)

sin_port:端口

sin_addr:IPv4的地址;

sin_zero:暫不使用,一般將其設置為0

通用地址結構

用來指定與套接字關聯的地址(可以支持其他協議).

struct sockaddr  
{  
    uint8_t  sin_len;  
    sa_family_t  sin_family;  
    char sa_data[14];   //14字節     
};  

說明:

sin_len:整個sockaddr結構體的長度

sin_family:指定該地址家族

sa_data:由sin_family決定它的形式。

注意:使用的時候通常把IPv4的地址結構強制轉換成通用地址結構,就像上面的sockaddr_in 轉換為sockaddr

網絡字節序

大端字節序和小端字節序 的出現是為了異構系統之間的使用

1.大端字節序(Big Endian)

最高有效位(MSB:Most Significant Bit)存儲於最低內存地址處,最低有效位(LSB:Lowest Significant Bit)存儲於最高內存地址處。

2.小端字節序(Little Endian)

最高有效位(MSB:Most Significant Bit)存儲於最高內存地址處,最低有效位(LSB:Lowest Significant Bit)存儲於最低內存地址處。

3.主機字節序

不同的主機有不同的字節序,如x86為小端字節序,Motorola 6800為大端字節序,ARM字節序是可配置的。

4.網絡字節序

網絡字節序規定為大端字節序

\

判斷自己主機的字節序是哪一種?

//測試當前系統是否為小端模式  
int main()  
{  
    int data = 0x12345678;  //int = 4字節(32位)  
                            //每4個二進制位代表1位十六進制位,  
                            //則8位十六進制位代表4*8=32位二進制位  
    char *p = (char *)&data;  
    printf("%x, %x, %x, %x\n",p[0],p[1],p[2],p[3]);  
  
    //0x78屬於低位,如果其放在了p[0](低地址)處,則說明是小端模式  
    if (p[0] == 0x78)  
    {  
        cout << "當前系統為小端模式" << endl;    //x86平台為小端模式  
    }  
    else if (p[0] == 0x12)  
    {  
        cout << "當前系統為大端模式" << endl;    //IBM為大端模式  
    }  
}  
如果是小端模式,那麼原串輸出是 78 56 34 12

字節序轉換函數(常用於端口轉換)

uint32_t htonl(uint32_t hostlong);  
uint16_t htons(uint16_t hostshort);  
uint32_t ntohl(uint32_t netlong);  
uint16_t ntohs(uint16_t netshort);  
/**說明: 
h代表(local)host;n代表network; 
s代表short;l代表long; 
*/  
//測試轉換結果  
int main()  
{  
    int localeData = 0x12345678;  
    char *p = (char *)&localeData;  
    printf("Begin: %0x %0x %0x %0x\n", p[0], p[1], p[2], p[3]);  
    //將本地字節轉換成網絡字節  
    int inetData = htonl(localeData);  
    p = (char *)&inetData;  
    printf("After: %0x %0x %0x %0x\n", p[0], p[1], p[2], p[3]);  
  
    if (p[0] == 0x12)  
        cout << "網絡系統為大端模式" << endl;  
    else  
        cout << "網絡系統為小端模式" << endl;  
    printf("host:%x, inet:%x\n", localeData, inetData);  
}  
地址轉換函數(用於IP地址轉換)

#include   
#include   
int inet_aton(const char *cp, struct in_addr *inp);  
in_addr_t inet_addr(const char *cp);      
char *inet_ntoa(struct in_addr in);  

//in_addr定義如下:  
typedef uint32_t in_addr_t;  
struct in_addr  
{  
    in_addr_t s_addr;  
};  
//實踐  
int main()  
{  
    //將點分十進制轉換成十進制數  
    cout << inet_addr("192.168.139.137") << endl;  
  
    //將十進制數轉換成點分十進制形式  
    struct in_addr address;  
    address.s_addr = inet_addr("192.168.139.137");  
    cout << inet_ntoa(address) << endl;  
  
    memset(&address,0,sizeof(address));  
    inet_aton("127.0.0.1", &address);  
    cout << address.s_addr << endl;  
    cout << inet_ntoa(address) << endl;  
    return 0;  
}  

套接字類型

1)流式套接字(SOCK_STREAM)

提供面向連接的、可靠的數據傳輸服務,數據無差錯,無重復的發送,且按發送順序接收, 對應TCP協議。

2)數據報式套接字(SOCK_DGRAM)

提供無連接服務。不提供無錯保證,數據可能丟失或重復,並且接收順序混亂, 對應UDP協議。

3)原始套接字(SOCK_RAW)

使我們可以跨越傳輸層直接對IP層進行封裝傳輸。(應用層直接和IP層)

\

socket函數

#include   
#include   
int socket(int domain, int type, int protocol);  

創建一個套接字用於通信

參數:

domain:指定通信協議族(protocol family),常用取值AF_INET(IPv4)

type:指定socket類型, 流式套接字SOCK_STREAM,數據報套接字SOCK_DGRAM,原始套接字SOCK_RAW

protocol:協議類型,常用取值0, 使用默認協議

返回值:

成功: 返回非負整數,套接字;

失敗: 返回-1

bind函數

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

綁定一個本地地址到套接字

參數:

sockfd:socket函數返回的套接字

addr:要綁定的地址

//sockaddr_in結構, bind時需要強制轉換成為struct sockaddr*類型  
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 */  
};  

/**示例:INADDR_ANY的使用, 綁定本機任意地址**/  
int main()  
{  
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);  
    if (listenfd == -1)  
        err_exit("socket error");  
  
    struct sockaddr_in addr;  
    addr.sin_family = AF_INET;  
    addr.sin_port = htons(8001);  
    //綁定本機的任意一個IP地址, 作用同下面兩行語句  
    addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    //inet_aton("127.0.0.1", &addr.sin_addr);  
    //addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
    if (bind(listenfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)  
        err_exit("bind error");  
    else  
        cout << "bind success" << endl;  
}  

listen函數

int listen(int sockfd, int backlog);  

listen函數應該用在調用socket和bind函數之後, 並且用在調用accept之前, 用於將一個套接字從一個主動套接字轉變成為被動套接字。

backlog說明:

對於給定的監聽套接口,內核要維護兩個隊列:

1、已由客戶發出並到達服務器,服務器正在等待完成相應的TCP三路握手過程(SYN_RCVD狀態)

2、已完成連接的隊列(ESTABLISHED狀態)

但是兩個隊列長度之和不能超過backlog

\

backlog推薦使用SOMAXCONN(3.13.0-44-generic中該值為128), 使用等待隊列的最大值;

bind之後變成了被動套接字,接受連接;主動套接字是用來發起連接,例如使用connect。

accept函數

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

從已完成連接隊列返回第一個連接(the first connection request on the queue of pending connections for the listening

socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket. The newly created socket is not in the listening state),如果已完成連接隊列為空,則阻塞。The original socket sockfd is unaffected by this call.

參數:

sockfd:服務器套接字

addr:將返回對等方的套接字地址, 不關心的話, 可以設置為NULL

addrlen:返回對等方的套接字地址長度, 不關心的話可以設置成為NULL, 否則一定要初始化

返回值: On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket. On error, -1 is returned, and errno is set appropriately.

connect函數

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

建立一個連接至addr所指定的套接字

參數:

sockfd:未連接套接字

addr:要連接的套接字地址

addrlen:第二個參數addr長度

示例:echo server/client實現

//server端代碼  
int main()  
{  
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);  
    if (listenfd == -1)  
        err_exit("socket error");  
  
    struct sockaddr_in addr;  
    addr.sin_family = AF_INET;  
    addr.sin_port = htons(8001);  
    addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    if (bind(listenfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)  
        err_exit("bind error");  
    if (listen(listenfd, SOMAXCONN) == -1)  
        err_exit("listen error");  
  
    char buf[512];  
    int readBytes;  
    struct sockaddr_in clientAddr;  
    //謹記: 此處一定要初始化  
socklen_t addrLen = sizeof(clientAddr);  
    while (true)  
    {  
        int clientfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);  
        if (clientfd == -1)  
            err_exit("accept error");  
        //打印客戶IP地址與端口號  
        cout << "Client information: " << inet_ntoa(clientAddr.sin_addr)  
             << ", " << ntohs(clientAddr.sin_port) << endl;  
  
        memset(buf, 0, sizeof(buf));  
        while ((readBytes = read(clientfd, buf, sizeof(buf))) > 0)  
        {  
            cout << buf;  
            if (write(clientfd, buf, readBytes) == -1)  
                err_exit("write socket error");  
            memset(buf, 0, sizeof(buf));  
        }  
        if (readBytes == 0)  
        {  
            cerr << "client connect closed..." << endl;  
            close(clientfd);  
        }  
        else if (readBytes == -1)  
            err_exit("read socket error");  
    }  
    close(listenfd);  
}  

//client端代碼  
int main()  
{  
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    if (sockfd == -1)  
        err_exit("socket error");  
  
    //填寫服務器端口號與IP地址  
    struct sockaddr_in serverAddr;  
    serverAddr.sin_family = AF_INET;  
    serverAddr.sin_port = htons(8001);  
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
    if (connect(sockfd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)  
        err_exit("connect error");  
  
    char buf[512];  
    while (fgets(buf, sizeof(buf), stdin) != NULL)  
    {  
        if (write(sockfd, buf, strlen(buf)) == -1)  
            err_exit("write socket error");  
        memset(buf, 0, sizeof(buf));  
        int readBytes = read(sockfd, buf, sizeof(buf));  
        if (readBytes == 0)  
        {  
            cerr << "server connect closed... \nexiting..." << endl;  
            break;  
        }  
        else if (readBytes == -1)  
            err_exit("read socket error");  
        cout << buf;  
        memset(buf, 0, sizeof(buf));  
    }  
    close(sockfd);  
}  
Copyright © Linux教程網 All Rights Reserved