歡迎關(guān)注博主 Mindtechnist 或加入【Linux C/C++/Python社區(qū)】一起學(xué)習(xí)和分享Linux、C、C++、Python、Matlab,機(jī)器人運(yùn)動(dòng)控制、多機(jī)器人協(xié)作,智能優(yōu)化算法,濾波估計(jì)、多傳感器信息融合,機(jī)器學(xué)習(xí),人工智能等相關(guān)領(lǐng)域的知識(shí)和技術(shù)。
專欄:《網(wǎng)絡(luò)編程》
多路IO轉(zhuǎn)接服務(wù)器也叫做多任務(wù)IO服務(wù)器。該類服務(wù)器實(shí)現(xiàn)的主旨思想是,不再由應(yīng)用程序自己監(jiān)視客戶端連接,取而代之由內(nèi)核替應(yīng)用程序監(jiān)視文件。
主要使用的方法有三種,下面一一介紹并給出代碼實(shí)現(xiàn)。
select
- select能監(jiān)聽的文件描述符個(gè)數(shù)受限于FD_SETSIZE,一般為1024,單純改變進(jìn)程打開的文件描述符個(gè)數(shù)并不能改變select監(jiān)聽文件個(gè)數(shù)。
- 解決1024以下客戶端時(shí)使用select是很合適的,但如果鏈接客戶端過多,select采用的是輪詢模型,會(huì)大大降低服務(wù)器響應(yīng)效率,不應(yīng)在select上投入更多精力。
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds: 監(jiān)控的文件描述符集里最大文件描述符加1,因?yàn)榇藚?shù)會(huì)告訴內(nèi)核檢測(cè)前多少個(gè)文件描述符的狀態(tài)
readfds: 監(jiān)控有讀數(shù)據(jù)到達(dá)文件描述符集合,傳入傳出參數(shù)
writefds: 監(jiān)控寫數(shù)據(jù)到達(dá)文件描述符集合,傳入傳出參數(shù)
exceptfds: 監(jiān)控異常發(fā)生達(dá)文件描述符集合,如帶外數(shù)據(jù)到達(dá)異常,傳入傳出參數(shù)
timeout: 定時(shí)阻塞監(jiān)控時(shí)間,3種情況
①NULL,永遠(yuǎn)等下去
②設(shè)置timeval,等待固定時(shí)間
③設(shè)置timeval里時(shí)間均為0,檢查描述字后立即返回,輪詢
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set); //測(cè)試文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
server實(shí)現(xiàn)
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE]; /* FD_SETSIZE 默認(rèn)為 1024 */
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; /* #define INET_ADDRSTRLEN 16 */
socklen_t cliaddr_len;
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20); /* 默認(rèn)最大128 */
maxfd = listenfd; /* 初始化 */
maxi = -1; /* client[]的下標(biāo) */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* 用-1初始化client[] */
FD_ZERO(&allset);
FD_SET(listenfd, &allset); /* 構(gòu)造select監(jiān)控文件描述符集 */
for ( ; ; ) {
rset = allset; /* 每次循環(huán)時(shí)都從新設(shè)置select監(jiān)控信號(hào)集 */
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (nready < 0)
perr_exit("select error");
if (FD_ISSET(listenfd, &rset)) { /* new client connection */
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < FD_SETSIZE; i++) {
if (client[i] < 0) {
client[i] = connfd; /* 保存accept返回的文件描述符到client[]里 */
break;
}
}
/* 達(dá)到select能監(jiān)控的文件個(gè)數(shù)上限 1024 */
if (i == FD_SETSIZE) {
fputs("too many clients\n", stderr);
exit(1);
}
FD_SET(connfd, &allset); /* 添加一個(gè)新的文件描述符到監(jiān)控信號(hào)集里 */
if (connfd > maxfd)
maxfd = connfd; /* select第一個(gè)參數(shù)需要 */
if (i > maxi)
maxi = i; /* 更新client[]最大下標(biāo)值 */
if (--nready == 0)
continue; /* 如果沒有更多的就緒文件描述符繼續(xù)回到上面select阻塞監(jiān)聽,
負(fù)責(zé)處理未處理完的就緒文件描述符 */
}
for (i = 0; i <= maxi; i++) { /* 檢測(cè)哪個(gè)clients 有數(shù)據(jù)就緒 */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
Close(sockfd); /* 當(dāng)client關(guān)閉鏈接時(shí),服務(wù)器端也關(guān)閉對(duì)應(yīng)鏈接 */
FD_CLR(sockfd, &allset); /* 解除select監(jiān)控此文件描述符 */
client[i] = -1;
} else {
int j;
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Write(sockfd, buf, n);
}
if (--nready == 0)
break;
}
}
}
close(listenfd);
return 0;
}
client實(shí)現(xiàn)
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) {
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n");
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}
pselect
pselect原型如下。此模型應(yīng)用較少。
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
用sigmask替代當(dāng)前進(jìn)程的阻塞信號(hào)集,調(diào)用返回后還原原有阻塞信號(hào)集。
poll
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 監(jiān)控的事件 */
short revents; /* 監(jiān)控事件中滿足條件返回的事件 */
};
POLLIN 普通或帶外優(yōu)先數(shù)據(jù)可讀,即POLLRDNORM | POLLRDBAND
POLLRDNORM 數(shù)據(jù)可讀
POLLRDBAND 優(yōu)先級(jí)帶數(shù)據(jù)可讀
POLLPRI 高優(yōu)先級(jí)可讀數(shù)據(jù)
POLLOUT 普通或帶外數(shù)據(jù)可寫
POLLWRNORM 數(shù)據(jù)可寫
POLLWRBAND 優(yōu)先級(jí)帶數(shù)據(jù)可寫
POLLERR 發(fā)生錯(cuò)誤
POLLHUP 發(fā)生掛起
POLLNVAL 描述字不是一個(gè)打開的文件
nfds 監(jiān)控?cái)?shù)組中有多少文件描述符需要被監(jiān)控
timeout 毫秒級(jí)等待
-1:阻塞等,#define INFTIM -1 , Linux中沒有定義此宏。
0:立即返回,不阻塞進(jìn)程。
> 0:等待指定毫秒數(shù),如當(dāng)前系統(tǒng)時(shí)間精度不夠毫秒,向上取值。
如果不再監(jiān)控某個(gè)文件描述符時(shí),可以把pollfd中,fd設(shè)置為-1,poll不再監(jiān)控此pollfd,下次返回時(shí),把revents設(shè)置為0。
server實(shí)現(xiàn)
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024
int main(int argc, char *argv[])
{
int i, j, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);
client[0].fd = listenfd;
client[0].events = POLLRDNORM; /* listenfd監(jiān)聽普通讀事件 */
for (i = 1; i < OPEN_MAX; i++)
client[i].fd = -1; /* 用-1初始化client[]里剩下元素 */
maxi = 0; /* client[]數(shù)組有效元素中最大元素下標(biāo) */
for ( ; ; ) {
nready = poll(client, maxi+1, -1); /* 阻塞 */
if (client[0].revents & POLLRDNORM) { /* 有客戶端鏈接請(qǐng)求 */
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 1; i < OPEN_MAX; i++) {
if (client[i].fd < 0) {
client[i].fd = connfd; /* 找到client[]中空閑的位置,存放accept返回的connfd */
break;
}
}
if (i == OPEN_MAX)
perr_exit("too many clients");
client[i].events = POLLRDNORM; /* 設(shè)置剛剛返回的connfd,監(jiān)控讀事件 */
if (i > maxi)
maxi = i; /* 更新client[]中最大元素下標(biāo) */
if (--nready <= 0)
continue; /* 沒有更多就緒事件時(shí),繼續(xù)回到poll阻塞 */
}
for (i = 1; i <= maxi; i++) { /* 檢測(cè)client[] */
if ((sockfd = client[i].fd) < 0)
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) {
if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
if (errno == ECONNRESET) { /* 當(dāng)收到 RST標(biāo)志時(shí) */
/* connection reset by client */
printf("client[%d] aborted connection\n", i);
Close(sockfd);
client[i].fd = -1;
} else {
perr_exit("read error");
}
} else if (n == 0) {
/* connection closed by client */
printf("client[%d] closed connection\n", i);
Close(sockfd);
client[i].fd = -1;
} else {
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Writen(sockfd, buf, n);
}
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
return 0;
}
client實(shí)現(xiàn)
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) {
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n");
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}
ppoll
GNU定義了ppoll(非POSIX標(biāo)準(zhǔn)),可以支持設(shè)置信號(hào)屏蔽字。
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <poll.h>
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts, const sigset_t *sigmask);
epoll
epoll是Linux下多路復(fù)用IO接口select/poll的增強(qiáng)版本,它能顯著提高程序在大量并發(fā)連接中只有少量活躍的情況下的系統(tǒng)CPU利用率,因?yàn)樗鼤?huì)復(fù)用文件描述符集合來(lái)傳遞結(jié)果而不用迫使開發(fā)者每次等待事件之前都必須重新準(zhǔn)備要被偵聽的文件描述符集合,另一點(diǎn)原因就是獲取事件的時(shí)候,它無(wú)須遍歷整個(gè)被偵聽的描述符集,只要遍歷那些被內(nèi)核IO事件異步喚醒而加入Ready隊(duì)列的描述符集合就行了。
目前epell是linux大規(guī)模并發(fā)網(wǎng)絡(luò)程序中的熱門首選模型。
epoll除了提供select/poll那種IO事件的電平觸發(fā)(Level Triggered)外,還提供了邊沿觸發(fā)(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態(tài),減少epoll_wait/epoll_pwait的調(diào)用,提高應(yīng)用程序效率。
可以使用cat命令查看一個(gè)進(jìn)程可以打開的socket描述符上限。
cat /proc/sys/fs/file-max
如有需要,可以通過修改配置文件的方式修改該上限值。
sudo vi /etc/security/limits.conf
在文件尾部寫入以下配置,soft軟限制,hard硬限制。
* soft nofile 65536
* hard nofile 100000
基礎(chǔ)API
創(chuàng)建一個(gè)epoll句柄,參數(shù)size用來(lái)告訴內(nèi)核監(jiān)聽的文件描述符的個(gè)數(shù),跟內(nèi)存大小有關(guān)。
#include <sys/epoll.h>
int epoll_create(int size) //size:監(jiān)聽數(shù)目
控制某個(gè)epoll監(jiān)控的文件描述符上的事件:注冊(cè)、修改、刪除。
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd: 為epoll_creat的句柄。
op: 表示動(dòng)作,用3個(gè)宏來(lái)表示:
-EPOLL_CTL_ADD (注冊(cè)新的fd到epfd),
-EPOLL_CTL_MOD (修改已經(jīng)注冊(cè)的fd的監(jiān)聽事件),
-EPOLL_CTL_DEL (從epfd刪除一個(gè)fd);
event: 告訴內(nèi)核需要監(jiān)聽的事件。
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
EPOLLIN : 表示對(duì)應(yīng)的文件描述符可以讀(包括對(duì)端SOCKET正常關(guān)閉)。
EPOLLOUT: 表示對(duì)應(yīng)的文件描述符可以寫。
EPOLLPRI: 表示對(duì)應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來(lái))。
EPOLLERR: 表示對(duì)應(yīng)的文件描述符發(fā)生錯(cuò)誤。
EPOLLHUP: 表示對(duì)應(yīng)的文件描述符被掛斷。
EPOLLET: 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對(duì)于水平觸發(fā)(Level Triggered)而言的。
EPOLLONESHOT:只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個(gè)socket的話,需要再次把這個(gè)socket加入到EPOLL隊(duì)列里。
等待所監(jiān)控文件描述符上有事件的產(chǎn)生,類似于select()調(diào)用。
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
events: 用來(lái)存內(nèi)核得到事件的集合。
maxevents: 告之內(nèi)核這個(gè)events有多大,這個(gè)maxevents的值不能大于創(chuàng)建epoll_create()時(shí)的size。
timeout: 是超時(shí)時(shí)間
-1: 阻塞
0: 立即返回,非阻塞
>0: 指定毫秒
返回值: 成功返回有多少文件描述符就緒,時(shí)間到時(shí)返回0,出錯(cuò)返回-1
server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024
int main(int argc, char *argv[])
{
int i, j, maxi, listenfd, connfd, sockfd;
int nready, efd, res;
ssize_t n;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
int client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
struct epoll_event tep, ep[OPEN_MAX];
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
Listen(listenfd, 20);
for (i = 0; i < OPEN_MAX; i++)
client[i] = -1;
maxi = -1;
efd = epoll_create(OPEN_MAX);
if (efd == -1)
perr_exit("epoll_create");
tep.events = EPOLLIN; tep.data.fd = listenfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
if (res == -1)
perr_exit("epoll_ctl");
while (1) {
nready = epoll_wait(efd, ep, OPEN_MAX, -1); /* 阻塞監(jiān)聽 */
if (nready == -1)
perr_exit("epoll_wait");
for (i = 0; i < nready; i++) {
if (!(ep[i].events & EPOLLIN))
continue;
if (ep[i].data.fd == listenfd) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (j = 0; j < OPEN_MAX; j++) {
if (client[j] < 0) {
client[j] = connfd; /* save descriptor */
break;
}
}
if (j == OPEN_MAX)
perr_exit("too many clients");
if (j > maxi)
maxi = j; /* max index in client[] array */
tep.events = EPOLLIN;
tep.data.fd = connfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
if (res == -1)
perr_exit("epoll_ctl");
} else {
sockfd = ep[i].data.fd;
n = Read(sockfd, buf, MAXLINE);
if (n == 0) {
for (j = 0; j <= maxi; j++) {
if (client[j] == sockfd) {
client[j] = -1;
break;
}
}
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
if (res == -1)
perr_exit("epoll_ctl");
Close(sockfd);
printf("client[%d] closed connection\n", j);
} else {
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Writen(sockfd, buf, n);
}
}
}
}
close(listenfd);
close(efd);
return 0;
}
client文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-520912.html
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) {
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n");
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-520912.html
到了這里,關(guān)于計(jì)算機(jī)網(wǎng)絡(luò)編程 | 多路I/O轉(zhuǎn)接服務(wù)器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!