前言
面經(jīng)中提到的epoll,涉及到了socket編程。為了深入了解epoll原理,需要首先了解socket編程。socket是進(jìn)程間通信IPC,就算在網(wǎng)絡(luò)中也是如此,所以說網(wǎng)絡(luò)中通信的主體是進(jìn)程,而不是計(jì)算機(jī)。socket學(xué)習(xí)內(nèi)容一個(gè)是如何建立服務(wù)器和客戶端,一個(gè)是如何使用socket API。
fd=socket(domain, type, protocol);
socket調(diào)用可以用來創(chuàng)建一個(gè)socket,例如
domain可以用來指定ipv4,type可以用來指定tcp,protocol一般是0。
domain
domain是通信范圍與通信地址的類型。有下面幾個(gè)經(jīng)典類型:
UNIX IPV4 IPV6,分別對應(yīng)的參數(shù)是AF_UNIX AF_INET AF_INET6。
domain 的參數(shù)都是以AF開頭的代表地址簇。PF開頭的代表協(xié)議簇。本來設(shè)計(jì)是地址簇和協(xié)議簇是多對多的,但是后來實(shí)現(xiàn)過程中,一個(gè)協(xié)議簇和地址簇是一一對應(yīng)的。所以基本上domain就是指定了協(xié)議簇,地址簇也被指定了。type
socket表示是流還是數(shù)據(jù)包,其實(shí)就是TCP還是UDP。如果是TCP就是SOCK_STREAM,如果是UDP就是SOCK_DGRAM
bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
該調(diào)用用于將socket綁定到一個(gè)地址上。之后可以發(fā)送TCP報(bào)文,在一些場合下也可以通過write發(fā)送UDP報(bào)文,但是只能在該socket上讀取對等socket數(shù)據(jù)。
- sockaddr
該結(jié)構(gòu)體有一個(gè)整形表示地址類型,然后后面跟著一個(gè)char數(shù)組。后面可以看到具體傳進(jìn)來的是根據(jù)使用場合的其他數(shù)據(jù)結(jié)構(gòu),但是是通用的
listen(int sockfd, int backlog)
將一個(gè)socket描述符標(biāo)記為被動(dòng)??梢员恢鲃?dòng)socket連接。backlog是用于限制等待連接的數(shù)量。
accept(int sockfd, struct sockaddr *addr, socklen_t * addrlen);
accept調(diào)用會阻塞并等待在文件描述符sockfd上的接入請求。一旦請求成功,會創(chuàng)建一個(gè)新的socket,這個(gè)新的socket與對方進(jìn)行連接。
- addr
返回對方的地址 - addrlen
傳入addr的長度,用于告知能夠?qū)懭胼斎氲淖畲箝L度。
connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);
將sockfd連接到addr所述的地址上。
close(int fd)
用于關(guān)閉連接
read write
用于對sockfd進(jìn)行讀入或讀出
recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
sendto(int sockfd, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
用于發(fā)送和接受udp報(bào)文。服務(wù)器端不能使用listen函數(shù)和accept函數(shù),客戶端不能使用connect函數(shù)。
unix domain
使用上面的API就可以實(shí)現(xiàn)本機(jī)上通過文件的通信。
unix domain所使用的sockaddr是sockaddr_run,如下表示:
struct sockaddr_un{
sa_family_t sun_family;
char sun_path[108];
}
網(wǎng)絡(luò)字節(jié)序
網(wǎng)絡(luò)字節(jié)順序是按照大端來的,x86是小端結(jié)構(gòu)。轉(zhuǎn)換使用的是如下的函數(shù)進(jìn)行的
- htons
- htonl
- ntohs
- ntohl
h是host,n是net,s是16位,l是32位。s和l是short和long,雖然現(xiàn)在已經(jīng)不再使用這樣的標(biāo)準(zhǔn)了。
Internet socket 地址結(jié)構(gòu)
網(wǎng)絡(luò)下使用的socket地址是sockaddr_in,定義如下
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char __pad[X];
}
可以看出不一樣的地方是后面的char數(shù)組變成了一個(gè)端口和地址。sin是socket Internet的簡寫,簡寫的和sun一樣很差。
Internet socket 地址轉(zhuǎn)換
字符串式的地址格式和二進(jìn)制地址格式轉(zhuǎn)換API:
inet_pton(int domain, const char *src, void *addrptr);
該函數(shù)用于將src中包含的字符串轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)的二進(jìn)制地址,存入addrptr。p是presentation的意思,就是人類方便的地址。
const char * inet_ntop(int domain, const void *addrptr, char *dst_str, size_t len);
該函數(shù)執(zhí)行網(wǎng)絡(luò)字節(jié)的二進(jìn)制地址轉(zhuǎn)換為人類可讀的地址,寫入到dst_str中,緩沖區(qū)的大小有l(wèi)en傳入。
getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct **result);
該函數(shù)給定一個(gè)主機(jī)名和服務(wù)器名,返回socket地址和端口號。
getaddrinfo以host、service、hints參數(shù)作為輸入,其中host參數(shù)包括一個(gè)主機(jī)名或一個(gè)以IPV4字符串。service是服務(wù)名或者是端口號。該函數(shù)的調(diào)用之后需要使用freeaddrinfo來釋放空間。
getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, size_t hostlen, char *service, size_t servlen, int flags);
給定一個(gè)socket地址結(jié)構(gòu),返回一個(gè)主機(jī)和服務(wù)器名的字符串。
setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd是代表指向套接字的文件描述符。參數(shù)level制定了套接字選項(xiàng)所適用的協(xié)議。例如TCP或者是IP,這表示選項(xiàng)作用的套接字API層。一般來說,該選項(xiàng)會設(shè)置為SOL_SOCKET,表示作用于套接字API層。參數(shù)optname表示了我們期待設(shè)置的選項(xiàng),optvalue是用來設(shè)置剛剛的選項(xiàng)的值,可以是整數(shù)或者是結(jié)構(gòu)體的指針,指向了一個(gè)緩沖區(qū),而參數(shù)optlen是剛剛那個(gè)指針?biāo)赶騾^(qū)域的大小。
例如,要設(shè)置sockfd為reuseaddr屬性時(shí),可以如下調(diào)用:
int reuse = 1;
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
err_exit("setsockopt");
}
getsockopt(int sockfd, int level, int optname, void *optval, socklen_t optlen);
用法和上面一樣,只是獲取而已。