1. 什么是socket套接字
套接字就像一個(gè)插座,插座需要一個(gè)插頭來連接雙方才能通電,而socket通信也需要兩個(gè)端,一個(gè)服務(wù)端一個(gè)客戶端。一般來說,服務(wù)端是被動的,客戶端是主動的,也就是說服務(wù)端應(yīng)該先啟動,啟動之后就被動的去準(zhǔn)備被(客戶端)連接以提供服務(wù),而客戶端需要服務(wù)的時(shí)候就主動去連接服務(wù)器端。
實(shí)際上,socket編程就是網(wǎng)絡(luò)IO編程,同樣也是讀寫操作,只不過是對網(wǎng)絡(luò)進(jìn)行讀寫,通過read/write和文件描述符來完成讀寫。我們在創(chuàng)建套接字的時(shí)候,會得到文件描述符,然后就可以通過這個(gè)文件描述符來完成讀寫操作。
實(shí)際上,我們在進(jìn)程間通信時(shí)用的管道也是在內(nèi)核中分配一塊緩沖區(qū),這個(gè)緩沖區(qū)是用一個(gè)環(huán)形隊(duì)列來維護(hù)的,本質(zhì)是內(nèi)存中的一塊存儲空間,在管道的讀寫兩端分別對應(yīng)一個(gè)文件描述符,操作讀端的文件描述符fd就相當(dāng)于操作內(nèi)核緩沖區(qū)。
套接字創(chuàng)建成功后,也會得到一個(gè)文件描述符fd,通過fd來操作一塊內(nèi)核緩沖區(qū)。在服務(wù)器端創(chuàng)建一個(gè)套接字,就會得到一個(gè)內(nèi)核緩沖區(qū)和文件描述符,這個(gè)緩沖區(qū)分為讀寫兩部分。在客戶端發(fā)數(shù)據(jù)使用的是write操作,當(dāng)我們執(zhí)行write(fd)的時(shí)候,數(shù)據(jù)并不是直接寫到網(wǎng)上的,而是先寫到文件描述符對應(yīng)的內(nèi)核緩沖區(qū)中的寫緩沖區(qū)部分,寫緩沖區(qū)中只要有數(shù)據(jù)就會自動發(fā)送到服務(wù)器端的讀緩沖區(qū)中,服務(wù)器端通過read就可以把數(shù)據(jù)讀出。我們所做的只有read和write操作,其他操作都是由操作系統(tǒng)完成的。需要注意的一點(diǎn)是,讀緩沖區(qū)中的數(shù)據(jù)讀走了之后就沒有了,和管道一樣。
套接字對應(yīng)的文件描述符默認(rèn)也是阻塞的,實(shí)際上阻塞是文件描述符對應(yīng)的文件所擁有的性質(zhì),而不是read/write的屬性,這兩個(gè)函數(shù)只負(fù)責(zé)讀取或者寫數(shù)據(jù),即阻塞性質(zhì)是對文件描述符所對應(yīng)的文件類型而言的。
?2. socket編程
- socket是一套網(wǎng)絡(luò)通信的函數(shù)接口
- TCP
- UDP
socket編程就是使用別人提供的一套網(wǎng)絡(luò)通信接口進(jìn)行編程。比如說我們使用瀏覽器搜索內(nèi)容,瀏覽器使用的是HTTP協(xié)議,而HTTP協(xié)議再往下封裝的就是TCP協(xié)議。
在套接字編程時(shí)需要IP和Port:
- IP地址:在網(wǎng)絡(luò)環(huán)境中,需要IP來定位一臺主機(jī)
- 端口號Port:在一臺主機(jī)上,需要Port來定位一個(gè)進(jìn)程
- IP:Port
?3. 網(wǎng)絡(luò)字節(jié)序
- 大端:網(wǎng)絡(luò)字節(jié)序,數(shù)據(jù)的高位字節(jié)存儲在內(nèi)存的低地址。
- 小端:主機(jī)字節(jié)序,數(shù)據(jù)的高位字節(jié)存儲在內(nèi)存的高位地址。常見的主機(jī)數(shù)據(jù)都是小端存儲。
函數(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
假如說我們要將小端字節(jié)序轉(zhuǎn)換為大端字節(jié)序,如果主機(jī)是小端字節(jié)序,這些函數(shù)將參數(shù)做相應(yīng)的大小端轉(zhuǎn)換后返回,如果主機(jī)是大端字節(jié)序,這些函數(shù)將不做任何變換,將參數(shù)原封不動的返回。
常見的文件字節(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(大端方式存儲)
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位端口號和32位IP地址,IPv6地址用socketaddr_in6結(jié)構(gòu)體表示,包括16位端口號、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ù)用來產(chǎn)生socket的協(xié)議,使用TCP或UDP來傳輸,使用IPv4的地址;
- AF_INET6:使用IPv6的地址;
- AF_UNIX:本地協(xié)議,使用在Unix和Linux系統(tǒng)上,一般都是當(dāng)客戶端和服務(wù)器端都在同一臺機(jī)器上的時(shí)候使用;
- type
- SOCK_STREAM:流式協(xié)議,這個(gè)協(xié)議是按照順序的、可靠的、數(shù)據(jù)類型完整的、基于字節(jié)流的連接。這是一個(gè)使用最多的socket類型,這個(gè)socket是使用TCP來進(jìn)行傳輸?shù)模?/p>
- SOCK_DGRAM:報(bào)式協(xié)議,這個(gè)協(xié)議式無連接的、固定長度的傳輸調(diào)用,該協(xié)議是不可靠的,使用UDP進(jìn)行傳輸;
- SOCK_SEQPACKET:該協(xié)議是雙線路的、可靠的連接,發(fā)送固定長度的數(shù)據(jù)包進(jìn)行傳輸,必須把這個(gè)包完整的接收才能進(jìn)行讀?。?/p>
- SOCK_RAW:socket類型提供單一的網(wǎng)絡(luò)訪問,這個(gè)socket類型使用ICMP公共協(xié)議,ping以及traceroute都使用該協(xié)議;
- SOCK_RDM:這個(gè)類型使用較少,在大部分操作系統(tǒng)上沒有實(shí)現(xiàn),它提供給數(shù)據(jù)鏈路層使用,不保證數(shù)據(jù)包的順序;
- protocol:設(shè)置0表示使用默認(rèn)協(xié)議;協(xié)議,常見的協(xié)議有IPPROTO_TCP、IPPTOTO_UDP、 IPPROTO_SCTP、IPPROTO_TIPC他們分別對應(yīng)這TCP傳輸協(xié)議、UDP傳輸協(xié)議、STCP傳輸協(xié)議、TIPC傳輸協(xié)議。當(dāng)protocol為0時(shí),會自動選擇type類型對應(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和端口號與創(chuàng)建出來的套接字綁定,將參數(shù)sockfd和addr綁定在一起,使sockfd這個(gè)用于網(wǎng)絡(luò)通訊的文件描述符監(jiān)聽addr所描述的地址和端口號。
- sockfd:創(chuàng)建出的文件描述符
- addr:端口和IP
- addrlen:addr結(jié)構(gòu)體的長度,sizeof(addr)
(3) 監(jiān)聽
int listen(int sockfd, int backlog);
- 設(shè)置同時(shí)連接到服務(wù)器的客戶端的個(gè)數(shù),listen()聲明sockfd處于監(jiān)聽狀態(tài),并且最多允許有backlog個(gè)客戶端處于連接等待狀態(tài),如果接收到更多的連接請求就忽略。
- sockfd:socket函數(shù)創(chuàng)建出來的文件描述符;
- backlog:同時(shí)能連接的最大數(shù)量,最大值為128;
(4) 接受連接
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
- 阻塞等待客戶端連接請求,并接受連接。
- sockfd:文件描述符,使用socket創(chuàng)建出來的文件描述符;
- 監(jiān)聽的文件描述符;
- addr:存儲客戶端的端口和IP,是一個(gè)傳出參數(shù);
- addrlen:傳入傳出參數(shù)(值 - 結(jié)果),傳入sizeof(addr)的大小,函數(shù)返回時(shí)返回真正接收到地址結(jié)構(gòu)體的大??;
- 函數(shù)返回值是一個(gè)套接字,對應(yīng)客戶端,服務(wù)器端與客戶端進(jìn)程通信使用accept的返回值對應(yīng)的套接字。
(5) 連接
int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
- 客戶端需要調(diào)用connect()函數(shù)連接服務(wù)器,connect和bind的參數(shù)形式一致,區(qū)別在于bind的參數(shù)是自己的地址,而connect的參數(shù)是對方的地址。
- sockfd:套接字;
- addr:傳入?yún)?shù),指定服務(wù)器端地址信息,服務(wù)器端的IP和端口;
- addrlen:第二個(gè)參數(shù)addr的長度;