第十二章 IO 复用——第二种并发服务器的实现方式

多进程服务端的缺点和解决方法

多进程服务端只要有客户端连接请求就会创建新进程。但是因为创建进程时需要付出极大的代价,需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法(IPC属于相对复杂的通信方法)。

而IO复用技术,可以在不创建进程的同时向多个客户端提供服务。

复用

select函数可以将多个文件描述符集中到一起同一监视,监视内容如下:

  • 是否存在套接字接收数据
  • 无需阻塞传输数据的套接字有哪些
  • 哪些套接字发生了异常
    在这里插入图片描述

设置文件描述符

在fd_set变量中注册或更改值的宏操作:

FD_ZERO(fd_set *fdset); // 初始化所有位为0
FD_SET(int fd, fd_set *fdset); // 注册文件描述符的信息
FD_CLR(int fd, fd_set *fdset); // 清楚文件描述符的信息
FD_ISSET(int fd, fd_set *fdset); // 若包含文件描述符fd,则返回真

设置检查范围及超时

// Linux
#include <sys/select.h>
#include <sys/time.h>

int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
// 成功时返回大于0的值,失败时返回-1
// maxfd - 监视对象文件描述符数量
// readset - 将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值。
// writeset - 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值
// exceptset - 将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值
// timeout - 调用select函数后,为防止陷入无限阻塞的状态,传递超时信息

// Windows
#include <winsock2.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *excepfds, const struct timeval *timeout);
// 成功时返回0,失败时返回-1

Windows下的IO复用服务器端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#define BUF_SIZE 1024
void ErrorHandling(const char *message);

int main(int argc, char *argv[])
{
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    SOCKADDR_IN servAdr, clntAdr;
    TIMEVAL timeout;
    fd_set reads, cpyReads;

    int adrSz;
    int strLen, fdNum, i;
    char buf[BUF_SIZE] = {0};

    if(argc != 2){
        printf("Usage %s <port>\n", argv[0]);
        exit(1);
    }

    if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){
        ErrorHandling("WSAStartup() error!");
    }

    hServSock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&servAdr, 0, sizeof(servAdr));
    servAdr.sin_family = AF_INET;
    servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAdr.sin_port = htons(atoi(argv[1]));

    if(bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR){
        ErrorHandling("bind() error!");
    }

    if(listen(hServSock, 5) == SOCKET_ERROR){
        ErrorHandling("listen() error!");
    }

    FD_ZERO(&reads);
    FD_SET(hServSock, &reads);

    while (1)
    {
        cpyReads = reads;
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;

        if((fdNum = select(0, &cpyReads, 0, 0, &timeout)) == SOCKET_ERROR){
            break;
        }

        if(fdNum == 0){
            continue;
        }

        for (i = 0; i < reads.fd_count; i++)
        {
            if(FD_ISSET(reads.fd_array[i], &cpyReads))
            {
                if(reads.fd_array[i] == hServSock)
                {
                    adrSz = sizeof(clntAdr);
                    hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &adrSz);
                    FD_SET(hClntSock, &reads);
                    printf("connected client: %d \n", hClntSock);
                }else{
                    strLen = recv(reads.fd_array[i], buf, BUF_SIZE - 1, 0);
                    if(strLen == 0){
                        FD_CLR(reads.fd_array[i], &reads);
                        closesocket(cpyReads.fd_array[i]);
                        printf("closed client: %d\n", cpyReads.fd_array[i]);
                    }else{
                        send(reads.fd_array[i], buf, strLen, 0);
                    }
                }
            }
        }
    }
    closesocket(hServSock);
    WSACleanup();
    return 0;
}

void ErrorHandling(const char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值