Linux学习之旅(29)----循环服务器模型

使用套接字编程的服务器模型主要包括:循环(轮询)服务器模型、并发服务器模型IO复用服务器模型

套接字函数错误处理

在前几篇文章中的程序中,没有对程序进行出错处理,这是一个非常不好的习惯,在这里我们将出错函数进行了封装。下面直接使用封装之后的函数就好了。

#ifndef SOCKETERRORHANDING
#define SOCKETERRORHANDING

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void Exit(const char* errStr);
int Socket(int domain,int type,int protocol);
int Bind(int sockfd,const struct sockaddr *my_addr,socklen_t addrlen);
int Listen(int sockfd,int backlog);
int Accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
int Connect(int sockfd,struct sockaddr *my_addr,int addrlen);
ssize_t Read(int sockfd,void* buf,size_t bufLen);
ssize_t Readn(int sockfd,void *buf ,size_t bufLen);
ssize_t Write(int sockfd,const void* buf,size_t bufLen);
ssize_t Writen(int sockfd,const void* buf,size_t bufLen);
int Close(int sockfd);

#endif
#include "sockErrHand.h"

void Exit(const char* errStr)
{
    perror(errStr);
}

int Socket(int domain,int type,int protocol)
{
    int sockDis=socket(domain,type,protocol);
    if(sockDis<0)
    {
        Exit("socket error!");
    }
    return sockDis;
}

int Bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen)
{
    int n=bind(sockfd,my_addr,addrlen);
    if(n<0)
    {
        Exit("bind error");
    }
    return 0;
}

int Listen(int sockfd,int backlog)
{
    if(listen(sockfd,backlog)<0)
    {
        Exit("listen error");
    }
    return 0;
}

int Accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)
{
    int newSockfd=accept(sockfd,addr,addrlen);
    while(newSockfd<0&&(errno == ECONNABORTED || errno == EINTR))
    {
        sleep(1);
        sockfd=accept(sockfd,addr,addrlen);
    }
    if(newSockfd<0)
    {
        Exit("accept error");
    }
    return newSockfd;
}

int Connect(int sockfd,struct sockaddr *my_addr,int addrlen)
{
    if(connect(sockfd,my_addr,addrlen)<0)
    {
        Exit("connect error");
    }
    return 0;
}

ssize_t Read(int sockfd,void *buf,size_t bufLen)
{
    ssize_t n=read(sockfd,buf,bufLen);
    while(n<0&&errno==EINTR)
    {
        n=read(sockfd,buf,bufLen);
    }
    if(n<0)
    {
        Exit("read error");
    }
    return n;
}

ssize_t Write(int sockfd,const void* buf,size_t bufLen)
{
    ssize_t n=write(sockfd,buf,bufLen);
    while(n<0&&errno==EINTR)
    {
        n=write(sockfd,buf,bufLen);
    }
    if(n<0)
    {
        Exit("write error");
    }
    return n;
}

int Close(int sockfd)
{
    if(close(sockfd)<0)
    {
        Exit("close error");
    }
    return 0;
}

ssize_t Readn(int sockfd,void *buf ,size_t bufLen)
{
    //当接收的数据包大于MTU时,会将数据分割为几个包。
    ssize_t rlen=0,len;
    while(rlen<bufLen)
    {
        len=Read(sockfd,buf,bufLen);
        rlen+=len;
        buf+=rlen;
    }
    return rlen;
}

ssize_t Writen(int sockfd,const void* buf,size_t bufLen)
{
    ssize_t rlen=0,len;
    while(rlen<bufLen)
    {
        len=Write(sockfd,buf,bufLen);
        rlen+=len;
        buf+=rlen;
    }
    return rlen;
}

循环模型

循环服务器指的是对于客户端的请求和连接,服务器再处理完毕一个之后再处理另一个,即进行串行处理客户端的请求。循环服务器经常用于UDP服务程序,例如时间服务程序、DHCP服务器。

UDP循环服务器模型

服务器再recv和处理数据这两项业务之间轮询处理,所以循环服务器模型又被称为轮询服务器模型。

程序1、UDP循环模型服务器

服务器功能:客服端发送时间请求,服务器向客户端会送服务器端的时间。

#include <string.h>
#include <stdio.h>
#include <time.h>
#include "sockErrHand.h"

#define PORT 5500
#define SIZE 1024

int main()
{
    int serSock=Socket(AF_INET,SOCK_DGRAM,0);
    struct sockaddr_in serAddr,cliAddr;
    bzero(&serAddr,sizeof(serAddr));
    bzero(&cliAddr,sizeof(cliAddr));
    serAddr.sin_family=AF_INET;
    serAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    serAddr.sin_port=htons(PORT);
    Bind(serSock,(struct sockaddr*)(&serAddr),sizeof(serAddr));
    printf("套接字和端口成功已绑定\n");
    while(1)
    {
        char dataBuf[SIZE];
        time_t t;
        socklen_t cliAddrLen=sizeof(cliAddr);
        //清空数据区内容,防止发送内容对接收内容造成影响
        memset(dataBuf,0,SIZE);
        recvfrom(serSock,(void*)(dataBuf),SIZE,0,(struct sockaddr*)(&cliAddr),&cliAddrLen);
        //当发送的数据为TIME是处理,否则不处理
        if(strcmp("TIME\n",dataBuf)==0)
        {
            time(&t);
            //防止接收内容对发送内容造成影响
            memset(dataBuf,0,SIZE);
            ctime_r(&t,dataBuf);
            sendto(serSock,(void*)dataBuf,strlen(dataBuf),0,(struct sockaddr*)(&cliAddr),cliAddrLen);
        }
    }
    Close(serSock);
    return 0;
}

使用nc命令测试服务器:

程序二、TCP循环服务器模型

相比较于UDP协议的循环服务器,TCP协议的循环服务器的主过程中多了一个accept的过程,即TCP服务器需要和客户端建立连接之后才可以通信,而且accept函数为阻塞函数,所以一般情况下,服务器会阻塞再accept函数出等待客户端的连接。对accept函数的不同处理时区分各种服务求的一个重要依据。

#include <stdio.h>
#include <string.h>
#include <time.h>
#include "sockErrHand.h"

#define PORT 5500
#define LISNUM 64
#define SIZE 1024

int main()
{
    //ipv4、流式套接字和默认协议
    int serSock=Socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serAddr,cliAddr;
    bzero(&serAddr,sizeof(serAddr));
    bzero(&cliAddr,sizeof(cliAddr));
    serAddr.sin_family=AF_INET;
    serAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    serAddr.sin_port=htons(PORT);
    Bind(serSock,(struct sockaddr_in*)(&serAddr),sizeof(serAddr));
    Listen(serSock,LISNUM);
    printf("TCPSERVERLISTENING.....\n");
    while(1)
    {
        char dataBuf[SIZE];
        time_t t;
        socklen_t cliAddrLen=sizeof(cliAddr);
        memset(dataBuf,0,SIZE);
        int cliSock=Accept(serSock,(struct sockaddr*)(&cliAddr),&cliAddrLen);
        int readLen=Read(cliSock,(void*)dataBuf,SIZE);
        if(strcmp("TIME\n",dataBuf)==0)
        {
            memset(dataBuf,0,SIZE);
            time(&t);
            ctime_r(&t,dataBuf);
            Write(cliSock,(void*)dataBuf,strlen(dataBuf));
        }
        Close(cliSock);
    }
    Close(serSock);
    return 0;
}

在上面的程序中存在一些问题:

因为accept、read和write函数都是阻塞函数,所以在程序执行过程中就有可能发生阻塞。

(1)如果没有客户端的连接请求,进程(单进程)会阻塞在accept函数出,程序不能进行其他任何操作(系统调用使得程序从用户态进入了内存态)。

(2)当和客户端成功建立连接后,程序会阻塞在read处,等待接收客户端发送的数据,如果此时有其他客户端尝试建立连接,程序就会无法响应。

(3)如果客户端接收数据异常的缓慢(客户端读取文件的长度比服务器写文件的速度慢),write就会导致写缓冲区满,数据发送不出去。

如果将这些阻塞函数修改为非阻塞函数:

那么就需要服务器就会一直在accept、read和write三个函数之间不停的循环,一旦客户端连接过多就会导致服务器的压力增大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值