用select来管理多个IO。一旦其中的一个IO或者多个IO检测到我们所感兴趣的事件,select函数返回,返回值为检测到的事件个数,并且返回那些IO发生了事件。
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数说明:
1、读、写、异常集合中的文件描述符的最大值+1.
2、读集合。
3、写集合
4、异常
5.超时结构体
读、写、异常事件发生条件:
1.可读:
套接口缓冲区有数据可读。
连接的读一半关闭,即接收到FIN段,读操作将返回0。
如果是监听套接口,已完成连接队列不为空时。
套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。
2.可写:
套接口发送缓冲区有空间可容纳数据。
连接的写一半关闭。即收到RST段之后,再次调用write操作。
套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。
3.异常:
套接口存在带外数据。
附上测试代码:
服务器端:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0); \
struct packet
{
int len;
char buf[1024];
};
ssize_t readn(int fd,void *buf,size_t count)
{
size_t nleft = count; //剩余要读取的字节数
ssize_t nread;
char *bufp = (char*)buf;
while(nleft>0)
{
if( (nread = read(fd,bufp,nleft)) < 0)
{
if(errno == EINTR) /*被信号中断*/
continue;
return -1; //读取失败
}
else if(nread == 0)
{
return count-nleft; //剩余的字节数
}
else
{
bufp += nread; //设置下一次读取时的位置
nleft -= nread; //剩余要读取的字节数
}
}
return count; //全部的字节数
}
ssize_t writen(int fd,const void *buf,size_t count)
{
size_t nleft = count; //剩余要写入的字节数
ssize_t nwritten;
char *bufp = (char*)buf;
while(nwritten > 0)
{
if( (nwritten = write(fd,bufp,nleft)) < 0 )
{
if(errno == EINTR) /*被信号中断*/
continue;
return -1;
}
else if(nwritten == 0)
{
return count-nleft; //剩余的字节数
}
else
{
bufp += nwritten; //设置下一次写入的位置
nleft -= nwritten;
}
}
return count;
}
void handle_sigpipe(int sig)
{
printf("recv sig = %d\n",sig);
}
int main(void)
{
signal(SIGPIPE,handle_sigpipe);
int listenfd;
if((listenfd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
{
ERR_EXIT("socket");
}
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr)); //初始化
servaddr.sin_family = AF_INET; //地址族
servaddr.sin_port = htons(5188); //端口号
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");
}
/*绑定*/
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0 )
{
ERR_EXIT("bind");
}
/*绑定监听*/
if(listen(listenfd,SOMAXCONN) < 0)
{
ERR_EXIT("listen");
}
struct sockaddr_in peeraddr; /*目标地址*/
socklen_t peerlen;
int conn;
struct packet recvbuf;
memset(&recvbuf,0,sizeof(recvbuf));
int n;
int client[FD_SETSIZE];
int i;
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");
}
if (nready == 0)
continue;
/*监听套接口产生的事件*/
if (FD_ISSET(listenfd,&rset))
{
peerlen = sizeof(peeraddr);
conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
if (conn == -1)
ERR_EXIT("accept");
for (i=0; i<FD_SETSIZE; i++)
{
if (client[i] < 0)
{
client[i] = conn;
break;
}
}
if (i == FD_SETSIZE)
{
fprintf(stderr,"too many clients\n");
exit(EXIT_FAILURE);
}
printf("ip = %s,port = %d\n",inet_ntoa(peeraddr.sin_addr),peeraddr.sin_port);
FD_SET(conn,&allset);
if (conn > maxfd)
maxfd = conn;
if (--nready <= 0)
continue;
}
/*已连接套接口产生的事件*/
for (i=0; i<FD_SETSIZE; i++)
{
conn = client[i];
if (conn == -1)
continue;
if (FD_ISSET(conn,&rset))
{
memset(&recvbuf,0,sizeof(recvbuf));
int ret = readn(conn,&recvbuf.len,4); //先接收4个字节
if(ret == -1)
{
ERR_EXIT("read");
}
if(ret < 4)
{
printf("click close\n");
FD_CLR(conn,&allset);
client[i] = -1;
break;
}
n = ntohl(recvbuf.len);
ret = readn(conn,&recvbuf.buf,n);
if(ret == -1)
{
ERR_EXIT("read");
}
if(ret < n)
{
printf("click close\n");
FD_CLR(conn,&allset);
client[i] = -1;
close(client[i]);
}
fputs(recvbuf.buf,stdout);
writen(conn,&recvbuf,4+n);
if (--nready <= 0 )
break;
}
}
}
return 0;
}
客户端:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0); \
struct packet
{
int len; //存放实际的数据长度
char buf[1024];
};
ssize_t readn(int fd,void *buf,size_t count)
{
size_t nleft = count; //剩余要读取的字节数
ssize_t nread;
char *bufp = (char*)buf;
while(nleft>0)
{
if( (nread = read(fd,bufp,nleft)) < 0)
{
if(errno == EINTR) /*被信号中断*/
continue;
return -1; //读取失败
}
else if(nread == 0)
{
printf("peer close\n");
return count-nleft; //剩余的字节数
}
else
{
bufp += nread; //设置下一次读取时的位置
nleft -= nread; //剩余要读取的字节数
}
}
return count; //全部的字节数
}
ssize_t writen(int fd,const void *buf,size_t count)
{
size_t nleft = count; //剩余要写入的字节数
ssize_t nwritten;
char *bufp = (char*)buf;
while(nwritten > 0)
{
if( (nwritten = write(fd,bufp,nleft)) < 0 )
{
if(errno == EINTR) /*被信号中断*/
continue;
return -1;
}
else if(nwritten == 0)
{
return count-nleft; //剩余的字节数
}
else
{
bufp += nwritten; //设置下一次写入的位置
nleft -= nwritten;
}
}
return count;
}
int main(void)
{
int sock;
if((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
{
ERR_EXIT("socket");
}
/*if(listenfd = socket(PF_INET,SOCK_STREAM,0)<0)*/
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr)); //初始化
servaddr.sin_family = AF_INET; //地址族
servaddr.sin_port = htons(5188); //端口号
/*servaddr.sin_addr.s_addr = htonl(INADDR_ANY);*/
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
/*iner_aton("127.0.0.1",&servaddr.sin_addr);*/
if( connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
{
ERR_EXIT("connect");
}
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;
struct packet sendbuff;
struct packet recvbuff;
memset(&sendbuff,0,sizeof(sendbuff));
memset(&recvbuff,0,sizeof(recvbuff));
printf("ip = %s,port = %d\n",inet_ntoa(servaddr.sin_addr),servaddr.sin_port);
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");
if (nready == 0)
continue;
if (FD_ISSET(sock,&rset))
{
int ret = readn(sock,&recvbuff.len,4); //先接收4个字节
if(ret == -1)
{
ERR_EXIT("read");
}
else if(ret < 4)
{
printf("click close\n");
break;
}
int n = ntohl(recvbuff.len);
ret = readn(sock,&recvbuff.buf,n);
if(ret == -1)
{
ERR_EXIT("read");
}
else if(ret < n)
{
printf("server close\n");
break;
}
fputs(recvbuff.buf,stdout);
memset(&recvbuff,0,sizeof(recvbuff));
}
if (FD_ISSET(fd_stdin,&rset))
{
if(fgets(sendbuff.buf,sizeof(sendbuff.buf),stdin) == NULL)
{
shutdown(sock,SHUT_WR);
}
int n = strlen(sendbuff.buf);
sendbuff.len = htonl(n);
writen(sock,&sendbuff,4+n);
memset(&sendbuff,0,sizeof(sendbuff));
}
}
return 0;
}