该函数打开一个文件(linux下任何皆文件),返回文件描述符,失败返回-1
int open(const char* pathname,int flags,mode_t mode)
flags:文件打开方式的标志 O_RDONLY:只读方式打开 O_WRONLY:只写方式打开 O_RDWR: 可读可写方式打开 O_CREAT:若打开的文件不存在则创建文件 O_APPEND:追加方式打开 O_TRUNC:若文件存在且可写方式打开则将文件长度清0 mode:打开文件的存取权限,只有在创建文件时才有效。也就是创建文件时文件的权限
关闭文件描述符打开的文件
#include
int close(int fd)
从文件读取指定数量的字节数据存入传入的buf区域
#include
ssize_t read(int fd, void *buf, size_t count)
fd:文件描述符 buf:指定用来存储所读数据的缓冲区 count:指定要读取的字节数 返回实际读取的字节数,返回0代表到达文件结尾,返回-1调用出错
#include
ssize_t write(int fd,void *buf, size_t count)
同write函数
off_t lseek(int fd, off_t offset, int whence)
offset:偏移量,可正可负,指相对于当前的偏移量 whence:表示当前基点位置 SEEK_SET:基点为当前文件的开头 SEEK_CUR:基点为当前文件指针的位置 SEEK_END:基点为当前文件的文件末
函数用于设置I/O的特性
#include
int ioctl(int fd,int cmd,...)
struct sockaddr{
u_char sa_len;//sockaddr的长度
u_short sa_family;//地址族
char sa_data[14];//14字节协议端点信息
};
#include
struct sockaddr_in{
short int sin_family;//地址族
unsigned short int sin_port;//端口号
struct in_addr sin_addr;//存储ip地址的结构
unsigned char sin_zero[8];//填充0以保持sockaddr相同大小
};
ipv4地址结构
struct in_addr{
unsigned long s_addr;//ip地址
};
将主机字节顺序转换为网络字节顺序(host-to-network-for type short)
uint16_t htons(uint16_t hostshort)
将主机字节顺序转换为网络字节顺序(host-to-network-for type long)
uint32_t htonl(uint32_t hostlong)
将网络字节顺序转换为主机字节顺序(network-to-host-for type short)
uint16_t ntohs(uint16_t netshort)
将网络字节顺序转换为主机字节顺序(network-to-host-for type long)
uint32_t ntohl(uint32_t netlong)
#include
in_addr_t inet_addr(const char *cp)
将点分十进制的ip地址转换为32位二进制表示的网络字节顺序的地址。出错返回-1
#include
int inet_aton(const char*cp, struct in_addr *inp)
将一个用点分十进制的in_addr结构转换为二进制后存储在inp中 inp:一个用二进制表示的32位的ip地址结构
#include
char * inet_ntoa(struct in_addr *inp)
将二进制表示的ip地址转换为点分十进制的地址
通过域名查询ip地址
#include
struct hostent * gethostbyname(const char *name)
struct hostenv{
char *h_name;//主机名
char **h_aliases;//主机别名
char h_addrtype;//主机ip地址类型ipv4(AF_INET),ipv6(AF_INET6)
char h_length;//ip地址长度
char ** h_addr_list;//以网络字节顺序存储主机ip地址列表(一个主机可能有多个ip地址)
};
通过服务名查找端口号
#include
struct servent * getservbyname(const char *name,const char * proto)
struct servent{
char *s_name;//主机名
char * *s_aliases;//主机别名
short s_port;//端口
char * s_proto;//协议名
};
根据协议名查找协议号
#include
struct protoenv * getprotobyname(const char *name)
struct protoenv{
char *p_name;//协议名
char * * s_aliases;//主机别名
int p_proto;//协议号
};
创建套接字,返回套接字描述符。
#include
#include
int socket(int domain, int type, int protocol)
domain:协议族。通常赋值为PF_INET,表示TCP/IP协议族 AF_INET:ipv4协议(和PF_ANET同值) AF_INET6:ipv6协议 AF_LOCAL:Unix域协议 AF_ROUTR:路由套接字 AF_KEY:密钥套接字 type:SOCK_STREAM (TCP)、SOCK_DGRAM (UDP)、SOCK_RAW (原始套接字) protocol:协议号。通常赋值为0。在前两种无法区分协议时通过这个参数来区分
用于配置socket并与远端服务器建立一个TCP连接
#include
#include
int connect(int sockfd, struct sockaddr * serv_addr, int addrlen)
serv_addr:服务端地址 addrlen:sockaddr结构的长度 sizeof运算符计算
用于将socket和本地端点地址相关联,调用成功返回一个整型数值,失败返回-1
#include
#include
int bind(int sockfd, struct sockaddr *my_addr,int addrlen)
my_addr:指向本地端点地址的结构。 addrlen:sizeof(struct sockaddr) 计算得到
用于将socket处于被动的监听状态,并为该socket建立一个输入数据队列。
#include
#include
int listen(int sockfd, int backlog)
backlog:指定允许在等待队列中所允许的连接数。
从等待队列中抽取第一个连接,并为该连接创建一个新的套接字。成功返回一个新的套接字描述符,失败返回-1。若为阻塞模式,accept函数将等到等待队列中有连接时才返回。
#include
#include
int accept(int sockfd, struct sockaddr * addr, socklen_t *addrlen)
addr:用于存储接受到连接的套接字地址 addrlen:远端地址的长度。(注意时传入的指针)
用于给TCP连接的另一端发送数据。成功返回实际发送的字节数,失败返回-1
#include
#include
ssize_t send(int sockfd,const void * buf, size_t len, int flags)
buf:要发送的数据的buf len:发送的数据长度 flags:调用方式,一般传0
从TCP连接的另一端获取传过来的数据。成功返回读取的实际字节数,失败返回-1。
#include
#include
int recv(int sockfd, void *buf, int len, unsigned int flags)
同send
用于UDP发送数据。成功返回实际发送的字节数,失败返回-1
#include
#include
int sendto(int sockfd,const void * msg,int len,unsigned int flags,const struct sockaddr * to,int to_len)
msg:发送的数据的缓冲区 len:缓冲区长度 flags:一般设置为0 to:对端端点地址 to_len:对端端点地址的长度
用于UDP接收数据。成功返回实际接收的字节数,失败返回-1
#include
#include
int recvfrom(int sockfd, void *buf,int len,unsigned int flags,struct sockaddr * from,int fromlen)
同sendto
关闭套接字。成功返回0,失败返回-1
#include
#include
int close(int sockfd)
用于套接字某个方向上关闭数据传输,而另一个方向上的传输任然可以继续进行
#include
#include
int shutdown(int sockfd,int howto)
howto: 0:仅关闭读。套接字不再接收任何数据,且丢弃当前缓冲区的所有数据 1:仅关闭写。套接字将缓冲区的数据发送完后,进程将不能够再对该套接字调用写函数 2:同时关闭读写。和close函数类似。与close不同的是当多个进程共享套接字的时候如果调用shutdown函数,那么所有的进程都会受到影响。close函数则只会影响调用的那个进程
获取连接的远端对等套接字的名称。成功返回0,失败返回-1
#include
#include
int getpeername(int sockfd, struct sockaddr *addr, int * addrlen)
addr:用于存储远端对等套接字的端点地址 addrlen:sizeof(struct sockaddr)获得
设置套接字的相关参数,成功返回0,失败返回-1
#include
#include
int setsockopt(int sockfd,int level, int optname, const void *optval,socklen_t optlen)
level:指定选项所在协议层。为了设置套接字层选项,设置为SOL_SOCKET。 optname:选项名。如下表 optval:选项的值 optlen:选项值的长度
选项名称 | 说明 | 数据类型 |
---|---|---|
SO_BROADCAST | 允许发送广播数据 | int |
SO_DEBUG | 允许调试 | int |
SO_DONTROUTE | 不查找路由 | int |
SO_ERROR | 获得套接字错误 | int |
SO_KEEPALIVE | 保持连接 | int |
SO_LINGER | 延迟关闭连接 | struct linger |
SO_OOBINLINE | 带外数据放入正常数据流 | int |
SO_RCVBUF | 接收缓冲区大小 | int |
SO_SNDBUF | 发送缓冲区大小 | int |
SO_RCVLOWAT | 接收缓冲区下限 | int |
SO_SNDLOWAT | 发送缓冲区下限 | int |
SO_RCVTIMEO | 接收超时 | struct timeval |
SO_SNDTIMEO | 发送超时 | struct timeval |
SO_REUSERADDR | 允许重用本地地址和端口 | int |
SO_TYPE | 获得套接字类型 | int |
SO_BSDCOMPAT | 与BSD系统兼容 | int |
注意:设置套接字缓冲区大小时要注意函数调用顺序,因为设置的参数生效时是在套接字建立连接的时候,建立连接就需要协商两边的窗口等信息。所以客户端要在connect函数之前调用,服务端要在listen函数之前调用
获取套接字的参数信息,成功返回0,出错返回-1
#include
#include
int getsockopt(int sockfd,int level, int optname, const void *optval,socklen_t optlen)
同setsockopt函数
对一段内存进行赋值或者清空
#include
void * memset(void *desmem,int val,size_t n)
desmem:操作的内存地址 val:要被赋予的值 n:要赋值的长度,单位字节
将字符串转化为整型数值,成功返回转换后的值,失败返回0
#include
int atoi(const char *str)
获取当前时间戳。成功返回GTM自1970年1月1日 00:00:00以来的秒数,失败返回0
#include
time_t time(time_t *time)
time:用来存储获取到的结果的指针。也可用返回值来接收结果
用于将日历时间转换为字符串形式的本地时间
#include
char *ctime(const time_t * timer)
timer:存储当前日历时间的指针,一般由time()函数获得。
返回错误编号对应的错误原因的字符串描述结果
#include
char * strerror(int errnum)
errnum:错误编号
#include
void va_start(va_list ap,argN)
void va_copy(va_list *dest,va_list src)
type va_arg(va_list ap,type)
void va_end(va_list ap)
使用范例
#include
#include
int add(int a1,...);
int main()
{
int a1 = 1,a2 = 2,a3 = 3,a4 = 4,end = -1;
printf("sum is %d\n",add(a1,a2,a3,a4,end));
}
int add(int a1, ...)
{
va_list args;
int sum = a1;
va_start(args,a1);
int num = 0;
for (;;)
{
num = va_arg(args, int);
if(num != -1){
sum += num;
}else{
break;
}
}
va_end(args);
return sum;
}
linux下用于创建进程。失败返回-1,成功在父进程中返回子进程的实际pid,在子进程中返回0。
#include
int fork();
示例
#include
int main()
{
int a = 0;
pid_t p;
p = fork();
if(p == -1){
fprintf(stderr,"创建进程失败\n");
}else{
if(p == 0){
a += 2;
fprintf(stdout,"子进程:%d",a);
}else{
a += 4;
fprintf(stdout,"父进程:%d",a);
}
}
return 0;
}
获取当前进程的id号
获取当前进程的父进程id号
#include
pid_t getpid(void);
pid_t getppid(void);
进程一旦调用wait()函数就会立即阻塞自己。由wait()函数自动分析当前进程的某个子进程已经退出,如果找到这样一个已经变成僵尸的子进程,waith()函数就会收集该子进程的信息并进行回收,如果没有找到这样的子进程,那么就会一直阻塞,直到出现为止。调用成功返回被收集的子进程的进程ID,失败返回-1
#include
#include
pid_t wait(int * status)
status:用来保存子进程退出时的状态。可以通过调用一下宏来判断子进程结束状况: WIFEXITED(status):子进程正常结束该宏返回非0值 WEXITSTATUS(status):若子进程正常结束,用这个宏可以获得子进程由exit()返回的结束代码 WIFSIGNALED(status):子进程因为信号而结束则该宏返回非0值 WTERMSIG(status):若子进程因为信号结束则该宏可以获得子进程中止信号代码 WIFSTOPPEN(status):子进程处于暂停执行状态则返回非0值 WSTOPSIG(status):若子进程处于暂停执行状态怎该宏获得引发暂停状态的信号代码
waitpid()会像wait()一样阻塞父进程直到子进程退出。waitpid()正常的时候返回子进程pid,如果设置了WNHAND选项,如果调用waitpid时没有子进程可收集,那么返回0。出错返回-1
#include
#include
pid_t waitpid(pid_t pid,int *status, int options)
pid:需要等待的那个子进程号 pid>0:只有等待到的子进程号等于pid时,waitpid()才停止阻塞父进程。 pid=-1:等待任何一个子进程退出就停止阻塞。此时等价wait() pid=0:等待同一组中的任何子进程。 pid<-1:等待指定组中的任何一个子进程,组id为与pid的绝对值。 status:被收集的子进程退出状态 options:主要含有以下参数,多个参数可以相与。 WNOHANG:子进程没有退出,waitpid也立即返回 WUNTRACED:当子进程处于暂停状态waitpid立即返回 0:相当于不设特殊参数
用于绑定收到指定信号的处理函数
#include
void (* signal(int signum,void (*handler)(int)))(int)
signum:信号编号 handler: void (*)(int)类型的函数名,当收到signum信号时执行handler函数 SIG_IGN:忽略信号 SIG_DFL:恢复成系统信号的默认处理
gcc编译时需要连接上线程库 gcc -lpthread xxx.c -o xxx
创建一个新线程,成功返回0,失败返回非0。
#include
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(* start_routine)(void *), void *arg)
thread:创建的线程标识符 attr:线程运行属性的结构体 start_routine:参数类型时void *返回值也是void *的函数指针。这个是线程执行的函数体。 arg:传递给线程体函数的参数 typedef struct{ int detachstate; //分离状态 int schedpolicy; //线程调度策略 struct sched_param schedparam; //线程调度参数 int inheritsched; //线程继承性 int scope; //线程作用域 size_t guardsize;//线程堆栈保护区大小 int stackaddr_set;//线程堆栈地址集 void * stackaddr;//线程堆栈地址 size_t stacksize;//线程堆栈大小 }pthread_attr_t;
LINUX下可以通过下面这些函数设置线程的运行状态。
#include
int pthread_attr_init(pthread_attr_t * attr)//初始化结构体
int pthread_attr_destroy(pthread_attr_t * attr)//去初始化结构体
//设置/获取线程分离状态
int pthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate)
int pthread_attr_setdetachstate(const pthread_attr_t *attr,int detachstate)
//成功返回0,失败返回-1
/***
detachstate:
PTHREAD_CREATE_DETACHED:以分离状态运行
PTHREAD_CREATE_JOINABLE:非分离状态运行(默认)
*/
其他的set,get函数类似
终止一个进程
#include
int pthread_exit(void * value_ptr)
int pthread_join(pthread_t thread, void ** value_ptr)
exit中的value_ptr:线程返回值指针,该返回值将被传递给另一个线程,可以通过调用pthread_join()函数获取 thread:等待结束的进程标识符 join中value_ptr:如果不为NULL,那么线程thread的返回值将存储在指针指向的位置。
获取线程标识符
#include
pthread_t phread_self()
将线程设置成分离线程。成功返回0,失败返回错误号
int pthread_detach(pthread_t thread)
互斥锁就是排他锁,一个时间只有一个线程能拥有该锁
互斥锁有三种:快速互斥锁,递归互斥锁,检错互斥锁。
快速互斥锁:调用的线程会阻塞直到解锁为止。
递归互斥锁:能成功的返回并增加调用线程在互斥锁上的加锁次数
检错互斥锁:调用的线程不会被阻塞,它会立刻返回一个错误信息
操作步骤
相关函数原型
#include
/**
初始化互斥锁变量
*/
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr)
/**
初始化互斥锁属性变量
*/
int pthread_mutexattr_init(pthread_mutexattr_t *mattr)
/**
释放互斥锁属性变量
*/
int pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)
/**
成功返回0,失败返回错误编号
*/
int pthread_mutex_lock(pthread_mutex_t *mutex)
/**
在互斥锁被其他线程锁住时立刻返回,不会阻塞当前进程。其他情况作用和pthread_mutex_lock()函数一样
*/
int pthread_mutex_trylock(pthread_mutex_t * mutex)
/***
解锁互斥锁
*/
int pthread_mutex_unlock(pthread_mutex_t * mutex)
/***
释放互斥锁变量。成功返回0,失败返回错误编号
*/
int pthread_mutex_destroy(pthread_mutex_t * mutex)
/**
互斥锁的共享属性
*/
int pthread_mutexattr_setshared(const pthread_attr_t *mattr, int pshared)
int pthread_mutexattr_getshared(const pthread_attr_t *mattr, int * pshared)
//pshared : PTHREAD_PROCESS_PRIVATE 和 PTHREAD_PROCESS_SHARED
/**
互斥锁类型
*/
int pthread_mutexattr_settype(pthread_mutexattr_t *mattr, int type)
int pthread_mutexattr_gettype(pthread_mutexattr_t *mattr, int *type)
//type: PTHREAD_MUTEX_NOMAL 快速互斥锁 PTHREAD_MUTEX_RECURSIVE 递归互斥锁 PTHREAD_MUTEX_ERRORCHECK 检错互斥锁
有名信号量,保存在文件中可以多线程同步和多进程同步
无名信号量,保存在内存,用于同一进程的不同线程同步。
初始化信号量。成功返回0,失败返回-1
#include
int sem_init(sem_t * sem,int pshared, unsigned int value)
sem:实质是一个int类型指针 pshared:决定能否在几个进程间共享。为0代表只能本进程的线程共享。不为0代表在多个进程间共享 value:初始化信号量的值
阻塞当前线程直到信号量sem大于0。成功返回0,sem减1,出错返回-1
#include
int sem_wait(sem_t *sem)
sem_wait()的非阻塞版本,成功返回0,sem减1,失败立即返回-1;
#include
int sem_trywait(sem_t * sem)
回退资源。成功返回0,将sem加1;出错返回-1;
#include
int sem_post(sem_t *sem)
用于获取信号量数量。成功返回0,出错返回-1.
#include
int sem_getvalue(sem_t *sem,int *sval)
归还信号量所占的资源。成功返回0,出错返回-1
#include
int sem_destroy(sem_t *sem)
有名信号量和无名信号量共用sem_wait(),sem_trywait(),sem_post()函数
有名信号量用sem_open()函数初始化,结束时需要调用sem_close()和sem_unlink()函数(有名信号量使用的是文件存储,所以需要关闭还要删除)
创建或打开已存在的有名信号量。成功返回信号量指针,出错返回SEM_FAILED
#include
sem_t * sem_open(const char* name,int oflag, mode_t mode,unsigned int value)
name:信号量的外部名称。信号量创建的文件都在/dev/shm目录下,指定名字时不能包含路径。 oflag:O_CREATE信号量不存在时则创建,且此时mode和value必须有效;若存在时则打开信号量,自动忽略mode和value。O_CREATE | EXCL若信号量不存在则和O_CREATE时一样,若存在将返回一个错误 mode:同文件权限 value:信号量初始值
sem_close函数用于关闭信号量,并释放资源。sem_unlink函数用于信号量关闭后删除所值的信号量。成功返回0,出错返回-1
#include
int sem_close(sem_t *sem)
int sem_unlink(const char *name)
条件变量是一种同步机制,允许线程挂起,直到共享数据上某些条件得到满足。条件变量是利用线程间的全局变量进行同步的一种机制。一个线程等待条件变量的条件成立,另一个线程使条件成立并发出条件成立信号。
条件变量一般要和互斥锁一起使用,基本操作步骤:
相关函数原型:
#include
/***
attr:赋值为NULL,还没有定义相关结构体
*/
int pthread_cond_init(pthread_cond_t * cond,const pthread_condattr_t *attr)
/****
发出条件变量,表示满足条件成立。唤醒一个线程
*/
int pthread_cond_signal(pthread_cond_t *cond)
/***
唤醒所有等待cond信号条件的线程
*/
int pthread_cond_broadcast(pthread_cond_t *cond)
/***
等待条件满足,成功返回0,出错返回错误编号
*/
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
/***
计时等待,在给定时间内等待,如果还是不满足则返回ETIMEOUT,否则成功返回0,失败返回错误编号。
abstime:和time()函数返回值代表的意义相同。GTM时间
*/
int pthread_cond_timedwait(pthread_cond_t * cond,pthread_mutex_t * mutex, const struct timespec * abstime)
/***
销毁指定条件变量,成功返回0,失败返回错误编号
*/
int pthread_cond_destroy(pthread_cond_t *cond)
示例程序:
#include
#include
#include
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void * thread1(void *);
void *thread2(void *);
int i = 1;
int main()
{
pthread_t t_a;
pthread_t t_b;
pthread_create(&t_a,NULL,thread1,(void *)NULL);
pthread_create(&t_b,NULL,thread2,(void *)NULL);
pthread_join(t_a,NULL);
pthread_join(t_b,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
void * thread1(void *arg)
{
for(i = 1;i <= 9 ;i++){
pthread_mutex_lock(&mutex);
if(i % 3 == 0){
pthread_cond_signal(&cond);
}else{
printf("thread 1:%d\n",i);
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
void * thread2(void *arg)
{
while(i < 9){
pthread_mutex_lock(&mutex);
if(i % 3 != 0){
pthread_cond_wait(&cond,&mutex);
}
printf("thread2:%d\n",i);
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
结果: thread1:1 thread1:2 thread2:3 thread1:4 thread1:5 thread2:6 thread2:6 thread1:7 thread1:8 thread2:9
提供异步io。让单线程/进程等待指定集合中任意一个文件描述符就绪。当没有设备准备就绪时,select()函数阻塞,若集合中任意一个设备准备就绪,select()函数返回。正常情况下select()返回就绪的文件描述符个数,如果timeout时间后还没有就绪的,那么返回0。如果select()被某个信号中断,返回-1,。调用出错返回-1。
#include
int select(int maxfdp, fd_set *readfds, fd_set* writefds, fd_set* errorfds, struct timeval * timeout)
maxfdp:指集合中所有文件描述符的范围。即所有文件描述符的最大值加1,该参数通常使用getdtablesize()函数获得 readfds:fd_set结构指针。存放可读文件描述符集合。如果集合中的文件有一个可读,那么select()就会返回大于0的值,如果没有就会根据timeout的值判断超时,超时返回0,出错返回-1 writefds:存放可写文件描述符集合。类似readfds。 errorfds:用来监控文件错误异常 timeout:timeval结构体指针,取值如下 NULL:select()处于阻塞模式,且永不超时 0秒0毫秒:select()函数处于非阻塞模式 大于0:设定定时器 fd_set结构体的操作宏 FD_ZERO(&fdset);//将fdset清零,清空fdset和文件句柄之间的关系 FD_SET(fd,&fdset);//将fd加入fdset FD_CLR(fd,&fdset);//将fd从fdset中删除 FD_ISSET(fd,&fdset);//判断fd是否在fdset中就绪 struct timeval{ long tv_sec;//秒 long tv_usec;//毫秒 };
示例程序片段:
...
#include
int main()
{
...
fd_set rfds;//可读集合
fd_set afds;//保存所有的文件描述符集合
msock = socket(...);
bind...
listen...
int nfds = getdtablesize();//获取最大描述符个数
FD_ZERO(&rfds);
FD_SET(msock,&afds);//将msock加入可读集合
...
while(1){//死循环
memcpy(&rfds,&afds,sizeof(rfds));
if(select(nfds,&rfds,(fd_set*)0,(fd_set*)0,(struct timeval *)0) < 0)
{
errexit("select 出错:%s\n",strerror(errno));
}
if(FD_ISSET(msock,rfds))
{
...
ssock = accept(...)
if(ssock < 0){
errexit("accept 出错:%s\n",strerror(errno));
}
FD_SET(ssock,rfds);
}
for(fd = 0;fd < nfds,++fd){
if(fd != msock && FD_ISSET(fd,rfds))
{
...
recv(...)
...
send(...)
close(fd);
FD_CLR(fd,&afds);
}
}
}
}
预先创建线程,在线程不足的情况下再创建新的。适用于线程任务时间短需要的线程多的情况,避免大量的时间浪费在创建线程上。
风险:同步错误,死锁,池的死锁,资源不足,线程泄漏等。
相对于select模型,epoll模型具有更大的文件描述符数量。select由FD_SETSIZE设置,其默认大小为2048,要修改只能修改后重新编译内核,而epoll则支持的数量远远大于该值,其最大值跟内存大小有关系,可以通过 cat /proc/sys/fs/file -max 查看。其次是选择就绪的文件的方式,select采用的是遍历的方式,epoll采用的是基于事件的选择。选择效率比select的遍历方式高。
创建epoll句柄。返回一个文件描述符,记得用完之后使用close()函数关闭,否则可能导致文件描述符耗尽。
#inlcude
int epoll_create(int size);
size:用来告诉内核监听的数目一共有多大。
为epoll的事件注册函数
#include
int epoll_ctl(int epfd,int op, int fd, struct epoll_event * event)
epfd:epoll_create()函数返回的句柄。 op: EPOLL_CTL_ADD:注册新的fd到epfd中 EPOLL_CTL_MOD:修改已经注册的fd的监听事件 EPOLL_CTL_DEL:从epfd中删除fd event:告诉内核需要监听什么事件。 struct epoll_event{ __uint32_t events;//事件 epoll_data_t data;//用户变量数据 }; events可以是一下宏的集合: EPOLLIN:对应文件描述符可以读(包括对端socket正常关闭) EPOLLOUT:对应文件描述符可以写 EPOLLPRI:对应文件描述符有紧急数据可读 EPOLLERR:对应文件描述符发生错误 EPOLLHUP:对应文件描述符被挂起 EPOLLET:将epoll设为边缘触发模式 EPOLLONESHOT:只监听一次事件,监听完后如果还想再次监听,则需要再次加入epoll队列
等待事件产生。返回需要处理事件的数目,返回0表示已经超时。
#include
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
events:用来从内核得到事件集合 maxevents:告诉内核events有多大。不能大于epoll_create()函数传入的size的大小。 timeout:超时时间(毫秒)。0 立即返回,-1 永久阻塞
水平触发(LT),边缘触发(ET)
ET模式事件效率高,但是编程复杂,需要程序员仔细处理事件,否则容易漏事件。
LT模式效率比ET模式低,但编程容易。
示例代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#include
//#include
#include
#include
#include
#include
#define MAXBUF 1024
#define MAXEPOLLSIZE 10000
int setnonblocking(int sockfd)
{
if(fcntl(sockfd,F_SETFL,fcntl(sockfd,F_GETFD,0)|O_NONBLOCK) == -1){
return -1;
}
return 0;
}
int handle_message(int new_fd){
char buf[MAXBUF+1];
int len;
bzero(bug, MAXBUF+1);
len = recv(new_fd, buf,MAXBUF,0);
if(len > 0){
printf("%d接收消息成功:'%s',共%d字节的数据",new_fd,buf,len);
}else{
if(len < 0){
printf("接受消息失败!错误代码:%d,错误信息:%s\n",errno,strerror(errno));
}
close(new_fd);
return -1;
}
return len;
}
int main(int argc,char *argv[])
{
int listener,new_fd,kdpfd,nfds,n,ret,curfds;
socklen_t len;
struct sockaddr_in my_addr,their_addr;
unsigned int myport,lisnum;
struct epoll_event ev;
struct epoll_event events[MAXEPOLLSIZE];
struct rlimit rt;
myport = 5000;
lisnum = 2;
rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
if(setrlimit(RLIMIT_NOFILE,&rt) == -1){
perror("setrlimit");
exit(1);
}else{
printf("设置系统参数成功!\n");
}
if((listener = socket(PF_INET,SOCK_STREAM,0))== -1){
perror("socket");
exit(1);
}else{
printf("socket创建成功!");
}
setnonblocking(listener);
bzero(&my_addr,sizeof(my_addr));
my_addr.sin_family = PF_INET;
my_addr.sin_port = htons(myport);
my_addr.sin_addr.s_addr = INADDR_ANY;
if(bind(listener,(struct sockaddr *)&my_addr,sizeof(struct sockaddr)) == -1){
perror("bind");
exit(1);
}else{
printf("ip地址端口绑定成功\n");
}
if(listen(listener,lisnum) == -1){
perror("listen");
exit(1);
}else{
printf("开启服务成功\n");
}
kdpfd = epoll_create(MAXEPOLLSIZE);
len = sizeof(struct sockaddr_in);
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = listener;
if(epoll_ctl(kdpfd,EPOLL_CTL_ADD,listener,&ev)< 0){
fprintf(stderr,"epoll set insertion error:fd = %d\n",listener);
return -1;
}else{
printf("监听socket加入epoll成功\n");
}
curfds = 1;
while(1){
nfds = epoll_wait(kdpfd,events,curfds,-1);
if(nfds == -1){
perror("epoll_wait");
break;
}
for(n = 0;n < nfds;n++){
if(events[n].data.fd == listerner){
new_fd = accept(listener,(struct sockaddr *)&their_addr,&len);
if(new_fd < 0){
perror("accept");
continue;
}else{
printf("有连接来自:%d:%d,分配的socket为:%d\n",inet_ntoa(their_addr.sin_addr),ntohs(their_addr.sin_port),new_fd);
}
setnonblocking(new_fd);
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = new_fd;
if(epoll_ctl(kdpfd,EPOLL_CTL_ADD,new_fd,&ev) < 0){
printf(stderr,"吧socket %d 加入epoll失败! %s",new_fd,strerror(errno));
return -1;
}
curfds++;
}else{
ret = handle_message(events[n].data.fd);
if(ret < 1 && errno != 11){
epoll_ctl(kdpfd,EPOLL_CTL_DEL,events[n].data.fd,&ev);
curfds--;
}
}
}
}
close(listener);
return 0;
}
存在死锁的示例程序:
#include
#include
#include
#include
#include
#define LOOP_TIMES 10000
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
void *thread_worker(void *);
void critical_sesstion(int thread_num,int i);
int main(void)
{
int rtn,i;
pthread_t pthread_id = 0;
rtn = pthread_create(&pthread_id,NULL,thread_worker,NULL);
if(rtn != 0){
printf("pthread_create error!\n");
return -1;
}
for(i = 0;i < LOOP_TIMES;i++){
pthread_mutex_lock(&mutex1);
pthread_mutex_lock(&mutex2);
critical_section(1,i);
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
}
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
return 0;
}
void * thread_worker(void * p)
{
int i = 0;
for(i = 0;i<LOOP_TIMES;i++){
pthread_mutex_lock(&mutex2);
pthread_mutex_lock(&mutex1);
critical_section(2,i);
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
}
}
void critical_section(int thread_num,int i)
{
printf("thread%d:%d\n",thread_num,i);
}