断断续续的看Beej’s Guide to Network Programming这本小册子,最近看了select函数的用法,感觉这个函数挺有用而且听特殊,理解上有点儿难度,因此记录下来关于这个函数的一些东西.
函数定义
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int numfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
fd_set
这里的fd_set类型相当于一个file descriptor的集合.
当select()函数返回时,readfs中只含有可以读的file descriptor.writefs包含可写的descriptor,exceptfds包含出错的file descriptor .(此三个参数不必都有) fd_set有一些列的操作函数:
FD_SET(int fd, fd_set *set); //Add fd to the set .
FD_CLR(int fd, fd_set *set); //Remove fd from the set .
FD_ISSET(int fd, fd_set *set); //Return true if fd is in the set .
FD_ZERO(fd_set *set); //Clear all entries from the set .
timeval
结构体
struct timeval {
int tv_sec; //seconds
int tv_usec;// microseconds
};
此结构体表示select函数的超时时间.设置为NULL则永远等待. 此函数使用时,numfds应设置为最高的file descriptor的值.
返回值
返回set中最后剩下的的数目.0表示超时,-1表示出错.
此函数如何使用的理解
我们可以在开始的时候使用FD_SET
将我们创建的服务端套接字描述符填入一个fd_set中,然后把这个fd_set复制一下传如select的readfds参数中然后用select监听,select有返回值后则遍历所有descriptors
- 如果这个descriptor在这个新的readfds中(
FD_ISSET
)则说明可以read- 如果这个descriptor正好是我们最初的那个服务端套接字
- 则可以进行accept操作,然后将读到的这个socksetfd就可以传入我们的fd_set中,判断这个socketfd是否比原有的最大的socketfd大,大则替换
select()
函数的第一个参数为这个新的socketfd+1.
- 则可以进行accept操作,然后将读到的这个socksetfd就可以传入我们的fd_set中,判断这个socketfd是否比原有的最大的socketfd大,大则替换
- 如果这个descriptor不是最初的那个服务端套接字
- 则说明是与某个主机的一个连接,则可以调用这个套接字的
recv()
方法接收数据,如果返回0说明这个套接字已经断开,可调用FD_CLR
将其从最初的fd_set中清除出去.否则则进行其他的一些列操作.
- 则说明是与某个主机的一个连接,则可以调用这个套接字的
- 如果这个descriptor正好是我们最初的那个服务端套接字
一个简单的demo
/*
** ChatServer.cpp - a practise on select()
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#define PORT "3456"
#define STDIN 0
void *get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET)
{
return &(((struct sockaddr_in *)sa)->sin_addr);
}
else
return &(((struct sockaddr_in6 *)sa)->sin6_addr);
}
int main(void)
{
fd_set master;
fd_set read_fds;
int fd_max;
int listener;
int newfd;
struct sockaddr_storage remoteaddr;
socklen_t addrlen;
char buf[256];
char mess[256];
int nbytes;
char remoteIP[INET6_ADDRSTRLEN];
int yes = 1;
int i, j, rv;
struct addrinfo hints, *ai, *p;
FD_ZERO(&master);
FD_ZERO(&read_fds);
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0)
{
fprintf(stderr, "selectserver:%s\n", gai_strerror(rv));
exit(1);
}
for (p = ai; p != NULL; p = p->ai_next)
{
listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (listener < 0)
{
continue;
}
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
if (bind(listener, p->ai_addr, p->ai_addrlen) < 0)
{
close(listener);
continue;
}
break;
}
if (p == NULL)
{
fprintf(stderr, "selectserver: failed to bind\n");
exit(2);
}
freeaddrinfo(ai);
if (listen(listener, 10) == -1)
{
perror("listen");
exit(3);
}
FD_SET(listener, &master);
FD_SET(STDIN, &master);
fd_max = listener > STDIN ? listener : STDIN;
for (;;)
{
read_fds = master;
if (select(fd_max + 1, &read_fds, NULL, NULL, NULL) == -1)
{
perror("select");
exit(4);
}
for (i = 0; i <= fd_max; i++)
{
if (FD_ISSET(i, &read_fds))
{ //we got one!!
if (i == STDIN)
{
fgets(buf, sizeof buf, stdin);
memset(mess, 0, sizeof mess);
sprintf(mess, "server said:%s", buf);
for (j = 0; j <= fd_max; j++)
{
if (FD_ISSET(j, &master))
{
if (j != listener && j != i && j != STDIN)
{
if (send(j, mess, strlen(mess), 0) == -1)
{
perror("send");
}
}
}
}
}
else if (i == listener)
{
addrlen = sizeof remoteaddr;
newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
if (newfd == -1)
{
perror("accept");
}
else
{
FD_SET(newfd, &master);
if (newfd > fd_max)
{
fd_max = newfd;
}
printf("selectserver:new connection from %s on socket %d\n",
inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr *)&remoteaddr), remoteIP, INET6_ADDRSTRLEN), newfd);
sprintf(buf, "hello!!! your socket id is %d\n", newfd);
if (send(newfd, buf, strlen(buf), 0) == -1)
{
perror("send");
}
sprintf(buf, "socket %d joined chatroom\n", newfd);
for (j = 0; j < fd_max; j++)
{
if (FD_ISSET(j, &master))
{
if (j != i && j != listener && j != STDIN)
{
if (send(j, buf, strlen(buf), 0) == -1)
perror("send");
}
}
}
}
}
else
{
if ((nbytes = recv(i, buf, sizeof buf, 0)) <= 0)
{
if (nbytes == 0)
{
sprintf(buf, "select server: socket %d hung up\n", i);
printf("%s", buf);
for (j = 0; j <= fd_max; j++)
{
if (FD_ISSET(j, &master))
if (j != listener && j != i && j != STDIN)
{
if (send(j, buf, strlen(buf), 0) == -1)
{
perror("send");
}
}
}
}
else
{
perror("recv");
}
close(i);
FD_CLR(i, &master);
}
else
{
if (nbytes <= 2)
nbytes = 3;
memset(mess, 0, sizeof mess);
strncpy(mess, buf, nbytes - 2);
printf("recv data %s from socket %d\n", mess, i);
sprintf(buf, "socket %d says: %s\n", i, mess);
for (j = 0; j <= fd_max; j++)
{
if (FD_ISSET(j, &master))
{
if (j != listener && j != i && j != STDIN)
{
if (send(j, buf, strlen(buf), 0) == -1)
{
perror("send");
}
}
}
}
}
}
}
}
}
return 0;
}
要特别注意一次select操作后readfs中只剩下了当前可读的fd.所以每次要复制一个副本穿进去.
题外话
还是认真学习认真思考能让人不那么丧…..庆幸自己找到了自己真正的兴趣所在.
不要让一些没意义的东西麻痹了自己. 要时刻提醒自己生存不易.
不高兴了就更更博,耐心看看技术书,让自己静一静.闲了就容易生事.
keep on moving!!!