IO多路复用之select的用法

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
  (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
  (2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
  (3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
  (4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
  (5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
  与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

服务端:

#include <iostream>
#include <WinSock2.h>
#include <ws2tcpip.h>
using namespace std;

int main()
{
    DWORD dwVersionRequest = MAKEWORD(2, 2);
    WSADATA wsadata;
    if (0 != WSAStartup(dwVersionRequest, &wsadata))
    {
        cout << "WSAStartup failed" << endl;
    }

    SOCKADDR_IN serv_addr;
    serv_addr.sin_family = AF_INET; //IPV4
    serv_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //①#define s_addr  S_un.S_addr ②本机所有ip
    serv_addr.sin_port = htons(8080); //端口号

    SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == INVALID_SOCKET)
    {
        cout << "socket failed errorCode = " << WSAGetLastError() << endl;
    }

    if (bind(sockfd, (SOCKADDR*)&serv_addr, sizeof(SOCKADDR)) == SOCKET_ERROR)
    {
        cout << "bind failed errorCode = " << WSAGetLastError() << endl;
        WSACleanup();
    }

    listen(sockfd, 5);

    //select准备
    fd_set readfds;
    fd_set readfds_backup; //因为select后会修改readfds,所以再定义一个readfds_backup保存accept的套接字
    struct timeval timeout;
    int maxfd = sockfd;
    FD_ZERO(&readfds);  //清空集合
    FD_ZERO(&readfds_backup);
    FD_SET(sockfd, &readfds_backup);  //套接字放到集合里

    //accept准备
    SOCKET sock_accept;
    SOCKADDR_IN accept_addr;
    socklen_t client_addr_len;
    char client_ip_str[INET_ADDRSTRLEN];

    //收发数据数组
    char send_buf[100];
    char recv_buf[100];
    int send_len = 0;
    int recv_len = 0;

    while (1)
    {
        readfds = readfds_backup;
        timeout.tv_sec = 50;
        timeout.tv_usec = 0;

        int selectRet = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
        if (selectRet < 0)
        {
            cout << "select failed" << endl;
        }
        else if (selectRet == 0)
        {
            cout << "select timeout" << endl;
            continue;
        }

        //遍历select后的readfds
        for (int i = 0; i <= maxfd; i++)
        {
            if (FD_ISSET(i, &readfds))
            {
                //分两种情况,1.有新的客户端连接,就去accept 2.已连接的客户端发信息过来
                if (i == sockfd)
                {
                    client_addr_len = sizeof(accept_addr);
                    sock_accept = accept(sockfd, (struct sockaddr*)&accept_addr, &client_addr_len);
                    inet_ntop(AF_INET, &(accept_addr.sin_addr), client_ip_str, sizeof(client_ip_str));
                    printf("----------------------------------------------\n");
                    printf("socket=%d, client ip=%s, port=%d 连接上来啦\n", sock_accept, client_ip_str, ntohs(accept_addr.sin_port));
                    FD_SET(sock_accept, &readfds_backup);
                    if (maxfd < sock_accept)
                    {
                        maxfd = sock_accept;
                    }
                }
                else
                {
                    //这里就是已连接的Client发送信息进来啦
                    memset(recv_buf, 0, sizeof(recv_buf));
                    recv_len = recv(i, recv_buf, sizeof(recv_buf), 0);
                    if (recv_len < 0)
                    {
                        cout << "recv failed\n";
                    }
                    else
                    {
                        printf("socket= %d 对你说:%s\n", i, recv_buf);
                    }
                    send_len = send(i, recv_buf, recv_len, 0);
                    if (send_len < 0)
                    {
                        cout << "send failed\n";
                    }
                }
            }
        }
    }
    WSACleanup();
    system("pause");
    return 0;
}

客户端:

#include <iostream>
#include <WinSock2.h>
#include <ws2tcpip.h>
#include <thread>
using namespace std;

int main()
{
    DWORD dwVersionRequest = MAKEWORD(2, 2);
    WSADATA wsadata;
    if (0 != WSAStartup(dwVersionRequest, &wsadata))
    {
        cout << "WSAStartup failed" << endl;
    }

    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(8080);

    SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);

    while (1)
    {
        if (connect(sockfd, (struct sockaddr*)& serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
        {
            cout << "connect failed continue " << "errorCode = " << WSAGetLastError() << endl;;
            Sleep(1000);
            continue;
        }
        cout << "connect success";
        break;
    }

    //定义长度变量
    int send_len = 0;
    int recv_len = 0;
    //定义发送缓冲区和接受缓冲区
    char send_buf[100];
    char recv_buf[100];

    thread recvThread = thread([&]
    {
        while (1)
        {
            recv_len = recv(sockfd, recv_buf, 100, 0);
            if (recv_len < 0) {
                cout << "接受信息失败" << endl;
                break;
            }
            else {
                cout << "服务端对你说:" << recv_buf << endl;
            }
        }
    });

    while (1)
    {
        cout << "请输入发给服务端的信息:\n";
        cin >> send_buf;
        send_len = send(sockfd, send_buf, 100, 0);
        if (send_len < 0) {
            cout << "发送失败!" << endl;
            break;
        }
    }
    WSACleanup();
    system("pause");
    return 0;
}

运行结果截图:

1.

2.

3.

4.

------------------------------------------------end-----------------------------------------------------------------
备注:写的不够细节,例如:select函数的用法,使用时需注意的地方,实现原理。以及与poll,epoll的区别,后续会进行补充

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值