多路复用select函数
函数介绍
函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数nfds:所有文件描述符的最大值加1
参数readfds/writefds/exceptfds: 读/写/错误 集合
读集合 | 写集合 | 错误集合 |
readfds | writefds | exceptfds |
参数timeout: 表示select的等待时间
返回值:文件描述符个数
select函数的执行过程
传递给select函数的参数会告诉内核以下信息:
(1)文件描述符(select函数监视的文件描述符分三类,分别是writefds、readfds和exceptfds)。
(2)每个描述符的状态(是想从一个文件描述符中读或者写,还是关注一个描述符中是否出现异常)。
(3)要等待的时间(可以等待无限长的时间,等待固定的一段时间,或者根本就不等待)。
从select函数返回后,内核会告诉我们以下信息:
(1)对我们的要求已经做好准备的描述符的个数。
(2)对于三种条件哪些描述符已经做好准备(读、写、异常)。
(3)有了这些返回信息,我们可以调用合适的I/O函数(通常是read或write),并且这些函数不会再阻塞。
注意:TCP是全双工的通信方式
工作方式如图:
当我们调用send/write函数的时候,实际上就是把数据写进发送缓冲区。同理,调用recv/read函数的时候,实际上就是从接收缓冲区接收数据。这种通信方式,被称作全双工。前面课程实现的代码,都是读到数据以后再去写,或者写完数据以后再去读。这种通信方式,只能称为半双工。我们可以运用select函数多路复用的特性实现全双工的通信方式,代码如下:
为了突出select所以对TCP相关函数进行了封装
net.h封装了两个函数和一个链表:
CheckArgment | 检查参数,运行的时候需要输入IP地址和端口 |
SocketCreate | 生成套接字,包括创建->绑定->监听套接字 |
Sqlist | 用于保存客户端文件描述符 |
/*net.h*/
#ifndef _NET_H_
#define _NET_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/select.h>
#define BACKLOG 5
#define ErrExit(msg) do { \
fprintf(stderr, "%s(%s:%d): %s\n", msg, __FUNCTION__, __LINE__, strerror(errno) ); \
exit(EXIT_FAILURE);} while(0)
#define ErrMsg(msg) do { \
fprintf(stderr, "%s(%s:%d): %s\n", msg, __FUNCTION__, __LINE__, strerror(errno) ); \
} while(0)
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
typedef int (*SocketHandle_t)(int, const Addr *, socklen_t);
typedef struct Sqlist{
int fd;
struct Sqlist *next;
}Sqlist;
void CheckArgment(int argc, char *argv[]);
int SocketCreate(in_port_t port, char *addr, bool LISTEN);
#endif
net.c 实现封装的函数
#include "net.h"
void CheckArgment(int argc, char *argv[])
{
if(argc < 3)
{
fprintf(stderr, "%s [IP][PORT]\n", argv[0]);
exit(0);
}
}
int SocketCreate(in_port_t port, char *addr, bool LISTEN)
{
SocketHandle_t p = LISTEN?bind:connect;
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
ErrExit("socket");
Addr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(addr);
server_addr.sin_port = htons(port);
p(fd, (Addr *)&server_addr, sizeof(Addr_in));
if(LISTEN)
{
int flag = 1;
if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, 4) )
ErrExit("setsockopt");
if( listen(fd, BACKLOG) )
ErrExit("listen");
}
return fd;
}
select实现全双工的TCP客户端
#include "net.h"
int main(int argc, char *argv[])
{
char buf[BUFSIZ] = {};
CheckArgment(argc, argv);
int fd = SocketCreate(atoi(argv[2]), argv[1], false);
printf("服务端套接字准备完毕\n");
int nfds;
fd_set rset;
while(1)
{
FD_ZERO(&rset);
FD_SET(fd, &rset);
FD_SET(0, &rset);
nfds = fd;
if( select(nfds+1, &rset, NULL, NULL, NULL) < 0)
ErrExit("select");
if( FD_ISSET(fd, &rset) )
{
if( !recv(fd, buf, BUFSIZ, 0) )
{
close(fd);
break;
}
else
printf("recv buf:%s\n", buf);
}
if( FD_ISSET(0, &rset) )
{
fgets(buf, BUFSIZ, stdin);
printf("send buf:%s", buf);
send(fd, buf, strlen(buf), 0);
}
}
close(fd);
return 0;
}
select实现服务端的多路复用
select.c使用select函数实现多路复用
#include "net.h"
int main(int argc, char *argv[])
{
char buf[BUFSIZ] = {};
CheckArgment(argc, argv);
int fd = SocketCreate(atoi(argv[2]), argv[1], true);
printf("服务端套接字准备完毕\n");
int nfds;
fd_set rset;
Sqlist *p, *q, *H = NULL;
while(1)
{
FD_ZERO(&rset);
FD_SET(fd, &rset);
nfds = fd;
p = H;
while(p != NULL)
{
FD_SET(p->fd, &rset);
if(p->fd >= nfds)
nfds = p->fd;
p = p->next;
}
if( select(nfds+1, &rset, NULL, NULL, NULL) < 0)
ErrExit("select");
if( FD_ISSET(fd, &rset) )
{
if( (p = calloc(1, sizeof(Sqlist)) ) == NULL)
ErrExit("calloc");
if( (p->fd = accept(fd, NULL, NULL) ) < 0 )
ErrExit("accept");
p->next = H;
H = p;
printf("accept newfd %d \n", p->fd);
}
if(H != NULL && FD_ISSET(H->fd, &rset) )
{
bzero(buf, BUFSIZ);
if( !recv(H->fd, buf, BUFSIZ, 0) )
{
printf("fd%d exit.\n", H->fd);
close(H->fd);
p = H;
H = H->next;
free(p);
}
else
printf("%s\n", buf);
}
p = H;
if(p != NULL)
q = p->next;
else
continue;
while(q != NULL)
{
if( FD_ISSET(q->fd, &rset) )
{
bzero(buf, BUFSIZ);
if( !recv(q->fd, buf, BUFSIZ, 0) )
{
printf("fd%d exit.\n", q->fd);
close(q->fd);
p->next = q->next;
free(q);
}
else
printf("%s\n", buf);
}
p = q;
q = p->next;
}
}
close(fd);
return 0;
}