网络管理考试最后一道大题涉及socket和多路转接I/O的select函数。哎,感觉复习的时候还是没有把握到精髓,那道题就有些遗憾了……
多路转接I/O:
先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。在返回时,它告诉进程哪些描述符已准备好可以进行I/O。
select函数
作用:允许程序监视多个文件描述符,以等待一个或多个文件描述符变为ready,以便进行I/O操作。
Ready:一个文件描述符可以执行相应I/O操作而不会被blocking时,该文件描述符被认为是ready
格式:#inlucde <sys/select.h>
int select(int maxfdp1,
fd_set *restrict readfds,
fd_set *restrict writefds,
fd_set *restrict exceptfds,
struct timeval *restrict tvptr);
•返回值:
—返回准备就绪的描述符,所以正返回值表示已经准备好的描述符
—若超时则返回0,表示没有描述符准备好
—若出错则返回-1
参数 timeout为结构timeval,用来设置select()的等待时间,其结构定义如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
有三种情况:
永远等待。如果捕捉到一个信号则中断此无限期等待
。当所指定的描述符中的一个已准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR
- tvptr->tv_sec == 0 && tvptr->tv_usec == 0
完全不等待。测试所有指定的描述符并立即返回。
- tvptr->tv_sec != 0 || tvptr->tv_usec != 0
等待指定的秒数和微妙数。当指定的描述符之一已准备好或当指定的时间值已经超过时立即返回。
如果在超时时还没有一个描述符准备好,则返回值是0.与第一种情况一样,这种等待可被捕捉到的信号中断。
中间三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读、可写或处于异常条件的各个描述符。每个描述符集存放在一个fd_set数据类型中。这种数据类型为每一可能的描述符保持了一位。
select的中间三个参数(指向描述符集的指针)中的任意一个或全部都可以是空指针,这表示对相应状态并不关心。如果所有三个指针都是空指针,则select提供了较sleep更精确的计时器。
select的第一个参数maxfdp1的意思是“最大描述符加1”。在三个描述符集中找出最大描述符值,然后加1,这就是第一个参数。也可以将第一个参数设置为FD_SETSIZE,这是<sys/select.h>中的一个常量,它说明了最大的描述符(经常是1024)。如果将第一个参数设置为我们关注的最大描述符编号值加1,内核就只需在此范围内寻找打开的位,而不必在三个描述符集中的数百位内搜索。
描述符集的处理函数:
FD_CLR(inr fd,fd_set* set); //用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set); //用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set); //用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set); //用来清除描述词组set的全部位
使用select函数的过程一般是:
先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。
以下是一个测试单个文件描述字可读性的例子:
int isready(int fd)
{
int rc;
fd_set fds;
struct tim tv;
FD_ZERO(&fds);
FD_SET(fd,&fds);
tv.tv_sec = tv.tv_usec = 0;
rc = select(fd+1, &fds, NULL, NULL, &tv);
if (rc < 0) //error
return -1;
return FD_ISSET(fd,&fds) ? 1 : 0;
}
基于select实现的网络服务器和客户端:
server.c
-
#include<stdio.h>
-
#include<sys/types.h>
-
#include<sys/socket.h>
-
#include<unistd.h>
-
#include<stdlib.h>
-
#include<errno.h>
-
#include<arpa/inet.h>
-
#include<netinet/in.h>
-
#include<string.h>
-
#include<signal.h>
-
#include<sys/wait.h>
-
-
-
-
-
-
-
#define ERR_EXIT(m) \
-
do { \
-
perror(m); \
-
exit(EXIT_FAILURE); \
-
} while (0)
-
-
-
int main(void)
-
{
-
signal(SIGPIPE, SIG_IGN);
-
-
-
int listenfd;
-
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
-
ERR_EXIT("socket error");
-
-
struct sockaddr_in servaddr;
-
-
-
-
-
servaddr.sin_family = AF_INET;
-
servaddr.sin_port = htons(8080);
-
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
-
-
-
-
-
int on = 1;
-
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
-
ERR_EXIT("setsockopt error");
-
-
-
if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
-
ERR_EXIT("bind error");
-
-
-
if (listen(listenfd, SOMAXCONN) < 0)
-
ERR_EXIT("listen error");
-
-
struct sockaddr_in peeraddr;
-
socklen_t peerlen = sizeof(peeraddr);
-
-
int conn;
-
int i;
-
int client[FD_SETSIZE];
-
int maxi = 0;
-
for (i = 0; i < FD_SETSIZE; i++)
-
client[i] = -1;
-
-
int nready;
-
int maxfd = listenfd;
-
fd_set rset;
-
fd_set allset;
-
FD_ZERO(&rset);
-
FD_ZERO(&allset);
-
FD_SET(listenfd, &allset);
-
-
while (1) {
-
rset = allset;
-
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
-
if (nready == -1) {
-
if (errno == EINTR)
-
continue;
-
ERR_EXIT("select error");
-
}
-
-
if (nready == 0)
-
continue;
-
-
if (FD_ISSET(listenfd, &rset)) {
-
-
conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);
-
if (conn == -1)
-
ERR_EXIT("accept error");
-
-
for (i = 0; i < FD_SETSIZE; i++) {
-
if (client[i] < 0) {
-
client[i] = conn;
-
if (i > maxi)
-
maxi = i;
-
break;
-
}
-
}
-
-
if (i == FD_SETSIZE) {
-
fprintf(stderr, "too many clients\n");
-
exit(EXIT_FAILURE);
-
}
-
-
printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),
-
ntohs(peeraddr.sin_port));
-
-
FD_SET(conn, &allset);
-
if (conn > maxfd)
-
maxfd = conn;
-
-
if (--nready <= 0)
-
continue;
-
}
-
-
for (i = 0; i <= maxi; i++) {
-
conn = client[i];
-
if (conn == -1)
-
continue;
-
-
if (FD_ISSET(conn, &rset)) {
-
-
char recvbuf[1024] = {0};
-
int ret = read(conn, recvbuf, 1024);
-
if (ret == -1)
-
ERR_EXIT("readline error");
-
else if (ret == 0) {
-
printf("client close \n");
-
FD_CLR(conn, &allset);
-
client[i] = -1;
-
close(conn);
-
}
-
-
fputs(recvbuf, stdout);
-
write(conn, recvbuf, strlen(recvbuf));
-
-
if (--nready <= 0)
-
break;
-
}
-
}
-
}
-
return 0;
-
}
client.c
-
#include<stdio.h>
-
#include<sys/types.h>
-
#include<sys/socket.h>
-
#include<unistd.h>
-
#include<stdlib.h>
-
#include<errno.h>
-
#include<arpa/inet.h>
-
#include<netinet/in.h>
-
#include<string.h>
-
-
-
#define ERR_EXIT(m) \
-
do { \
-
perror(m); \
-
exit(EXIT_FAILURE); \
-
} while (0)
-
-
-
-
-
int main(void)
-
{
-
int sock;
-
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
-
-
ERR_EXIT("socket error");
-
-
-
struct sockaddr_in servaddr;
-
memset(&servaddr, 0, sizeof(servaddr));
-
servaddr.sin_family = AF_INET;
-
servaddr.sin_port = htons(8080);
-
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
-
-
-
if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
-
ERR_EXIT("connect error");
-
struct sockaddr_in localaddr;
-
char cli_ip[20];
-
socklen_t local_len = sizeof(localaddr);
-
memset(&localaddr, 0, sizeof(localaddr));
-
if( getsockname(sock,(struct sockaddr *)&localaddr,&local_len) != 0 )
-
ERR_EXIT("getsockname error");
-
inet_ntop(AF_INET, &localaddr.sin_addr, cli_ip, sizeof(cli_ip));
-
printf("host %s:%d\n", cli_ip, ntohs(localaddr.sin_port));
-
-
fd_set rset;
-
FD_ZERO(&rset);
-
int nready;
-
int maxfd;
-
int fd_stdin = fileno(stdin);
-
if (fd_stdin > sock)
-
maxfd = fd_stdin;
-
else
-
maxfd = sock;
-
char sendbuf[1024] = {0};
-
char recvbuf[1024] = {0};
-
-
while (1)
-
{
-
-
FD_SET(fd_stdin, &rset);
-
FD_SET(sock, &rset);
-
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
-
if (nready == -1)
-
ERR_EXIT("select error");
-
-
if (nready == 0)
-
continue;
-
-
if (FD_ISSET(sock, &rset))
-
{
-
-
int ret = read(sock, recvbuf, sizeof(recvbuf));
-
if (ret == -1)
-
ERR_EXIT("read error");
-
else if (ret == 0)
-
{
-
printf("server close\n");
-
break;
-
}
-
-
fputs(recvbuf, stdout);
-
memset(recvbuf, 0, sizeof(recvbuf));
-
}
-
-
if (FD_ISSET(fd_stdin, &rset))
-
{
-
-
if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
-
break;
-
-
write(sock, sendbuf, strlen(sendbuf));
-
memset(sendbuf, 0, sizeof(sendbuf));
-
}
-
}
-
-
close(sock);
-
return 0;
-
}