目录
在网络模型中,服务器模型是指在计算机网络中扮演服务器角色的计算机系统或软件。它用于接收和处理客户端的请求,并向客户端提供所需的服务或资源。以下是一些常见的服务器模型:
这些服务器模型在不同的网络环境和应用场景中有不同的适用性。根据具体的需求和目标,可以选择适当的服务器模型来满足网络服务的要求。
在网络程序里面,通常都是一个服务器处理多个客户机。
为了处理多个客户的请求,服务器的程序有不同的处理方式。
循环服务器模型(Round Robin Server Model)是一种负载均衡的服务器模型。在这种模型中,多台服务器按照轮询的方式依次接收和处理客户端的请求,以实现请求的均衡分配。循环服务器一次只能响应一个客户端的请求。伪代码如下
- socket() //创建套接字
- bind()//绑定套接字
- listen()//监听
- while(1)
- {
- accept();//创建新套接字和客户端连接
- while(1)
- {
- process();//对接收的数据进行处理
- }
- close();//关闭套接字
- }
并发服务器模型(Concurrent Server Model)是一种服务器模型,旨在处理同时到达的多个客户端请求。在这种模型中,服务器能够同时处理多个请求,而无需等待前一个请求的完成。
以下是一种常见的并发服务器模型:
在多线程服务器模型中,服务器为每个客户端请求创建一个独立的线程来处理。当有新的请求到达时,服务器创建一个新的线程,并将该请求分配给该线程进行处理。这样,服务器可以同时处理多个请求,每个请求都在独立的线程中执行。这种模型具有较低的开销和快速的响应时间,但需要额外的线程管理和同步机制来确保线程安全性。伪代码如下
- socket();
- bind();
- listen();
- while(1)
- {
- accept();
- if(fork()==0)//子进程
- {
- while(1)
- {
- process();//进行处理
- }
- close();
- exit();
- }
- else
- {
-
- }
- }
- //注:收到客户端消息后,打印下是来自哪个客户端的数据(来电显示)
- //注:使用SIGCHLD来处理子进程结束的信号,信号函数中回收进程资源。
- //当子进程结束时,会向其父进程发送一个SIGCHLD信号。这个信号通知父进程子进程的状态发生了变化,
- //可以通过捕捉SIGCHLD信号来处理子进程的退出状态。
在多进程服务器模型中,服务器为每个客户端请求创建一个独立的进程来处理。每个进程具有自己的内存空间和执行环境,因此可以独立地执行请求。这种模型提供了更好的隔离性和稳定性,但进程间切换的开销较大。伪代码如下
- socket()
- bind();
- listen();
- while(1)
- {
- accept();
- pthread_create();//创建线程
- }
在事件驱动服务器模型中,服务器使用异步I/O和事件循环机制来处理多个客户端请求。服务器使用单个线程或进程来接收和处理所有的请求,并使用事件通知机制来处理客户端的I/O操作。这种模型具有高效的资源利用率和可扩展性,适用于处理大量并发请求。
借助select、poll、epoll机制,将新连接的客户端描述符增加到描述符表中,只需要一个线程即可处理所有的客户端连接,在嵌入式开发中应用广泛,不过代码写起了稍显繁琐。、
在网络通信中,很多操作会使得进程阻塞:
TCP套接字中的recv/accept
UDP套接字中的recvfrom
超时检测的必要性
避免进程在没有数据时无限制地阻塞
实现某些特定协议要求,比如某些设备规定,发送请求数据后,如果多长时间后没有收到来自设备的回复,需要做出一些特殊处理
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;
- }
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));
- //设置应用层,允许重用端口和地址
- 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;
- }