网络编程 lesson6 服务器模型和网络超时检测

目录

服务器模型介绍

网络编程服务器模型

循环服务器模型

并发服务器模型

1. 多线程服务器

2. 多进程服务器

3. 事件驱动服务器

网络超时检测

应用场景

设置超时检测的方式

1.利用函数参数设置

代码示例

2.利用socket属性设置

3.利用alarm定时器设置

代码示例


服务器模型介绍

在网络模型中,服务器模型是指在计算机网络中扮演服务器角色的计算机系统或软件。它用于接收和处理客户端的请求,并向客户端提供所需的服务或资源。以下是一些常见的服务器模型:

  1. 客户端/服务器模型:这是最常见的服务器模型之一。在这种模型中,客户端(例如个人计算机、智能手机或其他设备)发送请求到服务器,服务器处理这些请求并返回响应。这种模型适用于诸如网页浏览、电子邮件、文件传输等各种应用。
  2. 分布式服务器模型:在分布式服务器模型中,多台服务器协同工作,共同处理客户端的请求。这种模型可以提高系统的性能和可伸缩性,因为负载可以在多个服务器之间分配,而不是由单个服务器承担所有请求。
  3. 客户端/代理/服务器模型:在这种模型中,代理服务器充当位于客户端和目标服务器之间的中间人。客户端发送请求到代理服务器,代理服务器代表客户端与目标服务器通信,并将响应返回给客户端。这种模型常用于提供缓存、负载均衡和安全性增强等功能。
  4. 对等网络模型:在对等网络模型中,计算机之间没有明确的客户端和服务器角色。相反,所有计算机都可以充当客户端和服务器,并与其他计算机直接通信。这种模型适用于对等文件共享、即时通讯等应用。
  5. 云服务器模型:云服务器模型是指在云计算环境中提供的服务器服务。云服务器提供虚拟化的计算资源,客户可以根据需要动态分配和管理这些资源。这种模型具有弹性和可伸缩性,适用于处理大规模的网络流量和复杂的应用。

这些服务器模型在不同的网络环境和应用场景中有不同的适用性。根据具体的需求和目标,可以选择适当的服务器模型来满足网络服务的要求。

网络编程服务器模型

在网络程序里面,通常都是一个服务器处理多个客户机。

为了处理多个客户的请求,服务器的程序有不同的处理方式。

循环服务器模型

循环服务器模型(Round Robin Server Model)是一种负载均衡的服务器模型。在这种模型中,多台服务器按照轮询的方式依次接收和处理客户端的请求,以实现请求的均衡分配。循环服务器一次只能响应一个客户端的请求。伪代码如下

socket() //创建套接字
bind()//绑定套接字
listen()//监听
while(1)
{
    accept();//创建新套接字和客户端连接
    while(1)
    {
        process();//对接收的数据进行处理
    }
    close();//关闭套接字
}

并发服务器模型

并发服务器模型(Concurrent Server Model)是一种服务器模型,旨在处理同时到达的多个客户端请求。在这种模型中,服务器能够同时处理多个请求,而无需等待前一个请求的完成。

以下是一种常见的并发服务器模型:

1. 多线程服务器

在多线程服务器模型中,服务器为每个客户端请求创建一个独立的线程来处理。当有新的请求到达时,服务器创建一个新的线程,并将该请求分配给该线程进行处理。这样,服务器可以同时处理多个请求,每个请求都在独立的线程中执行。这种模型具有较低的开销和快速的响应时间,但需要额外的线程管理和同步机制来确保线程安全性。伪代码如下

socket();
bind();
listen();
while(1)
{
    accept();
    if(fork()==0)//子进程
    {
        while(1)
        {
            process();//进行处理
        }
        close();
        exit();
    }
    else
    {
        
    }
}
//注:收到客户端消息后,打印下是来自哪个客户端的数据(来电显示)
//注:使用SIGCHLD来处理子进程结束的信号,信号函数中回收进程资源。
//当子进程结束时,会向其父进程发送一个SIGCHLD信号。这个信号通知父进程子进程的状态发生了变化,
//可以通过捕捉SIGCHLD信号来处理子进程的退出状态。

2. 多进程服务器

在多进程服务器模型中,服务器为每个客户端请求创建一个独立的进程来处理。每个进程具有自己的内存空间和执行环境,因此可以独立地执行请求。这种模型提供了更好的隔离性和稳定性,但进程间切换的开销较大。伪代码如下

socket()
bind();
listen();
while(1)
{
	accept();
	pthread_create();//创建线程
}

3. 事件驱动服务器

在事件驱动服务器模型中,服务器使用异步I/O和事件循环机制来处理多个客户端请求。服务器使用单个线程或进程来接收和处理所有的请求,并使用事件通知机制来处理客户端的I/O操作。这种模型具有高效的资源利用率和可扩展性,适用于处理大量并发请求。

借助select、poll、epoll机制,将新连接的客户端描述符增加到描述符表中,只需要一个线程即可处理所有的客户端连接,在嵌入式开发中应用广泛,不过代码写起了稍显繁琐。、

网络超时检测

应用场景

在网络通信中,很多操作会使得进程阻塞:

TCP套接字中的recv/accept

UDP套接字中的recvfrom

超时检测的必要性

避免进程在没有数据时无限制地阻塞

实现某些特定协议要求,比如某些设备规定,发送请求数据后,如果多长时间后没有收到来自设备的回复,需要做出一些特殊处理

设置超时检测的方式

1.利用函数参数设置

select,poll,epoll函数可以通过最后一个参数设置超时检测

//1.select函数设置超时检测
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
struct timeval tm={2,0};//设置2s打算阻塞
sret=select(maxfd+1,&tempfds,NULL,NULL,&tm);
//通过设置第5个参数来进行超时检测
struct timeval{
    long tv_sec;//秒
    long tv_usec;//微秒
}

//2.poll设置超时
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//第三个参数:时间单位是毫秒 -1阻塞, 2000=2s
 ret = poll(event, num, 2000);//超时检测时间为2s

//3.epoll函数设置超时
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
//第四个参数:时间单位是毫秒 -1阻塞, 2000=2s
ret = epoll_wait(epfd, events, 20, 2000);

上面设置超时后的返回值:
<0	error
=0	超时
>0	正常

代码示例

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void *mythread(void *arg)
{
    int acceptfd = *((int *)arg); //4
                                  //循环收发消息
    char buf[128];
    int ret;
    while (1)
    {
        ret = recv(acceptfd, buf, sizeof(buf), 0);
        if (ret < 0)
        {
            perror("recv err.");
            return NULL;
        }
        else if (ret == 0)
        {
            printf("client exit\n");
            close(acceptfd);
            break;
        }
        else
        {
            printf("buf:%s\n", buf);
        }
    }
    pthread_exit(NULL); //return NULL;
}
int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("please input %s \n", argv[0]);
        return -1;
    }
    //1.创建流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0); //链接
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }
    printf("sockfd:%d\n", sockfd); //3
    //填充ipv4的通信结构体

    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1])); 
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    socklen_t len = sizeof(caddr);

    //2.绑定套接字 ip和端口(自己)
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err.");
        return -1;
    }
    printf("bind ok.\n");

    //3.监听
    if (listen(sockfd, 5) < 0)
    {
        perror("listen err.");
        return -1;
    }
    printf("listen ok.\n");

    //4.阻塞等待客户端链接
    while (1)
    {
        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
        if (acceptfd < 0)
        {
            perror("accept err.");
            return -1;
        }
        printf("acceptfd=%d\n", acceptfd); //通信
        printf("client:ip=%s port=%d\n",
               inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

        pthread_t tid;
        pthread_create(&tid, NULL, mythread, &acceptfd);
        pthread_detach(tid);
    }
    close(sockfd);
    return 0;
}

2.利用socket属性设置

socket属性表

选项名称        说明                  数据类型 
======================================================================== 
	SOL_SOCKET  应用层
------------------------------------------------------------------------ 
SO_BROADCAST	允许发送广播数据          int 
SO_DEBUG		允许调试            		 int 
SO_DONTROUTE    不查找路由                 int 
SO_ERROR        获得套接字错误             int 
SO_KEEPALIVE    保持连接                  int 
SO_LINGER       延迟关闭连接               struct linger 
SO_OOBINLINE    带外数据放入正常数据流           int 
SO_RCVBUF       接收缓冲区大小               int 
SO_SNDBUF       发送缓冲区大小               int 
SO_RCVLOWAT     接收缓冲区下限               int 
SO_SNDLOWAT     发送缓冲区下限              int 
SO_RCVTIMEO     接收超时                 struct timeval 
SO_SNDTIMEO     发送超时                 struct timeval 
SO_REUSEADDR    允许重用本地地址和端口          int 
SO_TYPE         获得套接字类型              int 
SO_BSDCOMPAT    与BSD系统兼容               int 
==========================================================================             
         IPPROTO_IP  IP层/网络层
----------------------------------------------------------------------------
IP_HDRINCL      在数据包中包含IP首部          int 
IP_OPTINOS      IP首部选项               int 
IP_TOS          服务类型 
IP_TTL          生存时间                int 
IP_ADD_MEMBERSHIP       将指定的IP加入多播组                    struct ip_mreq
==========================================================================            
		IPPRO_TCP  传输层
-----------------------------------------------------------------------------
TCP_MAXSEG      TCP最大数据段的大小            int 
TCP_NODELAY     不使用Nagle算法             int  

API接口

int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen)
int setsockopt(int sockfd,int level,int optname,void *optval,socklen_t optlen)
功能:获得/设置套接字属性
参数:
sockfd:套接字描述符
level: 协议层
		SOL_SOCKET(应用层)
		IPPROTO_TCP(传输层)
		IPPROTO_IP(网络层)
optname:选项名
		SO_BROADCAST    允许发送广播数据            int 
		SO_RCVBUF       接收缓冲区大小              int 
		SO_SNDBUF       发送缓冲区大小              int 
		SO_RCVTIMEO     接收超时                	struct timeval 
		SO_SNDTIMEO     发送超时                	struct timeval
optval:选项值
optlen:选项值大小指针
设置超时检测

struct timeval{
    long tv_sec; //秒
    long tv_usec; //微秒
}
//设置接收超时
struct timeval tm ={2,0};
setsockopt(acceptfd,SOL_SOCKET,SO_RCVTIMEO,&tm,sizeof(tm));
//设置应用层的发送超时


//设置端口和地址重用
int optval=1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
//设置应用层,允许重用端口和地址

3.利用alarm定时器设置

alarm(5) 闹钟	定时器
//5秒之后,会有一个信号产生SIGALRM
int sigaction(int signum,const struct *act,struct sigaction *oldact);
功能:对接收到的指定信号处理
struct sigaction{
	void     (*sa_handler)(int);
};

//设置信号属性
struct sigaction act;
sigaction(SIGALRM,NULL,&act);//获取原属性
act.sa_handler=handler;//修改属性
sigaction(SIGALRM,&act,NULL);//将修改的属性设置回去

注:
在recv前调用alarm函数
alarm的 SIGALRM信号产生后会打断(终端)下面的系统调用recv;
打断后相当于recv执行完毕。

代码示例

#include 
#include 
#include 

void handler(int sig)
{
    printf("timeout\n");
}

int main(int argc, char const *argv[])
{
    char buf[32];
    //修改信号的属性
    struct sigaction act;
    sigaction(SIGALRM,NULL,&act);
    act.sa_handler=handler;
    sigaction(SIGALRM,&act,NULL);

    while(1)
    {
        alarm(5);
        printf("hello\n");
        if(fgets(buf,sizeof(buf),stdin) == NULL)
        {
            perror("fgets err.");
            continue;
        }
        printf("buf:%s\n",buf);
    }
    return 0;
}

你可能感兴趣的:(网络编程,服务器,网络)