在实际情况中,人们往往遇到多个客户端连接服务端的情况。由于connect()、recv()和send()等都是阻塞性函数,如果资源没有准备好,则调用该函数的进程将进入睡眠状态,这样就无法处理I/O多路复用的情况。以下给出两个解决办法:
1.fcntl()
(1)fcntl()回顾:
所需头文件 | #include #include #include |
|
---|---|---|
函数原型 | int fcntl(int fd,int cmd,struct flock *lock) | |
函数传入值 | fd:文件描述符 | |
cmd | F_DUPFD:复制文件描述符 | |
F_GETFD:获得fd的close-on-exec标志,若标志未设置,则文件经过exec()函数之后仍保持打开状态 | ||
F_SETFD:设置close-on-exec标志,该标志由参数arg的FD_CLOEXEC位决定 | ||
F_GETFL:得到open设置的标志 | ||
F_SETFL:改变open设置的标志 | ||
F_GETLK:根据lock参数值,决定是否上文件锁 | ||
F_SETLK:设置lock参数值的文件锁 | ||
F_SETLKW:这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。在无法获取锁时,会进入睡眠状态;如果可以获取锁或者捕捉到信号则会返回 | ||
lock:结构为flock,设置记录锁的具体状态 | ||
函数返回值 | 0:成功 | |
-1:出错 |
lock的结构如下所示:
struct flock
{
short l_type;
//F_RDLCK:读取锁(共享锁)
//F_WRLCK:写入锁(排斥锁)
//F_UNLCK:解锁
off_t l_start; //相对位移量(字节)
short l_whence; //相对位移量的起点
//SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小
//SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量
//SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小
off_t l_len; //加锁区域的长度
pid_t l_pid;
}
(2)函数fcntl()针对socket编程提供了如下的编程特性:
(1)非阻塞I/O:可将cmd设置为F_SETFL,将lock设置为O_NONBLOCK。 |
(2)异步I/O:可将cmd设置为F_SETFL,将lock设置为O_ASYNC。 |
下面是用fcntl()将套接字设置为非阻塞I/O的实例代码:
/*net_fcntl.c*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234 //端口号
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5
int main()
{
struct sockaddr_in server_sockaddr,client_sockaddr; /* Structure describing an Internet (IP) socket address. */
int sin_size,recvbytes,flags;
int sockfd,client_fd;//sockfd=文件描述符
char buf[BUFFER_SIZE];
/*建立socket连接*/
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)//AF_INET=IPv4,SOCK_STREAM=TCP
{
perror("socket");
exit(1);
}
printf("Socket id = %d\n",sockfd);
/*设置sockaddr_in结构体中相关参数*/
server_sockaddr.sin_family = AF_INET; //sin_family=地址族,AF_INET=IPv4
server_sockaddr.sin_port = htons(PORT);//sin_port=端口号,htons()主机字节序>转化
server_sockaddr.sin_addr.s_addr = INADDR_ANY;//s_addr=IP地址
bzero(&(server_sockaddr.sin_zero),8);//填充0以保持与struct sockaddr同样大小
int i=1;//允许重复使用本地地址与套接字进行绑定
if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))) < 0)//用于任意类型、任意状态套接口的设置选项值
{
perror("setsockopt");
exit(1);
}
/*sockfd = 标识一个套接口的描述字
level = 选项定义的层次:支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
optname = 需设置的选项
optval = 指针,指向存放选项待设置的新值的缓冲区
optlen = optval缓冲区长度*/
/*绑定函数bind()*/
if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)//server_sockaddr = 本地地址
{ //sizeof(struct sockaddr) = 地址长度
perror("bind");
exit(1);
}
printf("Bind success!\n");
/*调用listen()函数,创建未处理请求的队列*/
if(listen(sockfd,MAX_QUE_CONN_NM) == -1)//MAX_QUE_CONN_NM=请求队列中允许的>最大请求数,大多数系统默认值为5
{
perror("listen");
exit(1);
}
printf("Listening...\n");
flags = fcntl(sockfd,F_GETFL);
if(flags < 0 || fcntl(sockfd,F_SETFL,flags|O_NONBLOCK) < 0)
{
perror("fcntl");
exit(1);
}
while(1)
{
sin_size = sizeof(struct sockaddr_in);
/*调用accept()函数,等待客户端的连接*/
if((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size)) == -1)//client_sockaddr = 客户端地址
{ //地址长度
perror("accept");
exit(1);
}
/*调用recv()函数接收客户端的请求*/
if((recvbytes = recv(client_fd,buf,BUFFER_SIZE,0)) == -1)//返回接收
的字节数
{
perror("recv");
exit(1);
}
printf("Received a message:%s\n",buf);
}
close(sockfd);
exit(0);
}
2.select()
(1)select()回顾
所需头文件 | #include #include #include |
|
---|---|---|
函数原型 | int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exeptfds,struct timeval *timeout) | |
函数传入值 | numfds:该参数值为需要监视的文件描述法的最大值加1 | |
readfds:由select()监视的读文件描述符集合 | ||
writefds:由select()监视的写文件描述符集合 | ||
exeptfds:由select()监视的异常处理文件描述符集合 | ||
timeout | NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止 | |
具体值:struct timeval 类型的指针,若等待了timeout时间还没有检测到任何文件描述符准备好,就立即返回 | ||
0:从不等待,测试所有指定的描述符并立即返回 | ||
函数返回值 | 大于0:成功,返回准备好的文件描述符的数量 | |
0:超时 | ||
-1:出错 |
对文件描述符进行处理的宏函数:
FD_ZERO(fd_set *set) | 清除一个文件描述符集合 |
FD_SET(int fd,fd_set *set) | 将一个文件描述符加入文件描述符集合 |
FD_CLR(int fd,fd_set *set) | 将一个文件描述法从文件描述符集中清除 |
FD_ISSET(int fd,fd_set *set) | 如果文件描述法fd为fd_set集合中的一个元素,则返回非零值,可以用于调用select()之后测试文件描述符集合中的文件描述符是否有变化 |
PS:在使用select()之前,首先使用FD_ZERO()和FD_SET()来初始化文件描述符集合;
在使用select()时,循环使用FD_ISSET()来测试描述符集合;
在执行完相关描述符的操作后,使用FD_CLR()来清除描述符集合;
select()函数中的timeout是一个struct timeval类型的指针,结构体如下:
struct timeval
{
long tv_sec; /* 秒 */
long tv_unsec; /* 微妙 */
}
(2)下面是使用select()函数的服务器端源代码:
/*net_select.c*/
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 4321 //端口号
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5
#define MAX_SOCK_FD FD_SETSIZE
int main()
{
struct sockaddr_in server_sockaddr,client_sockaddr; /* Structure describing an Internet (IP) socket address. */
int sin_size,count;
fd_set inset,tmp_inset;
int sockfd,client_fd,fd;//sockfd=文件描述符
char buf[BUFFER_SIZE];
/*建立socket连接*/
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)//AF_INET=IPv4,SOCK_STREAM=TCP
{
perror("socket");
exit(1);
}
//printf("Socket id = %d\n",sockfd);
/*设置sockaddr_in结构体中相关参数*/
server_sockaddr.sin_family = AF_INET; //sin_family=地址族,AF_INET=IPv4
server_sockaddr.sin_port = htons(PORT);//sin_port=端口号,htons()主机字节序>转化
server_sockaddr.sin_addr.s_addr = INADDR_ANY;//s_addr=IP地址
bzero(&(server_sockaddr.sin_zero),8);//填充0以保持与struct sockaddr同样大小
int i=1;//允许重复使用本地地址与套接字进行绑定
if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))) < 0)//用于任意
类型、任意状态套接口的设置选项值
{
perror("setsockopt");
exit(1);
}
/*sockfd = 标识一个套接口的描述字
level = 选项定义的层次:支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
optname = 需设置的选项
optval = 指针,指向存放选项待设置的新值的缓冲区
optlen = optval缓冲区长度*/
/*绑定函数bind()*/
if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)//server_sockaddr = 本地地址
{ //sizeof(struct sockaddr) = 地址长度
perror("bind");
exit(1);
}
printf("Bind success!\n");
/*调用listen()函数,创建未处理请求的队列*/
if(listen(sockfd,MAX_QUE_CONN_NM) == -1)//MAX_QUE_CONN_NM=请求队列中允许的>最大请求数,大多数系统默认值为5
{
perror("listen");
exit(1);
}
printf("Listening...\n");
/*将调用socket()函数的描述符作为文件描述符*/
FD_ZERO(&inset);//将inset清零使集合中不含任何fd
FD_SET(sockfd,&inset);//将fd加入inset集合
while(1)
{
tmp_inset = inset;
sin_size = sizeof(struct sockaddr_in);
memset(buf,0,sizeof(buf));
/*调用select()函数*/
if(!(select(MAX_SOCK_FD,&tmp_inset,NULL,NULL,NULL)) > 0)
{
perror("select");
}
for(fd=0;fd 0)
{
if(fd == sockfd)
{
/*服务端接收客户端的连接请求*/
/*调用accept()函数,等待客户端的连接*/
if((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size)) == -1)//client_sockaddr = 客户端地址
{ //地址长度
perror("accept");
exit(1);
}
FD_SET(client_fd,&inset);
printf("New connection from %d(socket)\n",client_fd);
}
else
{
/*调用recv()函数接收客户端的请求*/
if((count = recv(fd,buf,BUFFER_SIZE,0)) > 0)//返回接收的字节数
{
printf("Received a message from %d:%s\n",fd,buf);
}
else
{
close(fd);
FD_CLR(fd,&inset);
printf("Client %d(socket) has left\n",fd);
}
}
}
}
}
close(sockfd);
exit(0);
}