之前所用到的函数如recv,send,recvfrom,sendto,read,和write等函数都是阻塞性的函数,如果资源没有准备好,那么调用该函数的进程将进入阻塞状态,解决方案:I/O多路复用:1.fcntl函数(非阻塞方式)。2.select函数。
主控线程将每个客户端产生的fd放置到一个动态数组里面去,子线程来遍历整个动态数组中的所有fd,并通过这些fd和对应的客户端进行双向通信(采用非阻塞的read/write)。
采用非阻塞的方式如果读不到或者写不了直接返回。
主函数中修改:
int val;
fcntl(fd, F_GETEK,&val);
val |= O_NONBLOCK;
fcntl(fd,F_SETEL, val);
最后将新的fd加入到fd的动态数组中。
函数calloc()
可以用来动态分配内存空间,可以直接初始化所分配的内存空间。用来存放字符类型或整数类型数据,初始化为0;用来存放指针类型,初始化为空指针;用来存放实型数据,初始化为浮点型的零。calloc()函数两个参数分别为元素的个数和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小。
函数原型 void *calloc(size_t numElements,size_t sizeOfElement); 如果调用成功,函数calloc()将返回所分配的内存空间的首地址。
函数assert(expression)
作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
/********************************************************************************/
vector_fd.h
#ifndef __VECTOR_H___
#define __VECTOR_H__
typedef struct{
int *fd;
int counter;
int max_counter;
}VectorFD;
extern VectorFD* create_vector_fd(void);
extern void destroy_vector_fd(VectorFD*);
extern int get_fd(VectorFD*,int index);
extern void remove_fd(VectorFD*,int fd);
extern void add_fd(VevtorFD*,int fd);
#endif
/********************************************************************************/
vector_fd.c
#include
#include
#include
#include
#include"vector_fd.h"
static void encapacity(VectorFD*vfd)
{
if(vfd->counter >= cfd->max_counter){
int *fds = (int *)calloc(vfd->counter+5,sizeof(int));
assert(fds != NULL);
memcpy(fds,vfd->fd,sizeof(int)*vfd->counter);
free(vfd->fd);
vfd->fd = fds;
vfd->max_counter += 5;
}
}
static int indexof(VectorFD *vfd,int fd)//遍历fd
{
int i = 0;
for(;i < vfd->counter; i++){
if(vfd->fd[i] == fd) return i;
}
return -1;
}
//堆当中通过动态分配的创建一个动态数组
VectorFD* create_vector_fd(void)
{
VectorFD *vfd = (VectorFD*)calloc(1,sizeof(VectorFD));
assert(vfd != NULL);
vfd->fd = (int *)calloc(5,sizeof(int));//对成员fd再创建一个动态数组
assert(vfd->fd != NULL);
vfd->counter = 0;
vfd->max_counter = 0;
return vfd;
}
void destroy_vector_fd(VectorFD*vfd)
{
assert(vfd != NULL);
free(vfd->fd);
free(vfd);
}
int get_fd(VectorFD*vfd,int index)
{
assert(vfd != NULL);
if(index < 0 || index > vfd->counter-1)
return 0;
return vfd->fd[index];
}
void remove_fd(VectorFD*vfd,int fd)//后面的元素覆盖前面的元素
{
assert(vfd != NULL);
int index = indexof(vfd,fd);
if(index == -1)
return ;
int i = index;
for(;i < vfd->counter-1;i++){
vfd->fd[i] = vfd->fd[i+1];
}
vfd->counter--;
}
void add_fd(VevtorFD*vfd,int fd)
{
assert(vfd != NULL);
encapacity(vfd);//动态数组有可能满,动态增加动态数组的容量
vfd->fd[vfd->counter++] = fd;
}
select函数
同样需要延时函数,给内核时间去扫描所有的描述符。
timeout:指定愿意等待的时间。NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止。具体值:struct timeval类型的指针,若等待为timeout时间,还没有文件描述符准备好,就立即返回。0:从不等待,测试所有指定的描述符并立即返回。
传向select的参数告诉内核:我们所关心的描述符;对于每个描述符我们所关心的条件(是否可读一个给定的描述符,是否可写一个给定的描述符,是否关心一个描述符的异常条件);希望等待多长时间。
从select返回时内核告诉我们:已准备好的描述符的数量;哪一个描述符已准备好读写或异常条件;使用这种返回值,就可以调用相应的I/O函数(一般是read或者write),并且确认该函数不会阻塞。
守护进程:
守护进程(daemom)是生存期长的一种进程。他们常常在系统引导装入时启动,在系统关闭时终止。
所有守护进程都以超级用户(用户ID为0)的优先权运行。
守护进程没有控制终端。
守护进程的父进程都是init进程。
编程步骤:
使用umask将文件模式创建屏蔽字设为0;
调用fork,然后让父进程退出(exit);
调用setsid创建一个新会话;
将当前工作目录更改为根目录;
关闭不需要的文件描述符;
守护进程出错处理:
由于守护进程完全脱离了控制终端,因此,不能像其他程序一样通过输出错误信息到控制台的方式来通知程序员。
通常办法是使用syslog服务,将出错信息输入到“/var/log/syslog”系统日志文件中去。
syslog是Linux中的系统日志管理服务通过守护进程syslog来维护。
openlog函数用于打开系统日志服务的一个连接。
syslog函数用于向日志文件中写入消息,在这里可以规定消息的优先级、消息的输出格式等。
closelog函数用于关闭系统日志服务的连接。
//步骤1:创建屏蔽字为0
umask(0);
//步骤2:调用fork函数创建子进程,然后父进程退出。
pid_t pid = fork();
if(pid>0) exit(0);
//步骤3:调用setsid函数创建一个新会话。
setsid();
//步骤4:将当全工作目录更改为根目录。
chdir("/");
//步骤5:关闭不需要的文件描述符。
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
//打开系统日志服务的一个连接
openlog(argv[0],LOG_PID,LOG_SYSLOG);