第十六章 網(wǎng)絡(luò)IPC 套接字

套接字描述

套接字是通信端點(diǎn)的抽象

套接字描述符:正如使用文件描述符訪(fǎng)問(wèn)文件,應(yīng)用程序用套接字描述符訪(fǎng)問(wèn)套接字

套接字描述符在 UNIX 系統(tǒng)中被當(dāng)作是一種文件描述符

許多處理文件描述符的 函數(shù)(如 read 和 write)可以用于處理套接字描述符

為創(chuàng)建一個(gè)套接字,調(diào)用 socket 函數(shù)

#include <sys/socket.h>
int socket(int domain,int type,int protocol);

參數(shù) domain(域)確定通信的特性

AF_INET IPv4

AF_INET6 IPv6

AF_UNIX 別名 AF_LOCAL unix域

AF_UPSPEC

參數(shù) type 確定套接字的類(lèi)型,進(jìn)一步確定通信特征

SOCK_DGRAM 默認(rèn)UDP 無(wú)連接 報(bào)文

SOCK_RAW 直接訪(fǎng)問(wèn)下面的網(wǎng)絡(luò)層 應(yīng)用程序負(fù)責(zé)構(gòu)造自己的協(xié)議頭部,這是因?yàn)閭鬏攨f(xié)議(如 TCP 和 UDP) 被繞過(guò)了

SOCK_SEQPACKET 面向連接 報(bào)文

SOCK_STREAM 默認(rèn)tcp 面向連接 字節(jié)流

參數(shù) protocol 通常是 0,表示為給定的域和套接字類(lèi)型選擇默認(rèn)協(xié)議

當(dāng)對(duì)同一域和套接字 類(lèi)型支持多個(gè)協(xié)議時(shí),可以使用 protocol 選擇一個(gè)特定協(xié)議

因特網(wǎng)域套接字定義的協(xié)議:

IPPROTO_IP IPv4

IPPROTO_IPV6 IPv6

IPPROTO_ICMP

IPPROTO_RAW

IPPROTO_TCP tcp

IPPROTO_UDP udp

套接字通信是雙向的??梢圆捎?shutdown 函數(shù)來(lái)禁止一個(gè)套接字的 I/O

#include <sys/socket.h>
int shutdown(int sockfd,int flow);

如果 how 是 SHUT_RD(關(guān)閉讀端),那么無(wú)法從套接字讀取數(shù)據(jù)。
如果 how 是 SHUT_WR(關(guān)閉寫(xiě) 端),那么無(wú)法使用套接字發(fā)送數(shù)據(jù)。
如果 how 是 SHUT_RDWR,則既無(wú)法讀取數(shù)據(jù),又無(wú)法發(fā)送數(shù)據(jù)

尋址

進(jìn)程標(biāo)識(shí):計(jì)算機(jī)的網(wǎng)絡(luò)地址 + 計(jì)算機(jī)上用端口號(hào)表示的服務(wù)

字節(jié)序

big-endian:最大字節(jié)地址出現(xiàn)在最低有效字節(jié)

little-endian:最低有效字節(jié)包含最小字節(jié)地址

不管字節(jié)如何排序,最高有效字節(jié)總是在左邊msb,最低有效字節(jié)總是在右邊lsb

0x04030201:msb包含4,lsb包含1

大端 cp來(lái)存,cp[0]=4

小端 cp來(lái)存,cp[0]=1

網(wǎng)絡(luò)協(xié)議指定了字節(jié)序,TCP/IP 協(xié)議棧使用大端字節(jié)序

對(duì)于 TCP/IP 應(yīng)用程序,有 4 個(gè)用來(lái)在處理器字節(jié)序和網(wǎng)絡(luò)字節(jié)序之間實(shí)施轉(zhuǎn)換的函數(shù)

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32);//返回值:以網(wǎng)絡(luò)字節(jié)序表示的 32 位整數(shù)
uint16_t htons(uint16_t hostint16);//返回值:以網(wǎng)絡(luò)字節(jié)序表示的 16 位整數(shù)
uint32_t ntohl(uint32_t netint32);//返回值:以主機(jī)字節(jié)序表示的 32 位整數(shù)
uint16_t ntohs(uint16_t netint16);//返回值:以主機(jī)字節(jié)序表示的 16 位整數(shù)

h 表示“主機(jī)”字節(jié)序,n 表示“網(wǎng)絡(luò)”字節(jié)序。l 表示“長(zhǎng)”(即 4 字節(jié))整數(shù),s 表示“短” (即 4 字節(jié))整數(shù)

地址格式

為使不同格式地址能夠傳入到套接字函數(shù),地址會(huì)被強(qiáng)制轉(zhuǎn)換成一個(gè)通用的地址結(jié)構(gòu) sockaddr
struct sockaddr{
    sa_family_t sa_family;
    char sa_data[];//linux sa_data[14];
    ...
}
在 IPv4 因特網(wǎng)域(AF_INET)中,套接字 地址用結(jié)構(gòu) sockaddr_in 表示
struct socketaddr_in{
    sa_family_t sin_family;
    in_port_t sin_port;
    struct in_addr sin_addr;
}
struct in_addr{
    in_addr_t s_addr;
}
與 AF_INET 域相比較,IPv6 因特網(wǎng)域(AF_INET6)套接字地址用結(jié)構(gòu) sockaddr_in6 表示
struct socketaddr_in6{
    sa_family_t sin6_family;
    in_port_t sin6_port;
    uint32_t sin6_flowinfo;
    uint32_t sin6_scope_id;
    struct in6_addr sin6_addr;
}
struct in6_addr{
    uint8_t s6_addr[16];
}

盡管 sockaddr_insockaddr_in6 結(jié)構(gòu)相差比較大,但它們均被強(qiáng)制轉(zhuǎn)換成 sockaddr 結(jié)構(gòu)輸入到套接字例程中

二進(jìn)制地址格式與點(diǎn)分十進(jìn)制字符表示(a.b.c.d)之間的相互轉(zhuǎn)換的兩個(gè)函數(shù)

#include <arpa/inet.h>
const char *inet_ntop(int domain,const void *restrict addr,char *restrict str,socketlen_t size);
int inet_pton(int domain,const char *restrict str,void *restrict addr);

參數(shù) domain 僅支持兩個(gè)值:AF_INET 和 AF_INET6

地址查詢(xún)

通過(guò)調(diào)用 gethostent,可以找到給定計(jì)算機(jī)系統(tǒng)的主機(jī)信息

#include <netdb.h>
struct hostent *gethostent(void);
void sethostent(int stayopen);
void endhostent(void);
計(jì)算機(jī)系統(tǒng)的主機(jī)信息
struct hostent{
    
}

能夠采用一套相似的接口來(lái)獲得網(wǎng)絡(luò)名字和網(wǎng)絡(luò)編號(hào)

#include <netdb.h>
struct netent *getnetbyaddr(uint32_t net,int type);
struct netent *getnetbyname(const char *name);
struct netent *getnetent(void);
void setnetent(int stayopen);
void endnetent(void);
網(wǎng)絡(luò)名字和網(wǎng)絡(luò)編號(hào)
struct netent{
    
}

可以用以下函數(shù)在協(xié)議名字和協(xié)議編號(hào)之間進(jìn)行映射

#include <netdb.h>
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);

void setprotoent(int stayopen);
void endprotoent(void);
協(xié)議
struct protoent{
    
}

可以 使用函數(shù) getservbyname 將一個(gè)服務(wù)名映射到一個(gè)端口號(hào),使用函數(shù) getservbyport 將一 個(gè)端口號(hào)映射到一個(gè)服務(wù)名,使用函數(shù) getservent 順序掃描服務(wù)數(shù)據(jù)庫(kù)。

#include <netdb.h>
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getserbyport(int port, const char *proto);
struct servent *getservent(void);

void setservent(int stayopen);
void endservent(void);
服務(wù)名
struct servent{
    
}

getaddrinfo 函數(shù)允許將一個(gè)主機(jī)名和一個(gè)服務(wù)名映射到一個(gè)地址

#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *restrict host,
                const char *restrict service,
                const struct addrinfo *restrict hint,
                struct addrinfo **restrict res);

void freeaddrinfo(struct addrinfo *ai);
struct addrinfo{
    int ai_flags;
    int ai_family;// domain 域
    int ai_socktype;//類(lèi)型
    int ai_protocol;//協(xié)議
    socklen_t ai_addrlen;
    struct sockaddr *ai_addr;
    char *ai_canonname;
    struct addrinfo *ai_next;
    ...
}

可以提供一個(gè)可選的 hint 來(lái)選擇符合特定條件的地址。hint 是一個(gè)用于過(guò)濾地址的模板,包 括 ai_family、ai_flags、ai_protocol 和 ai_socktype 字段

getnameinfo 函數(shù)將一個(gè)地址轉(zhuǎn)換成一個(gè)主機(jī)名和一個(gè)服務(wù)名

#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *restrict addr, 
                socklen_t alen,
                char *restrict host, 
                socklen_t hostlen,
                char *restrict service, 
                socklen_t servlen, 
                int flags);

套接字與地址關(guān)聯(lián)

給一個(gè)接收客戶(hù)端請(qǐng)求的服務(wù)器套接字關(guān)聯(lián)上一個(gè)眾所周知的地址

客戶(hù)端應(yīng) 有一種方法來(lái)發(fā)現(xiàn)連接服務(wù)器所需要的地址,最簡(jiǎn)單的方法就是服務(wù)器保留一個(gè)地址并且注冊(cè)在 /etc/services 或者某個(gè)名字服務(wù)中

使用 bind 函數(shù)來(lái)關(guān)聯(lián)地址和套接字

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);

可以調(diào)用 getsockname 函數(shù)來(lái)發(fā)現(xiàn)綁定到套接字上的地址

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *restrict addr,socklen_t *restrict alenp);
int getpeername(int sockfd, struct sockaddr *restrict addr,socklen_t *restrict alenp);

建立連接

#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socket_t len);

服務(wù)器調(diào)用 listen 函數(shù)來(lái)宣告它愿意接受連接請(qǐng)求。

#include <sys/socket.h>
int listen(int sockfd, int backlog);

參數(shù) backlog 提供了一個(gè)提示,提示系統(tǒng)該進(jìn)程所要入隊(duì)的未完成連接請(qǐng)求數(shù)量

一旦服務(wù)器調(diào)用了 listen,所用的套接字就能接收連接請(qǐng)求。使用 accept 函數(shù)獲得連接 請(qǐng)求并建立連接

#include <sys/socket.h>
accept(int sockefd,struct socketaddr *addr,socklen_t *restrict len);

函數(shù) accept 所返回的文件描述符是套接字描述符,該描述符連接到調(diào)用 connect 的客戶(hù)端

這個(gè)新的套接字描述符和原始套接字(sockfd)具有相同的套接字類(lèi)型和地址族

傳給 accept 的原始套接字沒(méi)有關(guān)聯(lián)到這個(gè)連接,而是繼續(xù)保持可用狀態(tài)并接收其他連接請(qǐng)求

返回時(shí),accept 會(huì)在緩沖區(qū)填充客戶(hù)端的地址,并且更新指向 len 的整數(shù)來(lái)反映該地址的大小

如果沒(méi)有連接請(qǐng)求在等待,accept 會(huì)阻塞直到一個(gè)請(qǐng)求到來(lái)。如果 sockfd 處于非阻塞模式, accept 會(huì)返回?1,并將 errno 設(shè)置為 EAGAIN 或 EWOULDBLOCK

數(shù)據(jù)傳輸

盡管可以通過(guò) read 和 write 交換數(shù)據(jù),但這就是這兩個(gè)函數(shù)所能做的一切

3 個(gè)函數(shù)用來(lái)發(fā)送數(shù)據(jù),3 個(gè)用于接收數(shù)據(jù)

最簡(jiǎn)單的是 send,它和 write 很像,但是可以指定標(biāo)志來(lái)改變處理傳輸數(shù)據(jù)的方式

對(duì)于字節(jié)流協(xié)議,send 會(huì)阻塞直到整個(gè)數(shù)據(jù)傳 輸完成。函數(shù) sendto 和 send 很類(lèi)似。區(qū)別在于 sendto 可以在無(wú)連接的套接字上指定一個(gè)目 標(biāo)地址

#include <sys/socket.h>
ssize_t send(int sockfd,const void *buf,size_t nbytes,int flags);
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags,const struct sockaddr *destaddr, socklen_t destlen);

通過(guò)套接字發(fā)送數(shù)據(jù)時(shí),還有一個(gè)選擇。可以調(diào)用帶有 msghdr 結(jié)構(gòu)的 sendmsg 來(lái)指定多 重緩沖區(qū)傳輸數(shù)據(jù),這和 writev 函數(shù)很相似

#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

函數(shù) recv 和 read 相似,但是 recv 可以指定標(biāo)志來(lái)控制如何接收數(shù)據(jù)。

#include <sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t nbytes,int flags);
ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict addrlen);
//因?yàn)榭梢垣@得發(fā)送者的地址,recvfrom 通常用于無(wú)連接的套接字。否則,recvfrom 等同于recv

為了將接收到的數(shù)據(jù)送入多個(gè)緩沖區(qū),類(lèi)似于 readv,或者想接收輔助數(shù)據(jù),可以使用 recvmsg

#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

套接字選項(xiàng)

套接字機(jī)制提供了兩個(gè)套接字選項(xiàng)接口來(lái)控制套接字行為

  1. 通用選項(xiàng),工作在所有套接字類(lèi)型上
  2. 在套接字層次管理的選項(xiàng)
  3. 特定于某協(xié)議的選項(xiàng)
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
int getsockopt(int sockfd, int level, int option, void *restrict val, socklen_t *restrict lenp);

參數(shù) level 標(biāo)識(shí)了選項(xiàng)應(yīng)用的協(xié)議

帶外數(shù)據(jù)

TCP 支持帶外數(shù) 據(jù),但是 UDP 不支持

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

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

  • 網(wǎng)絡(luò)模型 物理層 物理層表示的是比特流傳輸,通常包括串口/COM口、并行/LPT口、USB、網(wǎng)線(xiàn)接口、電話(huà)線(xiàn)接口;...
    秋風(fēng)弄影閱讀 829評(píng)論 0 2
  • 基于 TCP 的套接字編程的所有客戶(hù)端和服務(wù)器端都是從調(diào)用socket 開(kāi)始,它返回一個(gè)套接字描述符??蛻?hù)端隨后調(diào)...
    我是強(qiáng)強(qiáng)閱讀 474評(píng)論 0 0
  • socket的基本概念 網(wǎng)絡(luò)上的兩個(gè)程序通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱(chēng)為一個(gè)socket。...
    小葉大孟閱讀 773評(píng)論 0 0
  • 文章首發(fā)于個(gè)人blog歡迎指正補(bǔ)充,可聯(lián)系lionsom_lin@qq.com原文地址:《網(wǎng)絡(luò)是怎樣連接的》閱讀整...
    Lucus_Linx閱讀 14,469評(píng)論 6 31
  • 姓名:沈華立 公司:慈溪市創(chuàng)鑫車(chē)輛零部件有限公司 六項(xiàng)精進(jìn)224期利他二組學(xué)員 【日精進(jìn)打卡273天】 【知~學(xué)習(xí)...
    沈華立閱讀 154評(píng)論 0 0

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