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)中移除.
- 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é)序
}
- 通用套接字地址結(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ù)報)。
-
有連接模式套接字通訊示意圖
當(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();
- 請求連接
請求連接是客戶端的動作,客戶端的套接字是主動套接字,客戶進(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()來指出套接字就緒

