目录
19.1 什么是并发
19.2 多进程并发服务器
19.3 多线程并发服务器
19.4 多路 I/O 转接服务器
(1)select
(2)epoll
19.5 线程池并发服务器
19.6 UDP局域网络服务器
19.7 其他常用函数
19.1 什么是并发
并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。———来源《百度百科》
顾名思义,高并发就是在指定时间内,系统同时能够处理大量的请求(连接数)。
19.2 多线程并发服务器
由于多进程服务器在创建进程时要消耗较大的系统资源,所以这里不做过多赘述。
流程如下:
服务器接收到客户端的一个请求之后,之后临时fork()一个进程,父进程等待下一个请求,子进程处理客户端请求
转接一份代码:
/*server.c*/
#include<stdio.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<sys/wait.h>
#include<sys/type.h>
#define MAXLINE 80
#define SERV_PORT 8000
void di_sigchild(int num)
{
waitpid(0,NULL,WNOHANG);
}
int main(void)
{
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddr_len;
int listenfd,connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i,n;
pid_t pid;
// 套路开始
struct sigaction newact; //信号量,那篇我准备重写
newact.sa_handler = di_sigchild;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGCHLD,&newact,NULL);
listenfd = socket(AF_INET,SOCK_STREAM,0); //创建一个网络套接字
bzero(&servaddr,sizeof(servaddr)); //清空结构体变量,准备开始刻画
servaddr.sin_family = AF_INET; //配置网络协议
servaddr.sin_port = htonl(SERV_PORT); //配置端口号
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //配置网络地址
bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); //好了,可以绑定了 别忘了历史遗留问题
listen(listenfd,20); //开始监听,允许20个进程进来
//开始接收数据了
printf("Accepting connections··· \n"); //写完一定要来检查一下这个换行,一不小心就忘记了
while(1)
{
cliaddr_len = sizeof(cliaddr); //这得实时更新
connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len); //接收连接
pid = fork();
if(pid == 0)
{
close(listenfd);
while(1)
{
n = read(connfd,buf,MAXLINE); //处理事务(这里为读取内容)
if(n == 0)
{
printf("The other side has been closed··· \n");
break;
}
printf("Read from %s at port %d \n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
/*将客户端的地址读取到str里面然后打印*/ /*将端口号转换成整形数输出*/
for(i = 0;i < n; i++)
{
buf[i] = toupper(buf[i]); //换大写
}
write(connfd,buf,n); //写回去
}
close(connfd);//用完关咯
return 0;
}
else if(pid > 0)
{
close(connfd);
}
else
{
perror("fork:");
}
}
}
//客户端代码和上一章的一样,不占篇幅了。
19.3 多线程并发服务器
多线程服务器是对多进程服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建。
使用多线程并发服务器时要注意以下问题:
1、调整进程内最大文件描述符上限
2、考虑线程同步
3、服务于客户端的线程退出时的退出处理
4、系统负载。随着连接客户端的增多,导致其他线程不能及时得到CPU
上代码:
扫描二维码关注公众号,回复:
9122683 查看本文章
/* server.c*/
#include<stdio.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#define MAXLINE 80
#define SERV_PORT 8000
struct s_info
{
struct sockaddr_in cliaddr;
int connfd;
}
void *run(void *arg)
{
int n,i;
struct s_info *ts = (struct s_info *)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
pthread_detach(pthread_self()); //在创建线程前设置线程属性为分离态
while(1)
{
n = read(ts->connfd,buf,MAXLINE); //处理事务(这里为读取内容)
if(n == 0)
{
printf("The other side has been closed··· \n");
break;
}
printf("Read from %s at port %d \n",inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr,str,sizeof(str)),ntohs((*ts).cliaddr.sin_port));
/*将客户端的地址读取到str里面然后打印*/ /*将端口号转换成整形数输出*/
for(i = 0;i < n; i++)
{
buf[i] = toupper(buf[i]); //换大写
}
write(ts->connfd,buf,n); //写回去
close(ts->connfd);//用完关咯is->
}
}
int main(void)
{
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddr_len;
int listenfd,connfd;
int i = 0;
pthread_t tid;
struct s_info[400];
listenfd = socket(AF_INET,SOCK_STREAM,0); //创建一个网络套接字
bzero(&servaddr,sizeof(servaddr)); //清空结构体变量,准备开始刻画
servaddr.sin_family = AF_INET; //配置网络协议
servaddr.sin_port = htonl(SERV_PORT); //配置端口号
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //配置网络地址
bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); //好了,可以绑定了 别忘了历史遗留问题
listen(listenfd,20); //开始监听,允许20个进程进来
//开始接收数据了
printf("Accepting connections··· \n"); //写完一定要来检查一下这个换行,一不小心就忘记了
while(1)
{
cliaddr_len = sizeof(cliaddr); //这得实时更新
connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len); //接收连接
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
pthread_create(&tid,NULL,run,(void)ts[i]); //达到线程最大时,create做出错处理,增加线程稳定性
i++;
}
return 0;
}
客户端代码同上
附上伪代码
19.4 多路 I/O 转接服务器
(重点来了)
三种模型,select、poll(略去)、epoll
(1)select 模型
1、select 能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数。
2、解决1024以下客户端使用select模型是很合适的,但如果连接客户端过多,由于select采用的是轮询模式,将会大大降低服务器响应效率。因此不应该在select上花过多时间。
直接来看select函数吧
#include<sys/select.h>
int select(int nfds,fd_set *readfds,fd_set writefds,fd_set *exceptfds,struct timeval *timeout);
参数释义:
nfds:监控的文件描述符表里最大的文件描述符+1,此参数会告诉内核级检测前面多少个文件描述符的状态。(等待套接字的数量)
readfds:要检查读事件的容器
writefds:要检查写事件的容器
exceptfds:监控异常事件的容器
timeout:超时时间
//使用fd_set数据类型来表示这个描述字集,我们不用去关心具体的实现细节。
void FD_CLR(int fd,fd_set *set); //把文件描述符表里的某个fd清零
int FD_ISSET(int fd,fd_set *set); //测试文件描述符表中某个fd是否置1 //存在返回非0,不存在返回0
void FD_SET(int fd,.fd_set *set); //将文件描述符表中某个fd置1
void FD_ZERO(fd_set *set); //把文件描述符表中所有位清零
//表从第0位开始计数
行了,上代码
/*server.c*/
#include<stdio.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddr_len;
int i,maxi,maxfd,listenfd,connfd,sockfd;
int nready,client[FD_SETSIZE];
ssize_t n;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
fd_set rset,allset;
// 套路开始
listenfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htonl(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
listen(listenfd,20);
maxfd = listenfd; //初始化
maxi = -1; //client[]的下标
for(i = 0 ; i < FD_SETSIZE ; i++ )
{
client[i] = -1; //用-1初始化client
}
FD_ZERO(&allset);
FD_SET(listenfd,&allset); //构建select监控文件描述符表
while(1)
{
rset = allset; /*区别于前面的服务器的关键步骤*/
//每次循环时都重新设置elect监控信号集
nready = select(maxfd,&rest,NULL,NULL,NULL); //只监控读
if(nready < 0)
{
perrno("select error:");
}
if(FD_ISSET(listenfd,&rest)) //添加监控成功
{
//开始接收数据了
printf("Accepting connections··· \n"); //写完一定要来检查一下这个换行,一不小心就忘记了
cliaddr_len = sizeof(cliaddr); //这得实时更新
connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len); //接收连接
printf("Read from %s at port %d \n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
/*将客户端的地址读取到str里面然后打印*/ /*将端口号转换成整形数输出*/
for(i = 0;i < FD_SETSIZE; i++)
{
if(client[i] < 0)
{
client[i] = connfd; //保存accept返回的文件描述符到client【】里
}
if( i == FD_SETSIZE )
{
printf("Too many clients \n",stderr);
exit(-1);
}
FD_SET(connfd,&allset); //添加一个新的文件描述符到监控信号集里
if(connfd>maxfd)
{
maxfd = connfd; //select 第一个参数需要
}
if(i > maxi)
{
maxi = i;
}
if(--nready == 0)
{
continue; //如果没有更多的就绪文件描述符,则继续回到上面的select阻塞监听,负责处理未处理完的就绪文件描述符
}
}
for(i = 0;i < maxi; i++) //检测哪个client有数据就绪
{
if((sockfd = client[2])<0)
{
continue;
}
if(FD_ISSET(sockfd,&rset))
{
/*当client关闭连接时,服务器端也关闭对应连接*/
close(sockfd);
FD_CLR(sockfd,&allset); //解除select监控此文件描述符
client[i] = -1;
}
else
{
int j;
for(j = 0;j < n; j++)
{
buf[j] = toupper(buf[j]);
}
write(sockfd,buf,n);
}
if(--nready == 0)
{
break;
}
}
}
close(listenfd);//用完关咯
return 0;
}
}
//8说了,我已经要倒了。。
epoll下个章节再说吧