1. 什么是socket套接字
套接字就像一個(gè)插座,插座需要一個(gè)插頭來(lái)連接雙方才能通電,而socket通信也需要兩個(gè)端,一個(gè)服務(wù)端一個(gè)客戶(hù)端。一般來(lái)說(shuō),服務(wù)端是被動(dòng)的,客戶(hù)端是主動(dòng)的,也就是說(shuō)服務(wù)端應(yīng)該先啟動(dòng),啟動(dòng)之后就被動(dòng)的去準(zhǔn)備被(客戶(hù)端)連接以提供服務(wù),而客戶(hù)端需要服務(wù)的時(shí)候就主動(dòng)去連接服務(wù)器端。
實(shí)際上,socket編程就是網(wǎng)絡(luò)IO編程,同樣也是讀寫(xiě)操作,只不過(guò)是對(duì)網(wǎng)絡(luò)進(jìn)行讀寫(xiě),通過(guò)read/write和文件描述符來(lái)完成讀寫(xiě)。我們?cè)趧?chuàng)建套接字的時(shí)候,會(huì)得到文件描述符,然后就可以通過(guò)這個(gè)文件描述符來(lái)完成讀寫(xiě)操作。
實(shí)際上,我們?cè)谶M(jìn)程間通信時(shí)用的管道也是在內(nèi)核中分配一塊緩沖區(qū),這個(gè)緩沖區(qū)是用一個(gè)環(huán)形隊(duì)列來(lái)維護(hù)的,本質(zhì)是內(nèi)存中的一塊存儲(chǔ)空間,在管道的讀寫(xiě)兩端分別對(duì)應(yīng)一個(gè)文件描述符,操作讀端的文件描述符fd就相當(dāng)于操作內(nèi)核緩沖區(qū)。
套接字創(chuàng)建成功后,也會(huì)得到一個(gè)文件描述符fd,通過(guò)fd來(lái)操作一塊內(nèi)核緩沖區(qū)。在服務(wù)器端創(chuàng)建一個(gè)套接字,就會(huì)得到一個(gè)內(nèi)核緩沖區(qū)和文件描述符,這個(gè)緩沖區(qū)分為讀寫(xiě)兩部分。在客戶(hù)端發(fā)數(shù)據(jù)使用的是write操作,當(dāng)我們執(zhí)行write(fd)的時(shí)候,數(shù)據(jù)并不是直接寫(xiě)到網(wǎng)上的,而是先寫(xiě)到文件描述符對(duì)應(yīng)的內(nèi)核緩沖區(qū)中的寫(xiě)緩沖區(qū)部分,寫(xiě)緩沖區(qū)中只要有數(shù)據(jù)就會(huì)自動(dòng)發(fā)送到服務(wù)器端的讀緩沖區(qū)中,服務(wù)器端通過(guò)read就可以把數(shù)據(jù)讀出。我們所做的只有read和write操作,其他操作都是由操作系統(tǒng)完成的。需要注意的一點(diǎn)是,讀緩沖區(qū)中的數(shù)據(jù)讀走了之后就沒(méi)有了,和管道一樣。
套接字對(duì)應(yīng)的文件描述符默認(rèn)也是阻塞的,實(shí)際上阻塞是文件描述符對(duì)應(yīng)的文件所擁有的性質(zhì),而不是read/write的屬性,這兩個(gè)函數(shù)只負(fù)責(zé)讀取或者寫(xiě)數(shù)據(jù),即阻塞性質(zhì)是對(duì)文件描述符所對(duì)應(yīng)的文件類(lèi)型而言的。
?2. socket編程
- socket是一套網(wǎng)絡(luò)通信的函數(shù)接口
- TCP
- UDP
socket編程就是使用別人提供的一套網(wǎng)絡(luò)通信接口進(jìn)行編程。比如說(shuō)我們使用瀏覽器搜索內(nèi)容,瀏覽器使用的是HTTP協(xié)議,而HTTP協(xié)議再往下封裝的就是TCP協(xié)議。
在套接字編程時(shí)需要IP和Port:
- IP地址:在網(wǎng)絡(luò)環(huán)境中,需要IP來(lái)定位一臺(tái)主機(jī)
- 端口號(hào)Port:在一臺(tái)主機(jī)上,需要Port來(lái)定位一個(gè)進(jìn)程
- IP:Port
?3. 網(wǎng)絡(luò)字節(jié)序
- 大端:網(wǎng)絡(luò)字節(jié)序,數(shù)據(jù)的高位字節(jié)存儲(chǔ)在內(nèi)存的低地址。
- 小端:主機(jī)字節(jié)序,數(shù)據(jù)的高位字節(jié)存儲(chǔ)在內(nèi)存的高位地址。常見(jiàn)的主機(jī)數(shù)據(jù)都是小端存儲(chǔ)。
函數(shù)介紹:
#include <arpa/inet.h>
(1) 主機(jī)字節(jié)序轉(zhuǎn)網(wǎng)絡(luò)字節(jié)序
uint16_t htons(uint16_t hostshort); //端口
uint32_t htonl(uint32_t hostlong); //IP
(2) 網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)主機(jī)字節(jié)序
uint16_t ntohs(uint16_t netshort); //端口
uint32_t ntohl(uint32_t netlong); //IP
假如說(shuō)我們要將小端字節(jié)序轉(zhuǎn)換為大端字節(jié)序,如果主機(jī)是小端字節(jié)序,這些函數(shù)將參數(shù)做相應(yīng)的大小端轉(zhuǎn)換后返回,如果主機(jī)是大端字節(jié)序,這些函數(shù)將不做任何變換,將參數(shù)原封不動(dòng)的返回。
常見(jiàn)的文件字節(jié)序:
- Adobe PS --- Big Endian
- BMP --- Little Endian
- GIF --- Little Endian
- JPEG --- Big Endian
- MacPaint --- Big Endian
- RTF --- Little Endian
注:在Java以及所有的網(wǎng)絡(luò)通訊協(xié)議都是使用Big-Endian編碼。
?4. IP地址轉(zhuǎn)換函數(shù)
指定IP轉(zhuǎn)換為點(diǎn)分十進(jìn)制字符串
- 本地IP轉(zhuǎn)網(wǎng)絡(luò)字節(jié)序:字符串 ---> int(大端方式存儲(chǔ))
int inet_pton(int af, const char* src, void* dst);
- af:地址簇協(xié)議
- src:點(diǎn)分十進(jìn)制IP
- dest:傳出參數(shù),轉(zhuǎn)換后的int整形的存放地址
- 網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)本地IP:int ---> 字符串
const char *inet_ntop(int af, const void* src, char* dst, socklen_t size);
5. sockaddr數(shù)據(jù)結(jié)構(gòu)
- sockaddr
- sockaddrin
- sockaddrun
struct sockaddr {
/* address family, AF_xxx */
sa_family_t sa_family;
/* 14 bytes of protocol address */
char sa_data[14];
};
struct sockaddr_in {
__kernel_sa_family_t sin_family; // 地址族協(xié)議
__be16 sin_port; // 端口
struct in_addr sin_addr; // IP地址
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct in_addr {
__be32 s_addr;
};
IPv4地址用socketaddr_in結(jié)構(gòu)體表示,包括16位端口號(hào)和32位IP地址,IPv6地址用socketaddr_in6結(jié)構(gòu)體表示,包括16位端口號(hào)、128位IP地址和一些控制字段。
6. 網(wǎng)絡(luò)套接字函數(shù)
(1) 創(chuàng)建套接字
int?socket(int?domain,?int?type,?int?protocol);
- 創(chuàng)建一個(gè)套接字
- domin
- AF_INET:這是大多數(shù)用來(lái)產(chǎn)生socket的協(xié)議,使用TCP或UDP來(lái)傳輸,使用IPv4的地址;
- AF_INET6:使用IPv6的地址;
- AF_UNIX:本地協(xié)議,使用在Unix和Linux系統(tǒng)上,一般都是當(dāng)客戶(hù)端和服務(wù)器端都在同一臺(tái)機(jī)器上的時(shí)候使用;
- type
- SOCK_STREAM:流式協(xié)議,這個(gè)協(xié)議是按照順序的、可靠的、數(shù)據(jù)類(lèi)型完整的、基于字節(jié)流的連接。這是一個(gè)使用最多的socket類(lèi)型,這個(gè)socket是使用TCP來(lái)進(jìn)行傳輸?shù)模?/p>
- SOCK_DGRAM:報(bào)式協(xié)議,這個(gè)協(xié)議式無(wú)連接的、固定長(zhǎng)度的傳輸調(diào)用,該協(xié)議是不可靠的,使用UDP進(jìn)行傳輸;
- SOCK_SEQPACKET:該協(xié)議是雙線路的、可靠的連接,發(fā)送固定長(zhǎng)度的數(shù)據(jù)包進(jìn)行傳輸,必須把這個(gè)包完整的接收才能進(jìn)行讀?。?/p>
- SOCK_RAW:socket類(lèi)型提供單一的網(wǎng)絡(luò)訪問(wèn),這個(gè)socket類(lèi)型使用ICMP公共協(xié)議,ping以及traceroute都使用該協(xié)議;
- SOCK_RDM:這個(gè)類(lèi)型使用較少,在大部分操作系統(tǒng)上沒(méi)有實(shí)現(xiàn),它提供給數(shù)據(jù)鏈路層使用,不保證數(shù)據(jù)包的順序;
- protocol:設(shè)置0表示使用默認(rèn)協(xié)議;協(xié)議,常見(jiàn)的協(xié)議有IPPROTO_TCP、IPPTOTO_UDP、 IPPROTO_SCTP、IPPROTO_TIPC他們分別對(duì)應(yīng)這TCP傳輸協(xié)議、UDP傳輸協(xié)議、STCP傳輸協(xié)議、TIPC傳輸協(xié)議。當(dāng)protocol為0時(shí),會(huì)自動(dòng)選擇type類(lèi)型對(duì)應(yīng)的默認(rèn)協(xié)議;
- 返回值為文件描述符(套接字),即創(chuàng)建好的socket套接字的文件描述符。On success, a file descriptor for ?the ?new ?socket ?is ?returned. ? On error, -1 is returned, and errno is set appropriately.
(2) 綁定
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
- 將本地的IP和端口號(hào)與創(chuàng)建出來(lái)的套接字綁定,將參數(shù)sockfd和addr綁定在一起,使sockfd這個(gè)用于網(wǎng)絡(luò)通訊的文件描述符監(jiān)聽(tīng)addr所描述的地址和端口號(hào)。
- sockfd:創(chuàng)建出的文件描述符
- addr:端口和IP
- addrlen:addr結(jié)構(gòu)體的長(zhǎng)度,sizeof(addr)
(3) 監(jiān)聽(tīng)
int listen(int sockfd, int backlog);
- 設(shè)置同時(shí)連接到服務(wù)器的客戶(hù)端的個(gè)數(shù),listen()聲明sockfd處于監(jiān)聽(tīng)狀態(tài),并且最多允許有backlog個(gè)客戶(hù)端處于連接等待狀態(tài),如果接收到更多的連接請(qǐng)求就忽略。
- sockfd:socket函數(shù)創(chuàng)建出來(lái)的文件描述符;
- backlog:同時(shí)能連接的最大數(shù)量,最大值為128;
(4) 接受連接
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
- 阻塞等待客戶(hù)端連接請(qǐng)求,并接受連接。
- sockfd:文件描述符,使用socket創(chuàng)建出來(lái)的文件描述符;
- 監(jiān)聽(tīng)的文件描述符;
- addr:存儲(chǔ)客戶(hù)端的端口和IP,是一個(gè)傳出參數(shù);
- addrlen:傳入傳出參數(shù)(值 - 結(jié)果),傳入sizeof(addr)的大小,函數(shù)返回時(shí)返回真正接收到地址結(jié)構(gòu)體的大??;
- 函數(shù)返回值是一個(gè)套接字,對(duì)應(yīng)客戶(hù)端,服務(wù)器端與客戶(hù)端進(jìn)程通信使用accept的返回值對(duì)應(yīng)的套接字。
(5) 連接
int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
- 客戶(hù)端需要調(diào)用connect()函數(shù)連接服務(wù)器,connect和bind的參數(shù)形式一致,區(qū)別在于bind的參數(shù)是自己的地址,而connect的參數(shù)是對(duì)方的地址。
- sockfd:套接字;
- addr:傳入?yún)?shù),指定服務(wù)器端地址信息,服務(wù)器端的IP和端口;
- addrlen:第二個(gè)參數(shù)addr的長(zhǎng)度;