《TCP/IP网络编程》第12章 I/O复用

92 篇文章 19 订阅
34 篇文章 3 订阅

理解复用

1个通信频道中传递多个数据(信号)的技术,用最少物理要素传递最多数据用于提高物理设备效率的技术。

可分为时(time)分复用技术和频(frequency)分复用技术。

select函数

select函数将多个文件描述符集中到一起统一监视:

  • 是否存在套接字接收了数据?
  • 无需阻塞传输数据的套接字有哪些?
  • 哪些套接字发生了异常?

监视项称为事件,发送监视项对应情况称为发生了事件。

调用方法和顺序

  1. 设置文件描述符
    需要监视的文件描述符(关联套接字),用fd_set数组变量进行设置。
//初始化
FD_ZERO(fd_set *fdset)
//注册
FD_SET(int fd, fd_set *fdset)
//清除
FD_CLR(int fd, fd_set *fdset)
//是否包含
FD_ISSET(int fd, fd_set *fdset)
  1. 指定监视范围及设置超时
#include <sys/select.h>
#include <sys/time.h>

//成功返回发生事件的文件描述符数,失败-1
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
//maxfd,监视文件描述符数量,一般取监视的最大文件描述符的值。
//readset,用于监视存在待读取数据的文件描述符。
//writeset,用于监视存在可传输无阻塞数据的文件描述符。
//exceptset,用于监视发生异常的文件描述符。
//timeout,超时信息。

//select只有在监视的文件描述符发生变化时(否则阻塞)才返回。timeval设置最长等待时间(NULL阻塞),超时后返回0。
struct timeval {
	long tv_sec;//seconds
	long tv_usec;//microseconds
};
  1. 调用select函数后查看调用结果
    select返回值大于0,必定存在监视文件描述符中发生了指定事件。
for(int i=0; i<=fd_max; i++) {
	if(FD_ISSET(i, &readset)) {
	
	} else if(FD_ISSET(i, &writeset)) {
	
	} else if(FD_ISSET(i, &exceptset)) {
	
	} 
}

select示例

12.select_linux.c
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 30

int main(int argc, char *argv[])
{
    fd_set reads;
    FD_ZERO(&reads);
    FD_SET(0, &reads); // 0 is standard input(console)

    while (1)
    {
        fd_set temps = reads;
        struct timeval timeout = {5, 0};
        // 调用select后,timeout的tv_sec和tv_usec将被替换为超时前剩余时间
        int result = select(1, &temps, 0, 0, &timeout);
        if (result == -1)
        {
            puts("select() error!");
            break;
        }
        else if (result == 0)
        {
            puts("Time-out!");
            continue;
        }
        else
        {
            if (FD_ISSET(0, &temps))
            {
                char buf[BUF_SIZE];
                int str_len = read(0, buf, BUF_SIZE - 1);
                buf[str_len] = 0;
                printf("message from console: %s", buf);
            }
        }
    }

    return 0;
}

// gcc 12.select_linux.c -o 12.select_linux && ./12.select_linux

select实现I/O复用服务器

12.echo_selectserver_linux.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>

#define PORT 9999
#define BUF_SIZE 100

void error_handling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char *argv[])
{
    int serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
        error_handling("socket() error");

    int opt = 1;
    if (setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt)) == -1)
        error_handling("setsockopt() error");

    socklen_t addr_size = sizeof(struct sockaddr_in);

    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, addr_size);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(PORT);

    if (bind(serv_sock, (struct sockaddr *)&serv_addr, addr_size) == -1)
        error_handling("bind() error");

    if (listen(serv_sock, 5) == -1)
        error_handling("listen() error");

    fd_set reads;
    FD_ZERO(&reads);
    FD_SET(serv_sock, &reads);
    int fd_max = serv_sock;

    while (1)
    {
        fd_set cpy_reads = reads;
        struct timeval timeout = {5, 5000};
        //调用select后,timeout的tv_sec和tv_usec将被替换为超时前剩余时间

        int fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout);
        if (fd_num == -1)
            break;
        else if (fd_num == 0)
            continue;
        for (int i = 0; i <= fd_max; i++)
        {
            if (FD_ISSET(i, &cpy_reads))
            {
                if (i == serv_sock)
                {
                    struct sockaddr_in clnt_addr;
                    int clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &addr_size);
                    FD_SET(clnt_sock, &reads);
                    if (fd_max < clnt_sock)
                        fd_max = clnt_sock;
                    printf("connected client: %d\n", clnt_sock);
                }
                else
                {
                    char buf[BUF_SIZE];
                    int str_len = read(i, buf, BUF_SIZE);
                    if (str_len == 0)
                    {
                        close(i);
                        FD_CLR(i, &reads);
                        printf("closed client: %d\n", i);
                    }
                    else
                    {
                        write(i, buf, str_len);
                    }
                }
            }
        }
    }

    close(serv_sock);

    return 0;
}

// gcc 12.echo_selectserver_linux.c -o 12.echo_selectserver_linux && ./12.echo_selectserver_linux

Windows实现

#include <winsock2.h>
//成功0,失败-1
//nfds无用
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *excepfds, const struct timeval *timeout);

typedef struct timeval {
	long tv_set;
	long tv_usec;
} TIMEVAL;

//Windows套接字句柄并非从0开始,且无规律
typedef struct fd_set {
	u_int fd_count;
	SOCKET fd_array[FD_SETSIZE];
} fd_set;
12.echo_selectserver_win.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#define PORT 9999
#define BUF_SIZE 1024

void ErrorHanding(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char *argv[])
{
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHanding("WSAStartup() error!");

    SOCKET hServSock = socket(PF_INET, SOCK_STREAM, 0);
    if (hServSock == INVALID_SOCKET)
        ErrorHanding("socket() error");

    int opt = 1;
    if (setsockopt(hServSock, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) < 0)
        ErrorHanding("setsockopt() error");

    int szAddr = sizeof(SOCKADDR_IN);

    SOCKADDR_IN servAddr;
    memset(&servAddr, 0, szAddr);
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(PORT);

    if (bind(hServSock, (SOCKADDR *)&servAddr, szAddr) == SOCKET_ERROR)
        ErrorHanding("bind() error");

    if (listen(hServSock, 5) == SOCKET_ERROR)
        ErrorHanding("listen() error");

    fd_set reads; // 记录所有文件描述符(套接字)
    FD_ZERO(&reads);
    FD_SET(hServSock, &reads); // 加入服务端套接字
    while (1)
    {
        fd_set cpyReads = reads; // 记录可读文件描述符(套接字)
        TIMEVAL timeout = {5, 5000};
        int fdNum = select(0, &cpyReads, 0, 0, &timeout);
        if (fdNum == SOCKET_ERROR)
            break;
        else if (fdNum == 0)
            continue;

        SOCKET hClntSock;
        for (int i = 0; i < reads.fd_count; i++) // 遍历所有文件描述符
        {
            if (FD_ISSET(reads.fd_array[i], &cpyReads)) // 当前文件描述符位于cpyReads集合中,可读
            {
                if (reads.fd_array[i] == hServSock) // 服务端套接字可读,表示有新的客户端连接
                {
                    SOCKADDR_IN clntAddr;
                    hClntSock = accept(hServSock, (SOCKADDR *)&clntAddr, &szAddr); // 新客户端套接字
                    FD_SET(hClntSock, &reads);                                     // 加入客户端套接字
                    printf("connected client: %d\n", hClntSock);
                }
                else // 客户端套接字可读
                {
                    char buf[BUF_SIZE];
                    int strLen = recv(reads.fd_array[i], buf, BUF_SIZE - 1, 0); // 从客户端套接字中读取缓冲区
                    if (strLen == 0)
                    {
                        closesocket(reads.fd_array[i]); // 关闭客户端套接字
                        printf("closed client: %d\n", reads.fd_array[i]);
                        FD_CLR(reads.fd_array[i], &reads); // 移除客户端套接字
                    }
                    else
                    {
                        send(reads.fd_array[i], buf, strLen, 0);
                    }
                }
            }
        }
    }

    closesocket(hServSock);

    WSACleanup();

    return 0;
}

// gcc 12.echo_selectserver_win.c -o 12.echo_selectserver_win -lws2_32 && 12.echo_selectserver_win
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值