?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議,API 對(duì)關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡(jiǎn)化了程序編程。
? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對(duì)各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。
poll
poll()????????? ?遵循 POSIX.1 - 2008
ppoll()? ? ? ? ?遵循 Linux
1.庫
標(biāo)準(zhǔn) c 庫,libc, -lc
2.頭文件
<poll.h>
3.接口定義
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *_Nullable tmo_p,
const sigset_t *_Nullable sigmask);
4.接口描述
? ? ? ? poll() 和 select() 做的事情差不多,它等待一個(gè)文件描述符集 I/O 就緒。Linux 的 epoll() 也是類似的,只是比 poll() 提供多了一些特性。
? ? ? ? fds 參數(shù)是要監(jiān)控的文件描述符集,是下面結(jié)構(gòu)體的一個(gè)數(shù)組:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
? ? ? ? 由調(diào)用者指定 fds 的項(xiàng)數(shù)。
? ? ? ? 結(jié)構(gòu)體中 fd 包含了一個(gè)打開的文件描述符,如果它是負(fù)值,那么 events 參數(shù)將被忽略,revents 返回 0。(也就是說?可以將?fd 設(shè)置為其補(bǔ)碼就可以忽略它)。
? ? ? ? events 參數(shù)是一個(gè)輸入?yún)?shù),通過按位掩碼來標(biāo)識(shí)應(yīng)用感興趣的文件描述符上的事件。參數(shù)可以設(shè)置為 0,那么就只能返回 POLLHUP/POLLERR/POLLNVAL 事件。
? ? ? ? revents 是一個(gè)輸出參數(shù),由內(nèi)核填充實(shí)際發(fā)生的事件。這些事件可以是 events 中指定的事件,也可以是? POLLHUP/POLLERR/POLLNVAL 中的一個(gè)。(events 中這三個(gè)事件對(duì)應(yīng)的位并沒有什么意義,只要對(duì)應(yīng)的條件發(fā)生,revents 就會(huì)返回該事件。)
? ? ? ? 如果沒有請(qǐng)求的事件(包括錯(cuò)誤)發(fā)生,那么 poll() 會(huì)一直阻塞,直到有事件發(fā)生。
? ? ? ? timeout 參數(shù)指定了 poll() 等待文件描述符就緒的毫秒數(shù),該調(diào)用會(huì)一直阻塞直到:
- 文件描述符就緒
- 調(diào)用被信號(hào)打斷
- 發(fā)生超時(shí)
? ? ? ? 同樣,timeout 值也會(huì)向上近似到系統(tǒng)時(shí)鐘粒度,由于內(nèi)核調(diào)度延遲阻塞的事件可能會(huì)稍微多一點(diǎn)。如果 timeout 是負(fù)值,表示超時(shí)時(shí)間是無限長(zhǎng)。如果 timeout 設(shè)置為 0,那么 poll() 會(huì)馬上返回,即使沒有任何文件描述符就緒。
? ? ? ? events 和 revents 中各個(gè)位在 poll.h 中定義:
? ? POLLIN
? ? ? ?有數(shù)據(jù)可以讀。
? ? POLLPRI
? ? ? ? 文件描述符上有異常發(fā)生,可能是(1)TCP socket 上有帶外數(shù)據(jù)(2)處于報(bào)文模式的偽終端主機(jī)發(fā)現(xiàn)了從機(jī)狀態(tài)變化(3)cgroup.events 文件被修改了。
? ? ? ? POLLOUT
? ? ? ?當(dāng)前可寫,但是寫大于 socket 或 pipe 中可用空間的數(shù)據(jù)仍然會(huì)導(dǎo)致阻塞(除非設(shè)置了 O_NONBLOCK)。
? ? ? ? POLLRDHUP
? ? ? ? 流 socket 對(duì)端關(guān)閉了連接或者在寫半連接時(shí)關(guān)機(jī)。這個(gè)定義依賴于 _GNU_SOURCE 宏定。
? ? ? ? POLLERR
? ? ? ?發(fā)生錯(cuò)誤。如果文件描述符指向了 pipe 的寫端,而讀端關(guān)閉了,那么也會(huì)返回這個(gè)錯(cuò)誤。
? ? ? ? POLLHUP
? ? ? ? 掛斷。在讀取 pipe 或者流 socket 時(shí),這個(gè)事件只表示對(duì)端關(guān)閉了其通道,后面的數(shù)據(jù)讀取時(shí),在通道中數(shù)據(jù)讀盡后再繼續(xù)讀會(huì)返回?0(EOF)。
? ? ? ? POLLNVAL
? ? ? ? 請(qǐng)求不合法:fd 沒有打開。
? ? ? ? 在使用 _XOPEN_SOURCE 宏編譯時(shí),還會(huì)有以下一些事件,不過也沒有提供太多信息:
? ? ? ? POLLRDNORM
? ? ? ? 等同于 POLLIN。
? ? ? ? POLLRDBAND
? ? ? ? 優(yōu)先帶寬數(shù)據(jù)可以讀(通常在 Linux 上用)
? ? ? ? POLLWRNORM
? ? ? ? 等同于 POLLOUT
? ? ? ? POLLWRBAND
? ? ? ? 可能寫了優(yōu)先數(shù)據(jù)
ppoll()
? ? ? ? ppoll() 和 poll() 的關(guān)系就像 select() 和 pselect() 的關(guān)系一樣,ppoll() 為應(yīng)用提供了等待信號(hào)或者就緒事件的安全方法。
? ? ? ? 除了 timeout 時(shí)間精度上的差異,以下兩段代碼幾乎等效
ready = ppoll(&fds, nfds, tmo_p, &sigmask);
sigset_t origmask;
int timeout;
timeout = (tmo_p == NULL) ? -1 :
(tmo_p->tv_sec * 1000 + tmo_p->tv_nsec / 1000000);
pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
ready = poll(&fds, nfds, timeout);
pthread_sigmask(SIG_SETMASK, &origmask, NULL);
??????????上面代碼說成幾乎等效而不是等效主要是因?yàn)樨?fù)值的 timeout 會(huì)被 poll() 解釋為一直等待,而 ppoll() 中負(fù)值的 *tmo_p 會(huì)報(bào)錯(cuò)。
? ? ? ? 可以參考 pselect(2) 來看為什么 ppoll 是必要的。
? ? ? ? 如果 sigmask 參數(shù)為 NULL,那么就不會(huì)有任何信號(hào)屏蔽操作,這時(shí)這兩個(gè)接口唯一的區(qū)別就是時(shí)間精度。
? ? ? ? tmo_p 指定了 ppoll() 會(huì)阻塞的時(shí)間上限,它是指向 timespec 結(jié)構(gòu)體的指針,指針為空時(shí),ppoll() 會(huì)一直阻塞。
5.返回值
? ? ? ? 成功時(shí),poll() 返回一個(gè)非負(fù)數(shù)表示 pollfds 中有多少個(gè)文件描述符上有事件發(fā)生,即對(duì)應(yīng)的 revents 有被更新為非 0 值。返回 0 表示沒有任何文件描述符就緒并超時(shí)。
? ? ? ? 發(fā)生錯(cuò)誤時(shí),返回 -1,并設(shè)置errno 來指示錯(cuò)誤類型。
? ? ? ? 錯(cuò)誤值定義如下:
EFAULT | fds 指向了進(jìn)程外的地址空間 |
EINTR | 請(qǐng)求事件發(fā)生前,發(fā)生了信號(hào),具體參見 signal(7) |
EIVAL | nfds 值超出了 RLIMIT_NOFILE 限制 |
EINVAL | ppoll() 中的 *tmo_P 是一個(gè)非法值(負(fù)數(shù)) |
ENOMEM | 沒有足夠內(nèi)存來分配內(nèi)核數(shù)據(jù)結(jié)構(gòu) |
一些其他 UNIX 系統(tǒng)上,如果內(nèi)核無法發(fā)分配內(nèi)核資源,poll() 可能會(huì)產(chǎn)生 EAGAIN 類的錯(cuò)誤,而不像 Linux 上的 ENOMEM。POSIX 允許這種行為。所以,一個(gè)可移植的程序需要檢測(cè)該錯(cuò)誤,并重試,就像處理 EINTR 一樣。
一些實(shí)現(xiàn)定義了非標(biāo)準(zhǔn)常量 INFTIM(-1),用作 poll() 的 timeout,但是這個(gè)常量并沒有被被 glibc 提供。
6.注意
? ? ? ?poll() 和 ppoll() 的行為不受 O_NONBLOCK 標(biāo)志影響。
? ? ? ? 對(duì)于一個(gè)文件描述符正在被 poll() 監(jiān)聽卻被另一個(gè)線程關(guān)閉了這種情況的討論,可以參考 select(2)。
7.BUGS
? ? ? ? 可以參考 select(2) 中關(guān)于虛假就緒通知的討論。?
8.代碼實(shí)例
? ? ? ? 該程序會(huì)打開命令行參數(shù)傳進(jìn)來的文件名并監(jiān)聽其 POLLIN 事件,程序會(huì)循環(huán)調(diào)用 poll() 來監(jiān)聽文件描述符,打印已經(jīng)就緒的文件描述符數(shù)。對(duì)于每個(gè)就緒的文件描述符,程序會(huì):
- 以可讀的格式顯示返回的 revents
- 如果文件描述符就緒,那么就從中讀一些數(shù)據(jù)出來并打印
- 如果文件描述符不可讀,但是發(fā)生了一些其他事件(比如 POLLHUP),就關(guān)閉文件描述符
? ? ? ? 假定我們?cè)谝粋€(gè)終端運(yùn)行程序,讓他打開一個(gè) FIFO:
$ mkfifo myfifo
$ ./poll_input myfifo
? ? ? ? 在另一個(gè)終端打開 FIFO,并寫入一些數(shù)據(jù),然后關(guān)閉 FIFO:
$ echo aaaaabbbbbccccc > myfifo
? ? ? ? ? ? ? ? ?我們將在運(yùn)行程序的終端上看到如下信息:
Opened "myfifo" on fd 3
About to poll()
Ready: 1
fd=3; events: POLLIN POLLHUP
read 10 bytes: aaaaabbbbb
About to poll()
Ready: 1
fd=3; events: POLLIN POLLHUP
read 6 bytes: ccccc
About to poll()
Ready: 1
fd=3; events: POLLHUP
closing fd 3
All file descriptors closed; bye
? ? ? ? ?從上面我們可以看到 poll() 返回了三次:文章來源:http://www.zghlxwxcb.cn/news/detail-705963.html
- 第一次返回是 POLLIN,表示文件描述符可讀,另一個(gè)是 POLLHUP 表示文件描述符的另一個(gè)端關(guān)閉了。程序接著讀取了一些可用的輸入數(shù)據(jù)
- 第二次返回同樣是這兩個(gè)事件,依然消費(fèi)了一些可用數(shù)據(jù)
- 最后一次返回,poll() 只有 POLLHUP 事件,然后關(guān)閉文件描述符并結(jié)束了程序。
/* poll_input.c
Licensed under GNU General Public License v2 or later.
*/
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0)
int
main(int argc, char *argv[])
{
int ready;
char buf[10];
nfds_t num_open_fds, nfds;
ssize_t s;
struct pollfd *pfds;
if (argc < 2) {
fprintf(stderr, "Usage: %s file...\n", argv[0]);
exit(EXIT_FAILURE);
}
num_open_fds = nfds = argc - 1;
pfds = calloc(nfds, sizeof(struct pollfd));
if (pfds == NULL)
errExit("malloc");
/* Open each file on command line, and add it to 'pfds' array. */
for (nfds_t j = 0; j < nfds; j++) {
pfds[j].fd = open(argv[j + 1], O_RDONLY);
if (pfds[j].fd == -1)
errExit("open");
printf("Opened \"%s\" on fd %d\n", argv[j + 1], pfds[j].fd);
pfds[j].events = POLLIN;
}
/* Keep calling poll() as long as at least one file descriptor is
open. */
while (num_open_fds > 0) {
printf("About to poll()\n");
ready = poll(pfds, nfds, -1);
if (ready == -1)
errExit("poll");
printf("Ready: %d\n", ready);
/* Deal with array returned by poll(). */
for (nfds_t j = 0; j < nfds; j++) {
if (pfds[j].revents != 0) {
printf(" fd=%d; events: %s%s%s\n", pfds[j].fd,
(pfds[j].revents & POLLIN) ? "POLLIN " : "",
(pfds[j].revents & POLLHUP) ? "POLLHUP " : "",
(pfds[j].revents & POLLERR) ? "POLLERR " : "");
if (pfds[j].revents & POLLIN) {
s = read(pfds[j].fd, buf, sizeof(buf));
if (s == -1)
errExit("read");
printf(" read %zd bytes: %.*s\n",
s, (int) s, buf);
} else { /* POLLERR | POLLHUP */
printf(" closing fd %d\n", pfds[j].fd);
if (close(pfds[j].fd) == -1)
errExit("close");
num_open_fds--;
}
}
}
}
printf("All file descriptors closed; bye\n");
exit(EXIT_SUCCESS);
}
下一篇 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(4)???????文章來源地址http://www.zghlxwxcb.cn/news/detail-705963.html
到了這里,關(guān)于【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(3)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!