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