unix网络编程
寫在前面: 最近在看nginx具體接口的實現(xiàn),發(fā)現(xiàn)一些網(wǎng)絡(luò)接口不是很熟悉,大概看了下Unix網(wǎng)絡(luò)編程,發(fā)現(xiàn)上面都有具體介紹。后續(xù)這段時間攻讀下這本教程。
記于 2018-1-30
第三章 ? 套接字編程簡介
1. IPv4套接字地址結(jié)構(gòu)
IPv4套接字地址結(jié)構(gòu)通常也稱為“”網(wǎng)際套接字地址結(jié)構(gòu)“”, 他以sockaddr_in命名, 定義在<netinet/in.h>中
struct in_addr{in_addr_t s_addr; //32-bit IPv4 address, network byte ordered};struct sockaddr_in{uint8_t sin_len; //length of structure(16)sa_family_t sin_family; //AF_INETin_port_t sin_port; //16-bit TCP or UDP port numberstruct in_addr sin_addr; char sin_zero; //unused};
2. 通用套接字地址結(jié)構(gòu)
當(dāng)作為一個參數(shù)傳遞進(jìn)任何套接字函數(shù)時, 套接字地址結(jié)構(gòu)總是以引用形式(也就是指向該結(jié)構(gòu)的指針)來傳遞,然而以這樣的指針作為參數(shù)之一的任何套接字函數(shù)必須處理來自所支持的任何協(xié)議族的套接字地址結(jié)構(gòu)
//<sys/socket.h>struct sockaddr { uint8_t sa_len; sa_family_t sa_family; //address family:AF_xxx value char sa_data[14]; //portocol-specific address };3. IPv6 套接字地址結(jié)構(gòu)
//<netinet/in.h> struct in6_addr{ uint8_t s6_addr[16]; //128-bit IPv6 address network byte ordered };#define SIN6_LEN; //required for compile-time testsstruct sockaddr_in6{ uint8_t sin6_len; //length of this struct sa_family_t sin6_family; //AF_INET6 in_port_t sin6_port; //transport layer port//network byte ordered uint32_t sin6_flowinfo; //flow information undefined struct in6_addr sin6_addr; //IPv6 address uint32_t sin6_scope_id; //set of interfaces for a scope };4. 套接字地址結(jié)構(gòu)是在進(jìn)程和內(nèi)核之間傳遞的
從進(jìn)程到內(nèi)核傳遞套接字地址結(jié)構(gòu)的函數(shù)有3個:bind 、connect、sendto
從內(nèi)核到進(jìn)程傳遞套接字地址結(jié)構(gòu)的函數(shù)有4個: accept、recvfrom、getsockname、getpeername
5. 術(shù)語“小端”和“大端”表示多個字節(jié)值的哪一端(小端或大端)存儲在該值的起始位置
我們把某個給定系統(tǒng)所用的字節(jié)序稱為主機(jī)字節(jié)序(host byte order)
主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換函數(shù)
#include<netinet/in.h>uint16_t htons(uint16_t host16bitvalue); uint32_t htonl(uint32_t host32bitvalue);//均返回:網(wǎng)絡(luò)字節(jié)序的值uint16_t ntohs(uint16_t net16bitvalue); uint32_t ntohl(uint32_t net32bitvalue);//均返回:主機(jī)字節(jié)序的值其中: h代表host, n代表network, s 代表short, l 代表long6. 字節(jié)操作函數(shù)
名字以b開頭的函數(shù)起源于4.2BSD, 名字以mem(表示內(nèi)存)開頭的函數(shù)起源于ANSI C標(biāo)準(zhǔn)
下面是Berkeley函數(shù):
#include<strings.h>void bzero(void* dest, size_t nbytes); void bcopy(const void* src, void* dest, size_t nbytes); void bcmp(const void* ptr1, const void* ptr2, size_t nbytes); //返回:若相等則為0, 否則為非0下面是ANSI C函數(shù):
#include<string.h>void* memset(void* dest, int c, size_t len); void* memcpy(void* dest, const void* src, size_t nbytes); int memcmp(const void* ptr1, const void* ptr2, size_t nbytes); //若相等則為0,否則為>0或<07. inet_aton 、inet_addr、 inet_ntoa 函數(shù)
#include<arpa/inet.h>int inet_aton(const char* strptr, struct in_addr* addrptr); //返回:若字符串有效則為1, 否則為0in_addr_t inet_addr(const char* strptr); //返回:若字符串有效則為32位二進(jìn)制網(wǎng)絡(luò)字節(jié)序的IPv4地址,否則為INADDR_NONEchar* inet_ntoa(struct in_addr inaddr); //返回:指向一個點分十進(jìn)制數(shù)串的指針inet_aton將strptr所指C字符串轉(zhuǎn)換成一個32位的網(wǎng)絡(luò)字節(jié)序二進(jìn)制值,并通過指針addrptr來存儲。
8. inet_pton 和 inet_ntop 函數(shù)
這兩個函數(shù)是隨IPv6出現(xiàn)的新函數(shù)。函數(shù)名中p和n分別代表表達(dá)(presentation)和數(shù)值(numeric)。
地址的表達(dá)式格式通常是ANSII字符串,數(shù)值格式則是存放到套接字地址結(jié)構(gòu)中的二進(jìn)制值
#include<arpa/inet.h>int inet_pton(int family, const char* strptr, void* addrptr); //返回:若成功則為1, 若輸入不是有效的表達(dá)式格式則為0, 若出錯則為-1const char* inet_ntop(int family, const void* addrptr, char* strptr, size_t len); //返回:若成功則為指向結(jié)果的指針,若出錯則為NULL注: 表達(dá)格式就是 點分十進(jìn)制格式, 數(shù)值格式就是in_addr{} 格式
表達(dá)格式就是在一個IPv4 的點分十進(jìn)制數(shù)串之后,或者在一個括以方括號的IPv6地址的十六進(jìn)制數(shù)串格式之后,跟一個終止符(分號),再跟一個十進(jìn)制的端口號,最后跟一個空字符。
因此,緩沖區(qū)大小對于IPv4至少為INET_ADDRSTRLEN加上6個字節(jié)(16+6=22),對于IPv6至少為INET6_ADDRSTRLEN加上8個字節(jié)(46+8=54)
9. sock_ntop和相關(guān)函數(shù)
#include"unp.h" char* sock_ntop(const struct sockaddr* sockaddr, socklen_t addlen);sockaddr指向一個長度為addrlen的套接字地址結(jié)構(gòu)。
10. 字節(jié)流套接字
字節(jié)流套接字(如TCP套接字)上的read和write函數(shù)所表現(xiàn)的行為不同于通常的文件I/O, 字節(jié)流套接字上調(diào)用read和write輸入或輸出的字節(jié)數(shù)可能比請求的數(shù)量少,然而這不是出錯的狀態(tài),這個現(xiàn)象的原因在于內(nèi)核中用于套接字的緩沖區(qū)可能已經(jīng)達(dá)到了極限。此時所需的是調(diào)用者再次調(diào)用read和write函數(shù)。
第四章 ? 基本TCP套接字編程
1. socket函數(shù)
#include<sys/socket.h>int socket(int family, int type, int portocol); //返回:若成功則為非負(fù)描述符,若出錯則為-1family 參數(shù)指明協(xié)議族, type指明套接字類型。 protocol設(shè)為某個協(xié)議類型常值,或者設(shè)為0
| family | 說明 |
| AF_INET | IPv4協(xié)議 |
| AF_INET6 | IPv6協(xié)議 |
| AF_LOCAL | UNIX域協(xié)議 |
| AF_ROUTE | 路由套接字 |
| AF_KEY | 秘鑰套接字 |
| type | 說明 |
| SOCK_STREAM | 字節(jié)流套接字 |
| SOCK_DGRAM | 數(shù)據(jù)報套接字 |
| SOCK_SEQPACKET | 有序分組套接字 |
| SOCK_RAW | 原始套接字 |
| protocol | 說明 |
| IPPROTO_TCP | TCP傳輸協(xié)議 |
| IPPROTO_UDP | UDP傳輸協(xié)議 |
| IPPROTO_SCTP | STCP傳輸協(xié)議 |
2. connect函數(shù)
#include<sys/socket.h> int connect(int sockfd, const struct sockaddr* seraddr, socklen_t addrlen);返回: 若成功則為0, 若出錯則為-1
按照TCP狀態(tài)轉(zhuǎn)換圖,connect函數(shù)導(dǎo)致當(dāng)前套接字從CLOSED 狀態(tài)(該套接字自從由socket函數(shù)創(chuàng)建以來一直所處的狀態(tài))轉(zhuǎn)移到SYN_SENT狀態(tài),若成功則再轉(zhuǎn)移到ESTABLISHED狀態(tài)。
若connect失敗則該套接字不再可用,必須關(guān)閉,我們不能對這樣的套接字再次調(diào)用connect函數(shù)。
3. bind函數(shù)
bind函數(shù)把一個本地協(xié)議地址賦予一個套接字
?
#include<sys/socket.h> int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen);對于TCP, 調(diào)用bind函數(shù)可以指定一個端口號,或指定一個IP地址,也可以兩者都指定,還可以都不指定
如果一個TCP客戶或者服務(wù)器未曾調(diào)用bind捆綁一個端口,當(dāng)調(diào)用connect或listen時,內(nèi)核就要為相應(yīng)的套接字選擇一個臨時端口。讓內(nèi)核來選擇臨時端口對于TCP客戶來說是正常的,除非應(yīng)用需要一個預(yù)留端口。
| 進(jìn)程指定 | ? | 結(jié)果 |
| IP地址 | 端口 | ? |
| 通配地址 | 0 | 內(nèi)核選擇IP地址和端口 |
| 通配地址 | 非0 | 內(nèi)核選擇IP地址, 進(jìn)程指定端口 |
| 本地IP地址 | 0 | 進(jìn)程指定IP地址,內(nèi)核選擇端口 |
| 本地IP地址 | 非0 | 進(jìn)程指定IP地址和端口 |
4. listen函數(shù)
根據(jù)TCP狀態(tài)轉(zhuǎn)換圖,調(diào)用listen導(dǎo)致套接字從CLOSED狀態(tài)轉(zhuǎn)換到LISTEN狀態(tài)
#include<sys/socket.h> int listen(int sockfd, int backlog); //返回:若成功返回0,失敗返回-1內(nèi)核為任何一個給定的監(jiān)聽套接字維護(hù)兩個隊列:
* 未完成連接隊列
* 已完成連接隊列
5. accept函數(shù)
accept函數(shù)由TCP服務(wù)器調(diào)用,用于從已完成連接隊列頭返回下一個已完成連接
#include<sys/socket.h> int accept(int sockfd, struct sockaddr* cliaddr, socklen_t* addrlen);//返回:若成功則為非負(fù)描述符,若出錯則為-1cliaddr 和addrlen 用來返回對方進(jìn)程(客戶)的協(xié)議地址。也就是返回客戶端的IP地址
我們稱第一個參數(shù)sockfd為監(jiān)聽套接字(listening socket)描述符(由socket創(chuàng)建,隨后用作bind和listen的第一個參數(shù)的描述符)
稱它的返回值為已連接套接字(connected socket)描述符。
一個服務(wù)器通常僅僅創(chuàng)建一個監(jiān)聽套接字,它在該服務(wù)器的生命期內(nèi)一直存在。內(nèi)核為每個由服務(wù)器進(jìn)程接收的客戶創(chuàng)建一個已連接套接字(也就是說對于它的三次握手過程已經(jīng)完成)。當(dāng)服務(wù)器完成對某個給定客戶的服務(wù)時,相應(yīng)的已連接套接字就會關(guān)閉。
如果accept函數(shù)的第二個和第三個參數(shù)都是空指針,表示我們隊客戶的身份不感興趣
6. fork和exec函數(shù)
#include<unistd.h> pid_t fork();返回:在子進(jìn)程中為0,在父進(jìn)程中為子進(jìn)程Id, -1--出錯fork函數(shù)調(diào)用一次,返回兩次。
在調(diào)用進(jìn)程(成為父進(jìn)程),它返回一次,返回值是新派生進(jìn)程(成為子進(jìn)程)的進(jìn)程ID;在子進(jìn)程它還返回一次,返回值為0.
因此可以用返回值來判斷當(dāng)前進(jìn)程是子進(jìn)程還是父進(jìn)程
fork 在子進(jìn)程返回0而不是父進(jìn)程ID,原因是:子進(jìn)程只有一個父進(jìn)程,它總是可以調(diào)用getppid來得到
fork有兩個典型應(yīng)用:
1. 一個進(jìn)程可以為自己創(chuàng)建一個拷貝,這樣,當(dāng)一個拷貝處理一個操作時,其他的拷貝可以執(zhí)行其他的任務(wù)。這是非常典型的網(wǎng)絡(luò)服務(wù)器
2. 一個進(jìn)程想執(zhí)行其他的程序,由于創(chuàng)建進(jìn)程的唯一方法是調(diào)用fork,進(jìn)程首先調(diào)用fork來生成一個拷貝,然后其中一個拷貝(通常為子進(jìn)程)調(diào)用exec來代替自己去執(zhí)行新程序。
#include<unistd.h> int execve(const char* pathname, char* argv[], char* const envp[] );返回:-1--出錯, 無返回--成功7. close函數(shù)
#include<unistd.h> int close(int sockfd);return: 0-ok, -1--error8. getsockname 和getpeername函數(shù)
#include<sys/socket.h> int getsockname(int sockfd, struct sockaddr* localaddr, socklen_t* addrlen); int getpeername(int sockfd, struct sockaddr* peeraddr, socklen_t* addrlen);return : 0-ok, -1-errorgetsockname : 返回與套接字關(guān)聯(lián)的本地協(xié)議地址
getpeername:? 返回與套接字關(guān)聯(lián)的遠(yuǎn)程協(xié)議地址
當(dāng)一個服務(wù)器由調(diào)用accept的進(jìn)程調(diào)用exec啟動執(zhí)行時,它獲得客戶身份的唯一途徑就是調(diào)用getpeername
第七章 套接字選項
7.1 概述??
有很多方法來獲取和設(shè)置影響套接字接口的選項
* getsockopt 和setsockopt
* fcntl
* ioctl
7.2 getsockopt 和setsockopt 函數(shù)
#include<sys/socket.h> int getsockopt(int sockfd, int level, int optname, void* optval, socklen_t* optlen);int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t* optlen);return : 0-ok, -1-errorsockfd: 指向一個打開的套接字描述符
level:
????? ? SOL_SOCKET :?
????????IPPROTO_TCP :?
optname:?
????SO_REUSEADDR :? 允許重用本地地址
????SO_REUSEPORT :?允許重用本地地址
????SO_KEEPALIVE :? 如果2 小時內(nèi)在此套接字的任一方向都沒有數(shù)據(jù)交換,TCP就自動給對方發(fā)一個keepalive probe
????SO_RCVBUF : 接收緩沖區(qū)大小
????SO_SNDBUF:發(fā)送緩沖區(qū)大小
????SO_SETFIB :?
????SO_SNDLOWAT : 發(fā)送低潮限度, select函數(shù)使用
????SO_ACCEPTFILTER :?
????SO_UPDATE_ACCEPT_CONTEXT
????SO_BINDANY :?
????SO_LINGER : 若有數(shù)據(jù)待發(fā)送則延遲關(guān)閉
????TCP_FASTOPEN :?
????TCP_NODELAY :? ? ?禁止Nagle 算法
????TCP_DEFER_ACCEPT :?
????TCP_NOPUSH
????TCP_CORK
????IP_RECVDSTADDR :? 返回目的ip地址
????IP_PKTINFO :? ??
????IP_BIND_ADDRESS_NO_PORT :?
????IP_TRANSPARENT :?
????IPV6_RECVPKTINFO :?
????IPV6_V6ONLY:?
????IPV6_TRANSPARENT:?
????IPV6_BINDANY :?
? ? 管道的容量稱為帶寬-延遲積(bandwidth-delay product), 我們可以將帶寬(位/秒)乘上RTT(秒),并將結(jié)果由位轉(zhuǎn)換成字節(jié)來計算得到。
總結(jié)
- 上一篇: ioctl , fcntl socke
- 下一篇: VirtualBox 安装 Linux时