一、socket地址API
1、主機字節序和網絡字節序
小端字節序(主機字節序)是指一個整數的高位字節存儲在內存的高地址處
大端字節序(網絡字節序)是指一個整數的高位字節存儲在內存的低地址處
判斷機器字節序
#include <stdio.h>void byteorder(){union {short value;char union_bytes[sizeof(short)];}test;test.value = 0x0102;if ((test.union_bytes[0] == 1)&& (test.union_bytes[1] == 2)) {printf("big endian\n");}else if((test.union_bytes[0] == 2) && (test.union_bytes[1] == 1)){printf("little endian\n");}else{printf("unknown\n");}
}
主機字節序和網絡字節序之間的轉換
#include <netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
功能:長整型的主機字節序轉化為網絡字節序unsigned short int htons(unsigned short int hostshort);
功能:短整型的主機字節序轉化為網絡字節序unsigned long int ntohl(unsigned long int netlong);
功能:長整型的網絡字節序轉化為主機字節序unsigned short int ntohs(unsigned short int netshort);
功能:短整型的網絡字節序轉化為主機字節序
2、通用socket地址
#include <bits/socket.h>
struct sockaddr{sa_family_t sa_family; // 地址族類型char sa_data[14]; // 存放socket地址值
}
// 協議族(protocol family 也稱domain)
協議族 地址族 描述 地址值含義和長度
PF_UNIX AF_UNIX UNIX本地域協議族 文件的路徑名,長度可到達108字節
PF_INET AF_INET TCP/IPv4協議族 16bit端口號和32bitV IPv4地址,共6個字節
PF_INET6 AF_INET6 TCP/IPv6協議族 16bit端口號,32bit流標識,128bit IPv6地址,32bit范圍ID,共有26字節// 為解決sa_data無法容納多數協議族的地址值,定義新的通用socker地址結構體
struct sockaddr_storage{sa_family_t sa_family;unsigned long int __ss_align;char __ss_padding[128-sizeof(__ss_align)];
}
3、專用socket地址
#include <sys/un.h>
struct sockaddr_un{sa_family_t sin_family; // AF_UNIXchar sun_path[108]; // 文件路徑名
}
struct sockaddr_in{sa_family_t sin_family; // 地址族u_int16_t sin_port; // 端口號,要用網絡字節序表示struct in_addr sin_addr; // IPv4地址結構體
};
struct in_addr{u_int32_t s_addr; // IPv4,要用網絡字節序表示
};
struct sockaddr_in6{sa_family_t sin6_family; // 地址族:AF_INET6u_int16_t sin6_port; // 端口號,要用網絡字節序表示u_int32_t sin6_flowinfo; // 流信息,應設置為0struct in6_addr sin6_addr; // IPv6地址結構體u_int32_t sin6_scope_id; // scope ID,尚處于實驗階段
};
struct in6_addr{unsigned char sa_addr[16]; // IPv6地址,要用網絡字節序表示
};注意:所有專用socket地址類型在實際使用時都需要轉化為通用socket地址類型sockaddr(強制轉換即可)
4、IP地址轉換函數
#include<arpa/inet.h>
in_addr_t inet_addr(const char* strptr);
功能:將用點分十進制字符串表示的IPv4的地址轉化為用網絡字節序整數表示的IPv4.
返回值:失敗:INADDR_NONEint inet_aton(const char* cp, struct in_addr* ip);
功能:同inet_addr同樣的功能。將轉化的結果存儲于參數inp指向的地址結構中
返回值:成功 1失敗 0char *inet_ntoa(struct in_addr in);
功能:將用網絡字節序整數表示的IPv4地址轉化為用點分十進制字符串表示的IPv4地址(函數內部用一個靜態變量存儲轉化結果,函數的返回值指向該靜態內存,因此inet_ntoa是不可重入的int inet_pton(int af, const char* src. void* dst);
功能:將用字符串表示的IP地址src轉換成網絡字節序整數表示的IP地址,并將轉化結果存儲于dst指向的內存中
參數:af 指定地址族 AF_INET 或者AF_INET6src IP地址字符串dst 轉化結果存儲于dst指向的內存中
返回值 成功 1失敗 0 設置errnoconst char* inet_ntop(int af, void *src, char *dst, socklen_t cnt);
功能:af,src,dst同上cnt指定目標存儲單元的大小 #define INET_ADDRSTRLEN 16#define INET6_ADDRSTRLEN 46
返回值:成功 目標存儲單元的地址失敗 NULL并設置errno
二、創建socket socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:
參數:domin:告訴系統使用底層協議族IPv4 PF_INET IPv6 PF_INET6本地協議族 PF_UNIXtype 指定服務類型SOCK_STREAM TCPSOCK_DGRAM UDPSOCK_NONBLOCK 設置為非阻塞的SOCK_CLOEXEC 用fork調用創建子進程時在子進程中關閉該socketprotocol 通常設置為0,表示默認協議
返回值:成功 返回一個socket文件描述符失敗 -1并設置errno
三、綁定socket bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
功能:將sockfd與my_addr進行綁定
參數:sockfd:socket創建出來的文件描述符my_addr:服務器的地址addrlen:my_addr的大小 sizeof(my_addr)
返回值:成功: 0失敗:-1并設置errnoEACCES:被綁定的地址是受保護的地址,僅超級用戶能夠訪問EADDRINUSE:被綁定的地址正在使用中/*注意:服務器中,就需要為sockfd與my_addr進行綁定,因為只有綁定后客戶端才能知道如何連接它客戶端不需要綁定,而是采用匿名方式,也就是操作系統自動分配socket地址
*/
四、監聽socket listen
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:創建監聽隊列以存放待處理的客戶連接
參數:sockfd:指定被監聽的socketbacklog:內核監聽隊列的最大長度,連接數超過backlog,客戶端收到ECONNREFUSED錯誤信息
返回值:成功 0失敗 -1并設置errno
五、接受連接 accept
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:從listen監聽隊列中接受一個了連接
參數:sockfd:執行過里listen系統調用的文件描述符addr:客戶端socket地址addrlen:addr的長度
返回值:成功:與客戶端通信的文件描述符,讀寫都是通過這個描述符來進行的失敗:-1并設置errno
六、發起連接 connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
功能:客戶端發起連接服務器
參數:sockfd:客戶端的sock文件描述符serv_addr:服務器地址addrlen:serv_addr的長度
返回值:成功 0失敗 -1并設置errnoECONNREFUSED:目標端口不存在,連接被拒絕ETIMEDOUT:連接超時
七、關閉連接 close
#include<unistd.h>
int close(int fd);
功能:關閉fd,并不是立即關閉一個連接,而是將fd的引用計數減1,只有當fd的引用計數為0時,才真正關閉連接#include<sys/socket.h>
int shutdown(int sockfd, int howto);
功能:立即終止連接
參數:howtoSHUT_RD:關閉讀SHUT_WR:關閉寫SHUT_RDWR:關閉讀寫
返回值:成功 0失敗 -1并設置errno
八、數據讀寫
1、TCP數據讀寫
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:讀取sockfd上的數據
參數:sockfd:文件描述buf:指定緩沖區的位置len:指定緩沖區的大小flags:0
返回值:成功:實際讀取的數據的長度失敗:0 對方已經關閉連接錯誤 -1并設置errnossize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:往sockfd上寫入數據
參數:sockfd:文件描述符buf:指定緩沖區的位置len:緩沖區的大小flags:0
返回值:成功 返回實際寫入數據的長度失敗 -1并設置errno
flags可以取以下選項的一個或幾個的邏輯或
選項名 | 含義 | send | recv |
---|---|---|---|
MSG_CONFIRM | 指示數據鏈路層協議持續監聽對方的回應,直到得到答復。它僅能用于SOCK_DGRAM和SOCK_RAW類型的socket | Y | N |
MSG_DONTROUTE | 不查看路由表,直接將數據發送給本地局域網絡內的主機。這表示發送者確切地知道目標主機就在本地網絡上 | Y | N |
MSG_DONTWAIT | 對socket的此次操作將是非阻塞的 | Y | Y |
MSG_MORE | 告訴內核應用程序還有更多數據要發送,內核將超時等待新數據寫入TCP發送緩沖區后一并發送。這樣可房子TCP發送過多小的報文段,從而提高傳輸效率 | Y | N |
MSG_WAITALL | 讀操作僅在讀取到指定數量的字節才返回 | N | Y |
MSG_PEEK | 窺探讀緩存中的數據,此次讀操作不會導致這些數據被清除 | N | Y |
MSG_OOB | 發送或接收緊急數據 | Y | Y |
MSG_NOSIGNAL | 往讀端的管道或者socket連接中寫數據時不引發SIGPIPE信號 | Y | N |
2、UDP數據讀寫
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
參數:sockfd 讀寫數據文件描述符buf:讀寫緩沖區的位置len:緩沖區的大小flags:0src_addr:獲取發送端的socket地址dest_addr:指定接收端的socket地址addrlen:地址長度// 注意:recvform/sendto也可以用于面向連接(STREAM)的socket的數據讀寫,只需要把最后兩個參數都設置為NULL以忽略發送端/接受端的地址
3、通用數據讀寫函數
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
參數:sockfd 讀寫數據文件描述符msg msghdr結構體類型指針flags 0struct msghdr
{void msg_name; // socket地址socklen_t msg_namelen; // socket地址的長度struct iovec* msg_iov; // 分散的內存塊int msg_iovlen; // 分散內存塊的數量void *msg_control; // 指向輔助數據的起始位置socklen_t msg_controllen; // 輔助數據的大小int msg_flags; // 復制函數中的flags參數,并在調用過程中更新
};struct iovec
{void *iov_base; // 內存起始地址size_t iov_len; // 這塊內存的長度
};
iovec結構體封裝了一塊內存的起始位置和長度,msg_iovlen指定這樣的分散的iovec結構對象有多少個
msg_control和msg_controllen 用于輔助數據的傳送
msg_flags 無須設定,它會復制函數的第三個參數flags,recvmsg還會在調用結束強,將某些更新后的標志設置到msg_flags中
九、帶外標記
#include <sys/socket.h>
int sockatmark(int sockfd);
功能:判斷sockfd是否處于帶外標記,下一個被讀取到的數據是否是帶外數據
參數:sockfd 文件描述符
返回值:1 是0 不是
用sockatmark判斷sockfd是否處于帶外標記,即下一個被讀取到的數據是否是帶外數據。如果是,sockatmark返回1,此時我們就可以利用帶MSG_OOB標志的recv調用來接收帶外數據。如果不是,則sockatmark返回0。
十、地址信息函數
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr* address, socklen_t *address_len);
功能:獲取sockfd對應的本端socket地址
參數:sockfd 文件描述符address 存儲地址的內存address_len 地址長度
返回值:0 成功-1 失敗int getpeername(int sockfd, struct sockaddr* address, socklen_t *address_len);
功能:獲取sockfd對應的遠端socket地址
參數:同getsockname
十一、socket選項
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int option_name, void* option_value,socklen_t* restrict option_len);
int setsockopt(int sockfd, int level, int option_name, const void* option_value,socklen_t option_len);
功能:讀取和設置socket文件描述符屬性
參數:sockfd 指定被操作目標socketlevel 參數指定要操作的那個協議的選項SOL_SOCKET 通用socket選項,與協議無關IPPROTO_IP IPv4選項IPPROTO_IPV6 IPv6選項IPPROTO_TCP TCP選項option_name:指定選項的名字,常用以下幾個SO_REUSEADDR:重用socket地址 intSO_RCVBUF:TCP接收緩沖區的大小 intSO_SNDBUF:TCP發送緩沖區的大小 intSO_RCVLOWAT:TCP接收緩沖區的低水位標記,默認1字節 intSO_SNDLOWAT:TCP發送緩沖區的低水位標記,默認1字節 intSO_LINGER:如下 lingeroption_value:被操作選項的值option_len:被操作選項的長度
返回值:0 成功-1 失敗并設置errnoSO_LINGER:
// SO_LINGER用于控制close系統調用在關閉TCP連接時的行為。
// 通常,使用close系統調用來關閉socket時,close將立即返回,TCP模塊負責把該socket對應TCP發送緩沖區中殘留的數據發送給對方。
#include <sys/socket.h>
struct linger{int l_onoff; // 開啟(非0)還是關閉(0)該選項int l_linger; // 滯留時間
};
- l_onoff = 0,此時SO_LINGER選項不起作用,close用默認行為來關閉socket
- l_onoff != 0,l_linger = 0,此時close系統立即返回,TCP模塊丟棄被關閉的socket對應的TCP發送緩沖區中殘留的數據,同時給對方發送一個復位報文段,,提供了異常終止一個連接的方法
- l_onoff != 0,l_linger大于0,此時close的行為取決于兩個條件:1.TCP緩沖區中是否殘留都數據,2. socket是阻塞的,還是非阻塞的- 阻塞:close將等待一段長為l_linger的時間,如果沒有發送往并得到對方確認,close系統調用返回-1并設置errno為EWOULDBLOCK- 非阻塞:close將立即返回,根據返回值和errno判斷數據是否發送完畢
十二、網絡信息API
1、gethostbyname和gethostbyaddr
//
#include <netdb.h>
struct hostent* gethostbyname(const char *name);
功能:根據主機名稱獲取主機的完整信息
參數:name:目的主機的主機名
返回值:hostent結構體類型的指針struct hostent* gethostbyaddr(const void *addr, size_t len, int type);
功能:根據IP地址獲取主機的完整信息
參數:addr:目標主機的IP地址len:addr所只IP地址的長度type:IP地址的類型, AF_INET(IPv4) AF_INEF69(IPv6)
返回值:hostent結構體類型的指針struct hostent{char* h_name; // 主機名char** h_aliases; // 主機別名列表,可能有多個int h_addrtype; // 地址類型int h_length: // 地址長度char** h_addr_list; // 按網絡字節序列出的主機IP地址列表
};
2、getservbyname和getservbyaddr
//
#include <netdb.h>
struct servent* getservbyname(const* name, const char* proto);
功能:根據名稱獲取某個服務的完整信息
參數:name:目標服務的名字proto:指定服務類型,“tcp”獲取流服務 “udp”獲取數據報服務 NULL獲取所有類型的服務
返回值:servent結構體類型的指針struct servent* getservbyport(int port, const char* proto);
功能:根據端口號獲取某個服務的完整信息
參數:port:目標服務的端口號proto:指定服務類型,“tcp”獲取流服務 “udp”獲取數據報服務 NULL獲取所有類型的服務
返回值:servent結構體類型的指針struct servent{char* s_name; // 服務名稱char** s_aliases; // 服務的別名列表,可能有多個int s_port; // 端口號char* s_proto; // 服務類型,通常是tcp或者udp
}
注意:以上4個函數都是不可重入的,即非線程安全的,不過netdb.h頭文件給出它們的可重入版本,正如Linux下所有其他函數的可重入的命名規則那樣。這些函數的函數名是在原函數名尾部加上_r(re-entrant)。
3、getaddrinfo
#include <netdb.h>
int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result);
功能:既能通過主機名獲得IP地址(內部使用的是gethostbyname函數),也能通過服務名獲得端口號(內部使用的是getservbyname函數)參數:hostname:接收主機名,也可以接收字符串表示的IP地址(IPv4采用點分十進制字符串,IPv6則采用十六進制字符串)service:接收服務名,也可以接收字符串表示的十進制端口號hints:應用程序給getaddrinfo的一個提示,對getaddrinfo的輸出進行更精確的控制。NULL,允許getaddrinfo反饋任何結果result:參數指向一個鏈表,該鏈表用于存儲getaddrinfo反饋的結果
返回值:成功 0失敗 返回錯誤碼struct addrinfo{int ai_flags; // 下面說明int ai_family; // 地址族int ai_socktype; // 服務類型,SOCK_STREAM或SOCK_DGRAMint ai_protocol; // 下面說明socklen_t ai_addrlen; // socket地址ai_addr的長度char* ai_canonname; // 主機的別名struct sockaddr* ai_addr; // 指向socket地址4struct addrinfo* ai_next; // 指向下一個sockinfo結構的對象
};
// ai_protocol:具體的網絡協議,其含義和socket系統調用的第三個參數相同,通常設置為0
// 使用hints參數的時候,可以設置ai_flags,ai_family,ai_socktype和ai_protocol四個字段,其他字段必須設置為NULL// result需要調用下面函數來釋放這塊內存
void freeaddrinfo(struct addrinfo* res);// 使用案例:
struct addrinfo hints;
struct addrinfo *res;
bzero(&hints, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
getaddinfo("ernest-laptop","daytime", &hists, &res);
...
freeaddrinfo(res);
ai_flags成員可以取以下的標志的按位或
選項 | 含義 |
---|---|
AI_PASSIVE | hists參數中設置,表示調用者是否會將取得的socket地址用于被動打開,服務器通常設置它,表示接受任何本地socket地址上的服務請求。客戶端程序不能設置它 |
AI_CANONNANE | 在hists參數中設置,告訴getaddrinfo函數返回主機的別名 |
AI_NUMERICHOST | 在hists參數中設置,表示hostnam必須是用字符串表示的IP地址,從而避免了DNS查詢 |
AI_NUMERICSERV | 在hists參數中設置,強制service參數使用十進制端口號的字符串形式,而不能是服務名 |
AI_V4MAPPED | 在hists參數中設置,如果ai_family被設置為AF_INET6,那么當沒有滿足條件的IPv6地址被找到時,將IPv4地址映射為IPv6地址 |
AI_ALL | 必須和AI_V4MAPPED同時使用,否則將被忽略。表示同時返回符合條件的IPv6地址以及由IPv4地址映射得到的IPv6地址 |
AI_ADDRCONGIG | 僅當至少配置有一個IPv4地址(除了回路地址)時,才返回IPv4地址信息;同樣,僅當至少配置有一個IPv6地址(除了回路地址)時,才返回IPv6地址信息。它和AI_V4MAPPED時互斥的 |
4、getnameinfo
int getnameinfo(const struct sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags);
功能:通過socket地址同時獲得以字符串表示的主機名和服務名
參數:sockaddr:socket地址host:主機名hostlen:主機名的長度serv:服務號servlen:服務號的長度flags:控制getnameinfo的行為
返回值:成功 0 失敗 返回錯誤號
// 將錯誤碼轉換成易讀的字符串形式
const char* gai_strerror(int error);
flags參數
選項 | 含義 |
---|---|
NI_NAMEREQD | 如果通過socket地址不能獲取主機名,則返回一個錯誤 |
NI_DGRAM | 返回數據服務。大部分同時支持流和數據報的服務使用相同的端口號來提供這兩種服務。但端口512~514是例外。比如TCP的514端口提供的是shell登錄服務,而UDP的514端口提供的是syslog服務 |
NI_NUMERICHOST | 返回字符串表示的IP地址,而不是主機名 |
NI_NUMERICSERV | 返回字符串表示的十進制端口號,而不是服務名 |
NI_NOFQDN | 僅返回主機域名的第一部分。比如對主機名nebula.testing.com,getnameinfo只將nebula寫入host緩存中 |
錯誤碼表
選項 | 含義 |
---|---|
EAI_AGAIN | 調用臨時失敗,提示應用程序過后再試 |
EAI_BADFLAGS | 非法的ai_flags值 |
EAI_FAIL | 名稱解析失敗 |
EAI_FAMILY | 不支持的ai_family參數 |
EAI_MEMORY | 內存分配失敗 |
EAI_NONAME | 非法的主機名或服務名 |
EAI_OVERFLOW | 用戶提供的緩沖區溢出。僅發生在getnameinfo調用中 |
EAI_SERVICE | 沒有支持的服務,比如用數據報服務類型來查找ssh服務。因為ssh服務只能使用流服務 |
EAI_SOCKTYPE | 不支持的服務類型。如果hints.ai_socktype和hists.ai_protocol不一致,比如前者指定SOCK_DGRAM,而后者使用的是IPROTO_TCP,則會觸發這類錯誤 |
EAI_SYSSTEM | 系統錯誤,錯誤值存儲在errno中 |