套接字描述
套接字是通信端點(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_in 與 sockaddr_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)控制套接字行為
- 通用選項(xiàng),工作在所有套接字類(lèi)型上
- 在套接字層次管理的選項(xiàng)
- 特定于某協(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 不支持