網(wǎng)絡(luò)編程

網(wǎng)絡(luò)編程中的驚群現(xiàn)象

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

  • 套接字是應(yīng)用程序和底層網(wǎng)絡(luò)協(xié)議之間的一個通信端口,這意味著想要讀取來至網(wǎng)絡(luò)的數(shù)據(jù)就需要讀取套接字,想要寫數(shù)據(jù)到網(wǎng)絡(luò)就要寫套接字,想要控制網(wǎng)絡(luò)協(xié)議選項就要設(shè)置套接字。從程序員的角度來講,套接字等價于網(wǎng)絡(luò),就像文件描述符是磁盤操作的一個端口一樣。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
/*@param
 * doamin--指明通信域,決定通信使用的網(wǎng)絡(luò)協(xié)議族,協(xié)議族不同,地址結(jié)構(gòu)也不同
 *       -AF_UNIX:UNIX通信域,即同一臺計算機(jī)內(nèi)兩個進(jìn)程通過文件系統(tǒng)進(jìn)行通信
 *    要求文件系統(tǒng)的路徑名作為套接字的地址
 *       -AF_INET:網(wǎng)絡(luò)通信,使用IPv4;要求32位IPv4地址
 *       -AF_INET6:網(wǎng)絡(luò)通信,使用IPv6;
 *
 * type--指明套接字的類型,套接字的類型指明通信的語義。
 *     -SOCK_STREAM:字節(jié)流套接字。(TCP)
 *     -SOCK_DGRAM:數(shù)據(jù)報套接字。(UDP)
 *     -SOCK_RAW:raw套接字,主要用于越過高層協(xié)議而直接訪問低層協(xié)議,使程序員
 *   可以直接使用IP協(xié)議或者是網(wǎng)絡(luò)的物理層。
 *
 * protocol--domain參數(shù)確定套接字采用什么協(xié)議族,而此參數(shù)在給定的協(xié)議族內(nèi)選
 *    選擇一種具體的協(xié)議。對于給定的通信域內(nèi)的每一種套接字類型,一般只存在
 *    一種協(xié)議,在這種情況之下,只要指明了通信域和通信類型,協(xié)議就是唯一的,
 *    一般都將此參數(shù)置為0,即讓系統(tǒng)選擇默認(rèn)的協(xié)議。
 */

該函數(shù)在通信域domain中創(chuàng)建一個類型為type、使用協(xié)議protocol的套接字,并返回一個套接字描述字。

int socketpair(int domain,int type,int protocol,int filedes[2]);

socket()創(chuàng)建的只是一個套接字,若通信的兩個進(jìn)程是由fork()派生的具有共同祖先的進(jìn)程,則可以使用socketpair();

#include <sys/socket.h> 
int socketpair(int domain,int type,int protocol,int filedes[2]);
/*若type指明是有鏈接的類型,得到的兩個套接字是已連接的,若指明了一個
 *   無連接的類型,得到的兩個套接字是無連接的,但是由于他們都知道
 *   對方的存在,因此可以互相發(fā)送數(shù)據(jù)包。該函數(shù)通常用于父子進(jìn)程之間
 *   通信,與無名管道非常的相似。
 */

該函數(shù)創(chuàng)建一對未命名的套接字,返回這對套接字描述符于filedes[0]和filedes[1]當(dāng)中,套接字偶對是一個全雙工的通信信道,其在兩端均可執(zhí)行讀和寫操作。fork之后須在一端關(guān)閉另一端使用的描述字。

close()和shutdown()

#include <unistd.h>
int close(int socket);

#include <sys/socket.h>
int shutdown(int socket,int how);
/*
 *how--指明執(zhí)行哪一種關(guān)閉動作可以為下列值:
 *    -SHUT_RD:停止從此套接字接收數(shù)據(jù),若還有數(shù)據(jù)到達(dá),則拒收。 
 *    -SHUT_WR:停止從此套接字發(fā)送數(shù)據(jù),忽略任何等待發(fā)送的數(shù)據(jù),
 *   停止查詢已經(jīng)發(fā)送的數(shù)據(jù)的有關(guān)消息,若已發(fā)送數(shù)據(jù)丟失則不在重新發(fā)送它。
 *    -SHUT_DOWN:停止從該套接字發(fā)送和接收數(shù)據(jù)。
*/

close()導(dǎo)致該套接字被銷毀,當(dāng)此套接字是面向連接的且還有數(shù)據(jù)等待傳送時,close()會嘗試完成傳送。有時我們并不需要關(guān)閉這個套接字,而是斷開其連接,則可以使用shutdown()來做。

套接字地址結(jié)構(gòu)

在Internet通信域當(dāng)中,套接字地址由主機(jī)的IP地址加上端口號組成。
程序中分別使用兩種不同的數(shù)據(jù)類型來表示32位和128位的IP地址

#include <netinet/in.h>
typedef unit32_t in_addr_t;
struct in_addr {in_addr_t s_addr;};
struct in6_addr {uint8 s6_addr[16]; };

UNIX提供了如下的一組函數(shù)將點-數(shù)表示的IPv4地址或者是冒號表示的IPv6地址轉(zhuǎn)換成按照網(wǎng)絡(luò)字節(jié)序表示的二進(jìn)制整數(shù)形式或者反之。
#include <apra/inet.h>
int inet_aton(const char *name,struct in_addr *addr);
/*將標(biāo)準(zhǔn)數(shù)-點表示的IPv4地址轉(zhuǎn)換為二進(jìn)制形式,并將結(jié)果存儲在addr中*/

char *inet_ntoa(struct in_addr inaddr);
/*轉(zhuǎn)換32位IPv4地址為標(biāo)準(zhǔn)的數(shù)-點表示字符串,存儲該字符串在一個靜態(tài)分配的緩存區(qū)當(dāng)中,并返回指向該緩沖區(qū)的指針。后繼調(diào)用將會覆蓋同一個緩沖區(qū),因此當(dāng)需要保存其內(nèi)容時應(yīng)該將其復(fù)制到別處*/

int inet_pton(int family,const char *nameptr, void *addrptr);
const char *inet_ntop(int family, void *addrptr, char *nameptr, size_t len);
/*這兩個函數(shù)是為了IPv6新加的即可轉(zhuǎn)換v6也可轉(zhuǎn)換v4,由family取值A(chǔ)F_INET還是AF_INET6來指定.*/

域名地址

UNIX內(nèi)部使用一個主機(jī)地址數(shù)據(jù)庫來記錄主機(jī)名和IP地址之間的映射,這一數(shù)據(jù)庫由文件/etc/hosts或者是域名服務(wù)系統(tǒng)提供.
gethostbyname()和gethostbyaddr()用來從該數(shù)據(jù)庫中獲得一臺主機(jī)的完整地址信息,包括主機(jī)名和IP地址.

#include<netdb.h>
struct hostent *gethostbyname(const char *name)
/*返回主機(jī)name的地址信息,失敗返回NULL,成功則保存這些信息在一個靜態(tài)分配的hostent結(jié)構(gòu)當(dāng)中,并返回該結(jié)構(gòu)的地址,若需保存,則復(fù)制出來.*/
struct hostent *gethostbyaddr(const void *addr,size_t length ,int type);

服務(wù)與端口號

Internet通信域當(dāng)中的套接字地址由機(jī)器的IP地址加上端口號組成,IP地址是唯一標(biāo)識網(wǎng)絡(luò)中的一臺計算機(jī)的,端口號則區(qū)別一臺機(jī)器中的不同的服務(wù)進(jìn)程.
在UNIX系統(tǒng)中存在一個專門記錄標(biāo)準(zhǔn)服務(wù)的數(shù)據(jù)庫,由文件/etc/services或者是域名服務(wù)器提供.定義在<netdb.h>中的servent結(jié)構(gòu)當(dāng)中,用于表示服務(wù)數(shù)據(jù)庫的登記項信息.

struct servent{
char *s_name;
char *s_aliases;
int s_port;
char *s_proto;
}
#include <netdb.h>
struct servent *getservbyname(const char *name,const char *proto);
//返回使用協(xié)議proto且名為name的服務(wù)的有關(guān)信息.

struct servent *getserbyport(int port,const char *proto);
//返回使用協(xié)議proto且位于端口port的服務(wù)的有關(guān)信息.

套接字地址數(shù)據(jù)結(jié)構(gòu)

現(xiàn)在我們已經(jīng)知道了表示IP地址的數(shù)據(jù)結(jié)構(gòu)in_addr和in6_addr,知道了如何從主機(jī)名獲得字符串形式的IP地址,已經(jīng)如何將字符串形式的IP地址轉(zhuǎn)換為32位或者是128位的數(shù)值形式,同時也知道了如何獲取端口號,由此便可以順利的形成套接字的地址.
1.UNIX通信域的套接字地址結(jié)構(gòu)

#include <sys/un.h>
struct sockaddr_un{
sa_family_t sun_family;
char sun_path[];//給出unix文件的路徑名,不能與文件系統(tǒng)中的其他文件同.
}

每一種通信域套接字地址結(jié)構(gòu)中的第一個成員都相同,他們指出套接字的地址族。對于sockaddr_un結(jié)構(gòu)類型,該成員的值只能是AF_UNIX。由sun_path命名的文件是在調(diào)用bind的時候創(chuàng)建的.此時內(nèi)核為他在文件系統(tǒng)中創(chuàng)建一個inode.所以應(yīng)該在關(guān)閉一個命令套接字之后,還應(yīng)該調(diào)用unlink()或者是remove()將其從文件系統(tǒng)中移除.

  1. IPv4和IPv6通信域套接字地址結(jié)構(gòu)
    IPv4套接字地址的類型是sockaddr_in結(jié)構(gòu),它至少由如下一些成員.表示IPv6的套接字地址的數(shù)據(jù)類型是sockaddr_in6結(jié)構(gòu),他至少有如下的成員:
#include <netinet/in.h>
struct sockaddr_in{
sa_family_t sin_family;        //套接字地址族或格式,值應(yīng)該為AF_INET
in_port_t sin_port;               //16位端口號,網(wǎng)絡(luò)字節(jié)序
struct in_addr sin_addr;      //32位IP地址,網(wǎng)絡(luò)字節(jié)序
unsigned char sin_zero[8];  //保留未用
}

struct sockaddr_in6{
sa_family_t sin6_family;      //AF_INET6
in_port_t sin6_port;              // 16位端口
uint32_t sin6_flowinfo;         //IPv6流標(biāo)簽和優(yōu)先級信號,網(wǎng)絡(luò)字節(jié)序
struct in6_addr sin6_addr;   //128位IPv6,網(wǎng)絡(luò)字節(jié)序
}
  1. 通用套接字地址結(jié)構(gòu)
    許多的套接字函數(shù)都要求指明套接字的地址,為了既能夠支持不同的協(xié)議族,又有統(tǒng)一的接口,這些函數(shù)使用指向結(jié)構(gòu)類型為sockaddr的指針作為參數(shù):
struct sockaddr{
sa_family_t sa_family;
char sa_data[];
}

例如:
struct sockaddr_un name;
name.sun_family = AF_UNIX;
strcpy(name.sun_path,filename);
bind(sockfd,(struct aockaddr *)&name, sizeof(name));

字節(jié)順序

數(shù)據(jù)是存放在存儲器當(dāng)中的,存放數(shù)據(jù)的基本單元是字節(jié)通常4個字節(jié)或者是八個字節(jié)組成一個機(jī)器字.一般與計算機(jī)的寄存器的大小相同.

當(dāng)將一個數(shù)存放在存儲器當(dāng)中時,有兩種存放順序,一種將一個數(shù)當(dāng)中最有意義的字節(jié)(MSB)防在存儲器編號較小的字節(jié)(稱為little-endian順序,因為數(shù)的最小意義位在后面.).另外一些計算機(jī)將最小意義字節(jié)(LSB)放在存儲器編號較小的字節(jié)(稱為big-endian順序,因為數(shù)的最有意義位在后面 )
大端模式:數(shù)據(jù)的結(jié)尾部分位于地址的高位.
小端模式:數(shù)據(jù)的結(jié)尾部分位于地址的地位.

例如:

兩臺計算機(jī)通過套接字通信時,若各自使用不同的字節(jié)順序?qū)?dǎo)致數(shù)據(jù)的解釋出現(xiàn)問題,Internet協(xié)議采用統(tǒng)一的big-endian字節(jié)順序,稱之為網(wǎng)絡(luò)字節(jié)序。
因此,當(dāng)建立一個Internet套接字連接時,必須要保證在sockaddr_in中的sin_port和sin_addr域當(dāng)中給出的數(shù)據(jù)是網(wǎng)絡(luò)字節(jié)序的。如果封裝一個整形數(shù)據(jù)在一個消息當(dāng)中并通過套接字發(fā)送出去,則也應(yīng)該將其轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序使用getservbyname()和gethostbyname()或者是inet_aton()獲得的端口號和機(jī)器地址已經(jīng)是網(wǎng)絡(luò)字節(jié)序的,可以直接使用。否則必須使用專門的函數(shù)顯式的轉(zhuǎn)換。

#include <arpa/inet.h>
unit16_t htos (unit16_t hostshort)//用于轉(zhuǎn)換sin_port

unit32_t htol(unit32_t hostlong) // 轉(zhuǎn)換sin_addr

unit16_t ntohs (uint16_t netshort) //用于轉(zhuǎn)換sin_port

unit32_t ntohl (uint32_t netlong); // 

命名套接字

用socket()創(chuàng)建的套接字只是系統(tǒng)中的一個沒有名字的資源,其他的進(jìn)程無法訪問,也無法從他接收消息,僅僅當(dāng)程序通過調(diào)用bind()給套接字指定了一個名字之后其他的進(jìn)程才能找到他。只有對外提供服務(wù)的套接字才需要bind();

int bind(int socket,const struct sockaddr *address,socklet_t address_len);

套接字通信模式

支持兩種通信模式:有連接的(流)和無連接的(數(shù)據(jù)報)。

  1. 有連接模式套接字通訊示意圖

    當(dāng)服務(wù)程序啟動的時候,先調(diào)用socket()創(chuàng)建一個套接字,之后調(diào)用bind()給該套接字命名一個眾所周知的名字,以便其他的程序能夠與之進(jìn)行通訊。命名之后,等待客戶端的連接,在多個客戶端同時連接時,調(diào)用listen()為進(jìn)入的連接創(chuàng)建一個連接隊列。通過調(diào)用accept()逐一的接收這些連接。
    服務(wù)器每一次調(diào)用accept()都會創(chuàng)建一個新的套接字,它不同于之前的已經(jīng)命名的套接字,這個新的套接字完全只用于和特定的客戶端通信

無連接模式套接字通訊示意圖

和有鏈接的套接字通信方式不同的是,無連接的套接字通信以對等的方式交換數(shù)據(jù)。盡管通信的兩個進(jìn)程仍然可能分為客戶和服務(wù)進(jìn)程,客戶和服務(wù)都必須要先創(chuàng)建套接字。

流套接字操作

在這種通信方式中,bind允許服務(wù)進(jìn)程給予一個套接字地址,并建立連接的一方,客戶進(jìn)程通過調(diào)用connect(),而服務(wù)經(jīng)常調(diào)用accept()來完成連接。我們稱一對已經(jīng)連接的套接字為對等套接字,其中請求服務(wù)的一方為主動套接字,等待連接請求的套接字為被動套接字。一個套接字在開始的時候是主動的,并且只有在調(diào)用了listen之后才會成為被動。只有主動套接字可以用于connect(),只有被動套接字可以用于accept();

  1. 請求連接
    請求連接是客戶端的動作,客戶端的套接字是主動套接字,客戶進(jìn)程通過調(diào)用connect()建立主動套接字與有名的被動套接字之間的連接。
#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, socklen address_len);

connect()調(diào)用成功時,套接字socket被連接到address給出的地址,后者必須是已經(jīng)存在的服務(wù)程序套接字。connect()等待直到服務(wù)程序回答了連接請求,或者是等待時間超過了某個時間限制之后才返回。若超時返回,connect()將會失敗并且流產(chǎn)連接請求。如果connect()在阻塞期間由于收到信號而被中斷,它將失敗并置errno為EINTR,但是連接請求并不會流產(chǎn),而是被異步的建立??梢詫μ捉幼謘ocket設(shè)置非阻塞方式,從而使得connect不等待回答就直接返回。對套接字設(shè)置非阻塞方式的方法和文件描述字一樣使用fcntl();若對套接字socket()設(shè)置了非阻塞標(biāo)志O_NONBLOCK,當(dāng)連接不能夠被立即建立的時候,connect將失敗并置errno為EINPROGRESS但是連接請求不是流產(chǎn),而是被異步的建立。在連接建立之前,后繼對同一個套接字調(diào)用connect()將會導(dǎo)致失敗。
對于異步建立的連接,可以使用select()和poll()來指出套接字就緒

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容