【1】epoll
epoll并发模型可以解决select缺陷,实现高效率,百万级服务器模型,被称为二十一世纪最好用的并发模型。
epoll的优点:
1、最大连接数量没有限制,上限是系统可以最大打开的文件数目;
2、epoll是一种异步通知,无需轮询,效率要比select高
3、成功返回实际事件个数,有效轮询。
API接口:
1.int epoll_create
int epoll_create(int size);
功能:创建红黑树根节点
参数:size已经废弃掉,填1即可
返回值:成功返回红黑树的一个句柄(非负数)
失败返回-1
2.int epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:设置指定文件描述符属性并挂在红黑树上
参数:
epfd:红黑树的句柄
op:操作文件描述符的方式
EPOLL_CTL_ADD:添加指定的文件描述符到红黑树上
EPOLL_CTL_MOD:修改对应文件描述符的属性
EPOLL_CTL_DEL:删除红黑树上对应的文件描述符
fd:指定的文件描述符
event:文件描述符的属性
typedef union epoll_data {
void *ptr;
int fd; //指定的文件描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; //文件描述符事件
epoll_data_t data;
};
EPOLLIN:允许可读事件
EPOLLOUT:允许可写事件
EPOLLET:边沿触发(水平触发方式为默认方式,效率较低)
返回值:成功返回0,失败返回-1
3.int epoll_wait
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能:等待挂在红黑树上的文件描述符发生IO事件
参数:
epfd:红黑树句柄
events:从内核得到具体那些文件描述符发生IO事件,是一个集合
maxevents:最多一次返回的事件个数
timeout:超时等待,毫秒级单位
-1:阻塞 0:立即返回
返回值:成功时返回发生io事件的数量
失败时返回-1
使用epoll完成并发服务器模型:
1、创建红黑树
2、将关心的文件描述符加入红黑树上
3、等待发生IO事件的文件描述符
4、判断哪个文件描述符发生了IO事件并且作出对应逻辑处理
实例
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#if 0
使用epoll完成并发服务器模型:
1、创建红黑树
2、将关心的文件描述符加入红黑树上
3、等待发生IO事件的文件描述符
4、判断哪个文件描述符发生了IO事件并且作出对应逻辑处理
#endif
int main(int argc, char *argv[])
{
//1.创建红黑树
int epfd = epoll_create(1);
if (epfd < 0)
{
perror("epfd err");
return -1;
}
//创建本地套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("sockfd err");
return -1;
}
//2.将关心的文件描述符加入到红黑树上
//EPOLL_CTL_ADD,struc epoll_event
//EPOLLIN :可读事件
//EPOLLET:边沿检测
//初始化对应文件描述符
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.fd = sockfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event) < 0)
{
perror("epoll_ctl err");
return -1;
}
//填充服务器地址
struct sockaddr_in serveraddr, clientaddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(serveraddr);
//绑定
if (bind(sockfd, (struct sockaddr *)&serveraddr, len) < 0)
{
perror("bind err");
return -1;
}
//监听
if (listen(sockfd, 10) < 0)
{
perror("listen err");
return -1;
}
int acceptfd, ret, i;
int bytes;
struct epoll_event events[20];
char buf[32] = {0};
while (1)
{
//3.使用epoll_wait轮询检测文件描述赋发生IO事件
ret = epoll_wait(epfd, events, 20, -1);
if (ret > 0)
{
for (i = 0; i < ret; i++)
{
//判断文件描述符数值
if (events[i].data.fd == sockfd)
{
//判断对应文件描述赋的IO类型
if (events[i].events & EPOLLIN == EPOLLIN)
{
acceptfd = accept(events[i].data.fd, (struct sockaddr *)&clientaddr, &len);
if (acceptfd < 0)
{
perror("accept err");
return -1;
}
printf("IP:%s connect\n", (char *)inet_ntoa(clientaddr.sin_addr));
//将新客户端通信套接字挂到树上
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.fd = acceptfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfd, &event) < 0)
{
perror("epoll_ctl err");
return -1;
}
}
}else{
//处理老客户端信息
if(EPOLLIN &events[i].events == EPOLLIN)
{
memset(buf,0,sizeof(buf));
bytes = recv(events[i].data.fd,buf,sizeof(buf),0);
if(bytes < 0)
{
perror("recv err");
return -1;
}else if(bytes ==0){
//客户端断开信息
printf("acceptfd:%d disconnect\n",events[i].data.fd);
//关闭对应文件描述符
close(events[i].data.fd);
//将挂在树上对应的文件描述符清楚
epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);
}else
{
printf("buf:%s",buf);
}
}
}
}
}else
{
perror("epoll_wait err");
return -1;
}
}
close(sockfd);
return 0;
}
【2】多线程实现服务器并发模型
线程函数:
{
recv(acceptfd)
接收数据,判断客户端退出
}
while(1)
{
accept()成功返回后创建线程用于客户端和服务器通信
}
练习:使用线程知识完成服务器并发模型
#include <stdio.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_callback(void *arg)
{
int acceptfd = *(int *)arg;
int bytes;
char buf[32];
while (1)
{
memset(buf, 0, sizeof(buf));
if (bytes == 0)
{
printf("acceptfd:%d disconnect\n", acceptfd);
close(acceptfd);
return NULL;
}
else if (bytes > 0)
{
printf("buf:%s", buf);
}
else
{
perror("recv err");
return NULL;
}
}
}
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("sockfd err");
return -1;
}
struct sockaddr_in serveraddr, clientaddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(serveraddr);
if (bind(sockfd, (struct sockaddr *)&serveraddr, len) < 0)
{
perror("bind err");
return -1;
}
if (listen(sockfd, 10) < 0)
{
perror("listen err");
return -1;
}
int acceptfd;
pthread_t tid;
//初始化线程分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
while (1)
{
//等待客户端链接
acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
if (acceptfd < 0)
{
perror("acceptfd err");
return -1;
}
//创建线程函数,并且将通信套接字传到线程函数中
if (pthread_create(&tid, &attr, pthread_callback, (void *)&acceptfd) != 0)
{
perror("pthread_create err");
return -1;
}
}
close(sockfd);
return 0;
}
多线程实现服务器并发模型的优点:
(1)资源共享,线程间通信简单
(2)资源切换开销小,效率高
多线程实现服务器并发模型的缺点:
(1)安全性低,临界资源需要保护
(2)程序的健壮性差,单一线程崩溃可能会引起整个进程崩溃
【3】多进程实现服务器并发模型
思路:
TCP服务器编码流程socket bind listen
设置信号处理函数(SIGCHLD)
/***************************************************
# filename : socket.c
# author : zhuangzhen
# e-mail : 1448686495@qq.com
# create time : 2022-05-12 14:14:47
***************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <signal.h>
void child_wait(int signum)
{
if(signum == SIGCHLD)
{
wait(NULL);
}
}
int main(int argc, char *argv[])
{
int sockfd;
//1.创建套接字文件
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket err");
return -1;
}
printf("scokfd:%d\n",sockfd);
//2.bind绑定服务器地址,需要填充服务器地址,根据协议的不同,
struct sockaddr_in serveraddr,clientaddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));//端口号网络字节序8888
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);//IP网络字节序192.168.2.66
socklen_t len = sizeof(serveraddr);
if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)) < 0)
{
perror("bind err");
return -1;
}
//3.监听
if(listen(sockfd,10) < 0)
{
perror("listen err");
return -1;
}
//4.链接客户端,阻塞
int acceptfd;
pid_t pid;
//处理僵尸进程
signal(SIGCHLD,child_wait);
while(1)
{
if((acceptfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len)) < 0)
{
perror("accept err");
return -1;
}
printf("IP:%s connect \n",(char *)inet_ntoa(clientaddr.sin_addr));
pid=fork();
if(pid == 0)
{
close(sockfd);
//child 负责处理老客户断通信
int bytes;
char buf[32];
while(1)
{
memset(buf,0,sizeof(buf));
bytes=recv(acceptfd,buf,sizeof(buf),0);
if(bytes == 0)
{
printf("acceptfd:%d\n",acceptfd);
printf("IP%sdiconnect\n",(char *)inet_ntoa(clientaddr.sin_addr));
close(acceptfd);
exit(0);
}else if(bytes > 0)
{
printf("buf:%s",buf);
}else
{
perror("recv err");
exit(0);
}
}
}else if(pid < 0){
perror("fork err");
return -1;
}
close(acceptfd);
}
close(sockfd);
return 0;
}