Linux高併發伺服器解決方案
Linux高併發伺服器案例演示
在網路通訊中,我們常常的伺服器經常會受到成千上萬的請求提示,而電腦會根據請求建立相對應的socket連結,但是接觸過Linux網路程式設計的人都知道,Linux連結和客戶端建立連線,會經過四步(這裡以TCP說明)
第一步,建立socket對應的描述符,這裡設定好socket的協議型別以及通訊型別(TCP/UDP)
#include <sys/types.h>/* See NOTES */
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
具體的使用方法可以使用man手冊檢視socket函式
第二步,繫結埠,以及相應的ip地址(伺服器不用設定)
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
intbind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
具體的使用方法可以使用man手冊檢視bind函式
第三步,監聽socket,並且設定最大監聽數
#include <sys/types.h> /* SeeNOTES */
#include<sys/socket.h>
int listen(int sockfd, int backlog);
具體的使用方法可以使用man手冊檢視listen函式
第四步,接受客戶端的連線
#include <sys/types.h> /* See NOTES */
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t*addrlen);
具體的使用方法可以使用man手冊檢視accept函式
注意:
由於上述四步socket建立連結中,accept是阻塞的,意味著如果有很多人同時發出socket請求的時候,伺服器只會接收到一少部分的連線,其他都在阻塞佇列排隊,甚至丟失!
具體實現方法:
使用epoll分路技術,讓accept不陷入阻塞,當那個客戶端發出請求,則處理髮出請求的客戶端資訊,這時候因為客戶端發出請求,則必定有讀寫操作,所以讀寫操作不用去掉阻塞。
伺服器程式碼:
//模擬接收每個客戶端發來的請求後並往dbg.txt檔案寫入一個位元組
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void sig_handle(int sig)
{
printf("recv signal :%d\n",sig);
}
int main(int argc, char * argv[])
{
signal(SIGPIPE,sig_handle);
if (argc<2)
{
printf("usage:%s + [count]\n",argv[0]);
return 0;
}
unlink("dbg.txt");
int dbg = open("dbg.txt",O_CREAT|O_APPEND|O_RDWR,0666);
int count = atoi(argv[1]);
int fd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(9988);
int ret = bind(fd,(struct sockaddr*)&addr,sizeof(addr));
if (ret ==-1)
{
perror("bind");
return 0;
}
listen(fd,250);
int is_child_process = 0;//判斷在哪個程序中,父程序0,子程序1
for (int i = 0 ; i < count ; i++)
{
pid_t pid = fork();
if (pid==0)
{
is_child_process = 1;
break;
}
}
struct epoll_event ev;
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = fd;
int epfd = epoll_create(1024);//建立epfd的描述符
int flags = fcntl(fd,F_GETFL);
flags |= O_NONBLOCK;
fcntl(fd,F_SETFL,flags);
epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
while (1)
{
struct epoll_event evs[10];
int process_count = epoll_wait(epfd,evs,10,5000);
if (process_count == 0) continue;//如果監聽的程序都沒有事件產生,則再次進入迴圈,繼續監聽
for (int i = 0 ; i < process_count ;i++)
{
if (evs[i].data.fd == fd)
{
//當程序中的socket描述符是server的socket本身時候,則accept否則就直接操作
int ret = accept(evs[i].data.fd,NULL,NULL);
if (ret == -1)
{
printf("errno:%s",strerror(errno));
//其他錯誤,直接exit
break;
}
ev.data.fd = ret;
epoll_ctl(epfd,EPOLL_CTL_ADD,ret,&ev);
}
else
{
//read or write
char buf[1024];
int ret = read(evs[i].data.fd,buf,sizeof(buf));
if (ret == -1)
{
perror("read");
if (errno == EINTR)
break;
exit(0);
}
else if (ret == 0)
{
//normal exit
close(evs[i].data.fd);
break;
}
//printf("recv data %s from pid:%d\n",buf,getpid());
write(dbg,"1",1);
}
}
}
if (!is_child_process)
{
for (int i = 0 ; i < count; i ++)
{
wait(NULL);//等待所有的子程序退出為止
}
}
return 0;
}
測試客戶端向伺服器發請求程式碼:
//模擬有20000個客戶端同時向伺服器發請求
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <string.h>
#define PROCESS_COUNT 20000
void func(int argc,char * argv[])
{
int fd =socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9988);
addr.sin_addr.s_addr =inet_addr("127.0.0.1");
connect(fd,(structsockaddr*)&addr,sizeof(addr));
if (argc==2)
write(fd,argv[1],strlen(argv[1]));
else
write(fd,"1",1);
char buf[1024];
//recv(fd,buf,sizeof(buf),0);
}
int main(int argc,char *argv[])
{
for (int i = 0 ; i <PROCESS_COUNT; i++)
{
pid_t pid = fork();
if (pid == 0)
{
func(argc,argv);
return 0;
}
}
for (int i = 0 ; i <PROCESS_COUNT; i++)
{
wait(NULL);
}
return 0;
}
結果分析:不同的電腦測試結果略有不同,我的客戶端定義有20000個同時向伺服器發請求,結果處理的請求大概有15500左右,不同的電腦最高併發數略有不同。成功的解決了多使用者同時向一個伺服器發請求的問題