D6并发服务器编程
6.1并发服务器(多线程)实现上
LWIP内部可以创建的socket的数量有限,必出通过一个宏开关进行配置,当一个新的客户端连接到并发服务器的时候,需要创建一个任务,一旦创建一个任务,每一个任务都要分配一个栈空间,越来越多的任务导致内存不够用
6.2并发服务器(多线程)实现下
6.3多路IO复用服务器模型
IO复用模型
selectAPI
标准select
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
//nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
//readfds: 监控有读数据到达文件描述符集合,传入传出参数
//writefds: 监控写数据到达文件描述符集合,传入传出参数
//exceptfds:监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
//timeout: 定时阻塞监控时间,3种情况
//1.NULL,永远等下去
//2.设置timeval,等待固定时间
//3.设置timeval里时间均为0,检查描述字后立即返回,轮询
//return: 成功:所监听的所有监听集合中,满足条件的总数!
//失败:0 超时
//错误:-1
struct timeval {
long tv_sec; /* seconds */ 秒
long tv_usec; /* microseconds */ 微秒
};
void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
/*
注意:
1. select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
2. 解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力
以前一个返回值,可以返回一个0或1,多个返回值返回的是参数,也就是用集合
LWIP select
/* FD_SET used for lwip_select */
#ifndef FD_SET
#undef FD_SETSIZE
/* Make FD_SETSIZE match NUM_SOCKETS in socket.c */
#define FD_SETSIZE MEMP_NUM_NETCONN // 默认4个栈
#define FDSETSAFESET(n, code) do { \
if (((n) - LWIP_SOCKET_OFFSET < MEMP_NUM_NETCONN) && (((int)(n) - LWIP_SOCKET_OFFSET) >= 0)) { \
code; }} while(0)
#define FDSETSAFEGET(n, code) (((n) - LWIP_SOCKET_OFFSET < MEMP_NUM_NETCONN) && (((int)(n) - LWIP_SOCKET_OFFSET) >= 0) ?\
(code) : 0)
#define FD_SET(n, p) FDSETSAFESET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] |= (1 << (((n)-LWIP_SOCKET_OFFSET) & 7)))
#define FD_CLR(n, p) FDSETSAFESET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] &= ~(1 << (((n)-LWIP_SOCKET_OFFSET) & 7)))
#define FD_ISSET(n,p) FDSETSAFEGET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] & (1 << (((n)-LWIP_SOCKET_OFFSET) & 7)))
#define FD_ZERO(p) memset((void*)(p), 0, sizeof(*(p)))
//fd_set
typedef struct fd_set
{
unsigned char fd_bits [(FD_SETSIZE+7)/8];
} fd_set;
//MEMP_NUM_NETCONN
#if !defined MEMP_NUM_NETCONN || defined __DOXYGEN__
#define MEMP_NUM_NETCONN 4
#endif
//LWIP_SOCKET_OFFSET
#if !defined LWIP_SOCKET_OFFSET || defined __DOXYGEN__
#define LWIP_SOCKET_OFFSET 0
#endi
select编程模型
位图:内存中的 一个字节的每一个 位 ,表示对应的一个状态,举个例子,有16个Led(标号 1~16) ,需要控制这些Led 亮灭的状态,如果用2个字节表示这些LED的状态,可以用第一个字节的bit0 表示 Led1的状态,用 bit1 表示 Led2 的状态,以此类推,用第二个字节的 bit 0 表示 Led9的状态,bit 1 表示 Led10 的状态。。。这里面,定义的 unsigned char 类型的 fd_bits[],数组中,每一个字符的每一个 bit 位,代表对应的 fd 有没有数据。
6.4并发服务器(select)实现上
在处理一个事件循环或IO多路复用(如使用select、poll、epoll等机制),"nready" 可能表示在当前检查周期内有多少个文件描述符(sockets、pipes等)已经准备好进行读、写或异常处理操作。