在实际情况中,人们往往遇到多个客户端连接服务端的情况。由于connect()、recv()和send()等都是阻塞性函数,如果资源没有准备好,则调用该函数的进程将进入睡眠状态,这样就无法处理I/O多路复用的情况。以下给出两个解决办法:
1.fcntl()
(1)fcntl()回顾:
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
",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!
");
/*调用listen()函数,创建未处理请求的队列*/
if(listen(sockfd,MAX_QUE_CONN_NM) == -1)//MAX_QUE_CONN_NM=请求队列中允许的>最大请求数,大多数系统默认值为5
{
perror("listen");
exit(1);
}
printf("Listening...
");
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
",buf);
}
close(sockfd);
exit(0);
}
2.select()
(1)select()回顾
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:出错
对文件描述符进行处理的宏函数:
select()文件描述符处理函数
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
",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!
");
/*调用listen()函数,创建未处理请求的队列*/
if(listen(sockfd,MAX_QUE_CONN_NM) == -1)//MAX_QUE_CONN_NM=请求队列中允许的>最大请求数,大多数系统默认值为5
{
perror("listen");
exit(1);
}
printf("Listening...
");
/*将调用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)
",client_fd);
}
else
{
/*调用recv()函数接收客户端的请求*/
if((count = recv(fd,buf,BUFFER_SIZE,0)) > 0)//返回接收的字节数
{
printf("Received a message from %d:%s
",fd,buf);
}
else
{
close(fd);
FD_CLR(fd,&inset);
printf("Client %d(socket) has left
",fd);
}
}
}
}
}
close(sockfd);
exit(0);
}