int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数:
- nfds:监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件文件描述符
- readfs:监控有读数据到达文件描述符集合,传入传出参数
- writefds:监控写数据到达文件描述符集合,传入传出参数
- exceptfds:监控异常发生到达文件描述符集合,如带外数据到达异常,传入传出参数
- timeout:定时阻塞监控时间。
#include <sys/select.h>
void FD_CLR(int fd, fd_set *set); // 把文件描述符集里fd位清0
int FD_ISSET(int fd, fd_set *set); // 测试文件描述符集里fd是否置1
void FD_SET(int fd, fd_set *set); // 把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); // 把文件描述符集合里所有位清0
二、select函数用法图解
三、实验一
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>void perr_exit(const char *s)
{perror(s);exit(-1);
}int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{int n;again:if ((n = accept(fd, sa, salenptr)) < 0) {//ECONNABORTED 发生在重传(一定次数)失败后,强制关闭套接字//EINTR 进程被信号中断if ((errno == ECONNABORTED) || (errno == EINTR)){goto again; }else{perr_exit("accept error");}}return n;
}int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = bind(fd, sa, salen)) < 0){perr_exit("bind error");}return n;
}int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;n = connect(fd, sa, salen);if (n < 0) {perr_exit("connect error");}return n;
}int Listen(int fd, int backlog)
{int n;if ((n = listen(fd, backlog)) < 0){perr_exit("listen error");}return n;
}int Socket(int family, int type, int protocol)
{int n;if ((n = socket(family, type, protocol)) < 0){perr_exit("socket error");}return n;
}ssize_t Read(int fd, void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = read(fd, ptr, nbytes)) == -1) {if (errno == EINTR)goto again;elsereturn -1;}return n;
}ssize_t Write(int fd, const void *ptr, size_t nbytes)
{ssize_t n;again:if ((n = write(fd, ptr, nbytes)) == -1) {if (errno == EINTR)goto again;elsereturn -1;}return n;
}int Close(int fd)
{int n;if ((n = close(fd)) == -1)perr_exit("close error");return n;
}
#ifndef __WRAP_H_
#define __WRAP_H_void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);#endif
- 服务端server.c
//server.c
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/select.h>
#include "wrap.h"#define SERV_PROT 6666int main(int argc, char *argv[])
{int i, j, n, nread;int maxfd = 0;int listenfd, connfd;char buf[BUFSIZ];struct sockaddr_in clie_addr, serv_addr;socklen_t clie_addr_len;listenfd = Socket(AF_INET, SOCK_STREAM, 0);int opt = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(SERV_PROT);Bind(listen, (struct sockaddr*)&serv_addr, sizeof(serv_addr));Listen(listenfd, 128);fd_set rset, allset;maxfd = listenfd;FD_ZERO(&allset);FD_SET(listenfd, &allset);while (1){rset = allset;nread = select(maxfd + 1, &rset, NULL, NULL, NULL); //每次循环都从新设置select监控信号集if(nread < 0)perr_exit("select error");if (FD_ISSET(listenfd, &rset)) //说明有新的客户端连接请求{clie_addr_len = sizeof(clie_addr);connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);FD_SET(connfd, &allset); //向监控文件描述符集合allset添加新的文件描述符connfdif (maxfd < connfd)maxfd = connfd;if (0 == --nread) //说明select只返回一个,并且listenfd,后续执行无须执行continue;}for (i = listenfd + 1; i <= maxfd; i++){if (FD_ISSET(i, &rset)){if ((n = Read(i, buf, sizeof(buf))) == 0) //当client关闭链接时, 服务端也关闭对应链接{Close(i);FD_CLR(i, &allset); //解除select对此文件描述符的监控}else if(n == -1)perr_exit("read error");for (j = 0; j < n; ++j)buf[j] = toupper(buf[j]);Write(i, buf, n);}}}Close(listenfd);return 0;
}
- 客户端:clent.c
#include <arpa/inet.h>
#include "wrap.h"#define SERV_IP "192.168.245.139"
#define SERV_PORT 6666int main(void)
{int sfd, len;struct sockaddr_in serv_addr;char buf[BUFSIZ];sfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);serv_addr.sin_port = htons(SERV_PORT);Connect(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));while (1) {fgets(buf, sizeof(buf), stdin);int r = Write(sfd, buf, strlen(buf));printf("Write r ======== %d\n", r);len = Read(sfd, buf, sizeof(buf));printf("Read len ========= %d\n", len);Write(STDOUT_FILENO, buf, len);}Close(sfd);return 0;
}
- 公共头文件集程序:wrap.h、wrap.c
输出结果:
客户端:
服务端: