服务器端的开发主要要考虑的问题:稳定.高效.如果要服务器高效, 最好莫过于异步模型,
所谓异步就是应用程序只管提交I/O请求,由系统内核帮你完成I/O处理。但是这样的模型少之又少。
WINDOWS下只有IOCP完成端口模型,Linux下就找不到这样的模型了。
下面介绍几种常见的模型:
--linux
(1)select模型.
与一般的模型相比.它可以防止因为应用程序在一次I/O请求中被"锁定"导致无法响应其他请求
使用select模型要注意, 每次I/O处理完后,对应的SOCKET就被在fd_set中了.故要重新FD_SET.
下面一个简单的Select模型的服务器端.希望能给大家带来帮助
{
int listenfd, connfd, maxfd, sockfd;
int i, reuse, nready, client[1024], nreadsize;
fd_set allset;
char buffer[1024];
struct sockaddr_in my_addr;
struct timeval tv;
if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket:");
return -1;
}
reuse = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(i));
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(global.cfg.c_smg.SpPort);
my_addr.sin_addr.s_addr=INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
if(bind(listenfd,(struct sockaddr *)&my_addr,
sizeof(struct sockaddr))==-1)
{
perror("bind:");
close(listenfd);
return -1;
}
if(listen(listenfd,BACKLOG)==-1)
{
perror("listen:");
close(listenfd);
return -1;
}
maxfd = listenfd;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
tv.tv_sec =0;
tv.tv_usec=100;
/* 初始化保存client的数组 */
for(i = 0; i< 1024; i++) {
client[i] = -1;
}
while(1) {
nready = select(maxfd+1, &allset, NULL, NULL, &tv);
if(nready < 0) {
perror("select:");
close(listenfd);
break;
}
/* 新的连接客户端 */
if(FD_ISSET(listenfd, &allset)) {
connfd = accept(listenfd, NULL, NULL);
if (connfd < 0) {
perror("accept:");
continue;
}
/* 保存 connfd */
for (i = 0; i< 1024; i++) {
if (client[i] < 0) {
client[i] = connfd;
break;
}
}
if (i == 1024) {
/* 客户端太多了 */
close(listenfd);
break;
}
FD_SET(connfd, &allset);
if (connfd > maxfd) {
maxfd = connfd;
}
if (--nready <=0) {
continue;
}
}
/*已连接的客户端 */
for (i = 0; i< 1024; i++) {
if (client[i] < 0) {
continue;
}
sockfd = client[i];
if(FD_ISSET(sockfd, &allset)) {
nreadsize = recv(sockfd, buffer, 1024, 0);
if (nreadsize <= 0) {
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
continue;
}
else {
/* 你的处理函数 */
buffer[nreadsize] = '/0';
ProcessSmgSentMsg(buffer,nreadsize);
}
if (--nready <= 0) {
break;
}
}
}
}
}
select 的缺点:
(1)每次都会对所有FD进行线性扫描.故导致效率不高
(2)有内核到应用程序的内存拷贝, 而这种拷贝很费时间
(2)EPOLL模型:
与一般的模型相比.它其实已经是一个半异步的模型了,只不过系统内核并没有帮你完成I/O处理.
内核通过绑定在FD上的CALLBACK函数通知应用程序I/O是否完成,没必要进行线性扫描, 而且内核
和应用程序共享一块内存, 避免了费时的内存拷贝,故EPOLL效率更高
EPOLL有2种工作模式:
(1)水平触发:(LT)
内核通知你FD已经就绪,可以进行I/O操作,如果你不操作内核会一直通知你
(2)边缘触发:
又称为高速模式, 内核通知你FD已经就绪,可以进行I/O操作,如果你不操作, 内核就不会通知你.
故使用这模式需要注意:
(1).FD应该为非阻塞模式
(2).每次操作,需要将缓冲区中的数据处理完
下面一个简单的EPOLL模型的服务器端.希望能给大家带来帮助
{
nsocket = socket(AF_INET, SOCK_STREAM, 0);
if(nsocket < 0) {
return 0;
}
//设置为非阻塞
setnonblocking(nsocket);
memset(&serverAddr, 0, sizeof(struct sockaddr_in));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(listenport);
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(nsocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
close(nsocket);
return 0;
}
listen(nsocket, 5);
epollfd = epoll_create(1024);
if(epollfd < 0) {
close(nsocket);
return 0;
}
ev.data.fd = nsocket;
ev.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, nsocket, &ev) < 0) {
close(nsocket);
return 0;
}
while (1) {
fdnums = epoll_wait(epollfd, events, 1024, 10);
if (fdnums < 0 && errno != EINTR) {
return 0;
}
for (int i = 0; i< fdnums; i++) {
if (nsocket == events[i].data.fd) {
clientSocket = accept(nsocket, (struct sockaddr*)&clientAddr, (socklen_t*)&clientlen);
if (clientSocket < 0) {
continue;
}
setnonblocking(clientSocket);
ev.data.fd = clientSocket;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, clientSocket, &ev);
}
else {
//处理请求
...
//处理完了
epoll_ctl(epollfd, EPOLL_CTL_DEL, events[i].data.fd, &ev);
close(events[i].data.fd);
}
}
}
}
明天介绍下WINDOWS下完成端口模型