IO多路复用实现TCP客户端与TCP并发服务器

IO多路复用实现TCP客户端与并发服务器

IO多路复用 :select函数

c 复制代码
功能:
	阻塞函数,让内核监测集合中是否有文件描述符准备就绪,若准备就绪则解除阻塞;
	当函数解除阻塞后,集合中会只剩下产生事件的文件描述符;例如:
	0号准备就绪,则集合中只剩下0号
	sfd准备就绪,则集合中只能下sfd;
	0和sfd均准备就绪,则0和sfd均存在
     
	若不将数据从触发事件的文件描述符对应的空间中取出,此时该文件描述符一直处于就绪状态。
原型:
	#include <sys/select.h>
	
	#include <sys/time.h>
	#include <sys/types.h>
	#include <unistd.h>
	
	int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数:
	int nfds:需要填充三个集合中最大的文件描述符编号+1;
	
	fd_set *readfds, fd_set *writefds,
	
	fd_set *exceptfds:读集合,写,其他集合,
	若集合不使用,填NULL; 一般只用读集合;
	
    struct timeval *timeout:设置超时时间;   
	1. 若不想设置超时时间,填NULL,则当前函数会一直阻塞,直到集合中有文件描述符准备就绪。
	2. 设置超时时间; 若时间到后依然没有事件产生,则该函数解除阻塞,且返回失败情况。
		struct timeval {
			long    tv_sec;         /* seconds */
			long    tv_usec;        /* microseconds */
		};
返回值:
	>0, 成功返回成功触发事件的文件描述符个数;
	=0, 超时了;
	失败,返回-1,更新errno;

操作集合的函数 select 函数中集合的函数

c 复制代码
void FD_CLR(int fd, fd_set *set);    //将fd从集合中删除
int  FD_ISSET(int fd, fd_set *set);  //判断fd是否在集合中
void FD_SET(int fd, fd_set *set);    //将fd添加到结合中
void FD_ZERO(fd_set *set);           //清空集合

TCP并发服务器 code

c 复制代码
#include <my_head.h>

#define PORT 8888           // 端口号的网络字节序,1024~49151
#define IP "192.168.101.63" // 本机IP,ifconfig

int keyboardEvents(void);
int client_connect(int sfd, struct sockaddr_in *pCin, fd_set *preadfds, int *pmaxfd);
int deal_client_message(int newfd, struct sockaddr_in pCin[], fd_set *preadfds, int *pmaxfd);

int main(int argc, const char *argv[])
{
    // 创建流式套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
    printf("socket create success  sfd=%d\n", sfd);

    // 允许端口快速复用
    int reuse = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
    {
        ERR_MSG("setsockopt");
        return -1;
    }
    printf("允许端口快速复用成功\n");

    // 填充地址信息结构体给bind函数绑定使用;
    // 真实的地址信息结构体根据地址族指定,AF_INET:man 7 ip
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;            // 必须填AF_INET;
    sin.sin_port = htons(PORT);          // 端口号的网络字节序,1024~49151
    sin.sin_addr.s_addr = inet_addr(IP); // 本机IP的网络字节序,ifconfig

    // 绑定服务器的地址信息---》必须绑定
    if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("bind");
        return -1;
    }
    printf("bind success\n");

    // 将套接字转换成被动监听状态
    if (listen(sfd, 128) < 0)
    {
        ERR_MSG("listen");
        return -1;
    }
    printf("listen success\n");

    // 创建一个读集合
    fd_set readfds, tempfds;

    // 由于fd_set是一个结构体,里面有一个整形数组,若不初始化
    // 则可能随机到合法的但是不需要监测的文件描述符
    // 所以必须初始化
    FD_ZERO(&readfds); // 清空集合

    // 将需要的文件描述符添加到集合中
    FD_SET(0, &readfds);
    FD_SET(sfd, &readfds);

    int maxfd = sfd; // 存储集合中最大的文件描述符

    struct sockaddr_in saveCin[1024 - 3]; // 将客户端的信息另存

    char buf[128] = "";
    ssize_t res = 0;
    int newfd = -1;
    int s_res = 0;

    while (1)
    {
        tempfds = readfds;

        // 调用IO多路复用函数,阻塞监测集合
        s_res = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
        if (s_res < 0)
        {
            ERR_MSG("select");
            return -1;
        }
        else if (0 == s_res)
        {
            printf("time out...\n");
            break;
        }
        printf("__%d__\n", __LINE__);

        // 能运行到当前位置,则代表集合中有文件描述符准备就绪
        // 判断哪个文件描述符准备就绪,走对应处理函数即可

        // 当函数解除阻塞后,集合中会只剩下产生事件的文件描述符;例如:
        // 0号准备就绪,则集合中只剩下0号
        // sfd准备就绪,则集合中只能下sfd;
        // 0和sfd均准备就绪,则0和sfd均存在

        // 所以只需要遍历集合中剩下哪个文件描述符,就代表哪个文件描述符准备就绪
        for (int i = 0; i <= maxfd; i++)
        {
            if (FD_ISSET(i, &tempfds) == 0) // 若i所代表的文件描述符不存在,则遍历下一个文件描述符
                continue;

            // 能运行到当前位置,说明i所代表的文件描述符在集合中。
            // 判断i所代表的文件描述符是什么(0, sfd , 其他newfd),走对应处理函数即可
            if (0 == i) // 说明0在集合中
            {
                printf("触发键盘输入事件\n");
                keyboardEvents();
            }
            else if (sfd == i) // 说明sfd在集合中
            {
                printf("触发客户端连接事件\n");
                client_connect(sfd, saveCin, &readfds, &maxfd);
            }
            else
            {
                printf("触发客户端交互事件\n");
                deal_client_message(i, saveCin, &readfds, &maxfd);
            }
        }
    }

    // 关闭套接字
    close(sfd);
    return 0;
}

int deal_client_message(int newfd, struct sockaddr_in pCin[], fd_set *preadfds, int *pmaxfd)
{
    ssize_t res = -1;
    char buf[128] = "";
    // 接收数据
    res = recv(newfd, buf, sizeof(buf), 0);
    if (res < 0)
    {
        ERR_MSG("recv");
        return -1;
    }
    else if (0 == res)
    {
        printf("[%s:%d] newfd=%d 客户端下线__%d__\n",
               inet_ntoa(pCin[newfd - 3].sin_addr), ntohs(pCin[newfd - 3].sin_port), newfd, __LINE__);

        close(newfd);            // 关闭文件描述符
        FD_CLR(newfd, preadfds); // 剔除该文件描述符

        // 更新maxfd, 从当前最大的文件描述符开始判断是否在集合中
        // 若在,则该文件描述符就是最大文件描述符
        for (; *pmaxfd >= 0; *pmaxfd--)
            if (FD_ISSET(*pmaxfd, preadfds))
                break;

        // while(!FD_ISSET(*pmaxfd, preadfds) && *pmaxfd-->=0);
        return 0;
    }

    printf("[%s:%d] newfd=%d : %s __%d__\n",
           inet_ntoa(pCin[newfd - 3].sin_addr), ntohs(pCin[newfd - 3].sin_port), newfd, buf, __LINE__);

    // 发送数据
    strcat(buf, "*_*");
    if (send(newfd, buf, sizeof(buf), 0) < 0)
    {
        ERR_MSG("send");
        return -1;
    }
    printf("send success\n");

    return 0;
}

int client_connect(int sfd, struct sockaddr_in *pCin, fd_set *preadfds, int *pmaxfd)
{
    struct sockaddr_in cin; // 存储客户端的地址信息
    socklen_t addrlen = sizeof(cin);
    int newfd;

    newfd = accept(sfd, (struct sockaddr *)&cin, &addrlen);
    if (newfd < 0)
    {
        ERR_MSG("accept");
        return -1;
    }
    printf("[%s:%d] newfd=%d 客户端连接成功__%d__\n",
           inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);

    pCin[newfd - 3] = cin; // 将客户端信息另存

    FD_SET(newfd, preadfds);                     // 将newfd添加到集合中
    *pmaxfd = *pmaxfd > newfd ? *pmaxfd : newfd; // 更新maxfd

    return 0;
}

int keyboardEvents(void)
{
    char buf[128] = "";
    fgets(buf, sizeof(buf), stdin);
    buf[strlen(buf) - 1] = 0;

    printf(":%s\n", buf);
    return 0;
}

TCP 客户端 code

c 复制代码
#include <my_head.h>

#define SERVER_IP "192.168.101.63"
#define SERVER_PORT 8888

int main(int argc, const char *argv[])
{
    // 创建流式套接字
    int client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }

    // 可以不用绑定

    // 连接connect
    // 填充信息
    struct sockaddr_in server_info;
    server_info.sin_addr.s_addr = inet_addr(SERVER_IP);
    server_info.sin_family = AF_INET;
    server_info.sin_port = htons(SERVER_PORT);

    socklen_t server_len = sizeof(server_info);
    if (connect(client_fd, (struct sockaddr *)&server_info, server_len) < 0)
    {
        ERR_MSG("connect");
        return -1;
    }

    // 创建一个读集合
    fd_set read_sets, temp_sets;
    // 将读集合清空
    FD_ZERO(&read_sets);
    // 将需要用到的文件描述符添加到读集合中
    FD_SET(0, &read_sets);
    FD_SET(client_fd, &read_sets);
    // 设一个最大文件描述符变量
    int max_fd = client_fd;

    int res_select = -1;
    char buff[128];
    while (1)
    {
        // 使用工具人
        temp_sets = read_sets;
        // 调用IO多路复用函数
        res_select = select(max_fd + 1, &temp_sets, NULL, NULL, NULL);
        if (res_select < 0)
        {
            ERR_MSG("select");
            return -1;
        }
        else if (0 == res_select)
        {
            printf("超时中。。。\n");
            break;
        }

        // printf("__%d__\n", __LINE__);

        for (int i = 0; i <= max_fd; i++)
        {
            if (!FD_ISSET(i, &temp_sets))
                continue;
            if (0 == i)
            {
                printf("触发读事件\n");
                bzero(buff, sizeof(buff));
                fgets(buff, sizeof(buff), stdin);
                buff[strlen(buff) - 1] = 0;
                printf("输入事件完成\n");
                // 输入完成后,需要对服务器进行发送消息
                send(client_fd, buff, sizeof(buff), 0);
            }
        }
    }

    return 0;
}
相关推荐
Sam902929 分钟前
828华为云征文|Flexus云服务器X实例部署宝塔运维面板
运维·服务器·华为云
亿林安全1 小时前
等保测评中的常见误区及应对策略
网络·安全·web安全
亿林网络安全事业部1 小时前
等保测评:企业如何构建安全的网络架构
网络·安全·架构
元素之窗1 小时前
如何在 CentOS 中管理用户、组和服务状态
linux·运维·centos
咩咩大主教2 小时前
Linux下的简单TCP客户端和服务器
linux·服务器·c语言·开发语言·c++·tcp/ip·网络编程
你不要在理我了2 小时前
weblogic CVE-2018-2894 靶场攻略
运维·服务器·安全
小理想!2 小时前
如何在Linux Centos7系统中挂载群晖共享文件夹
linux·运维·服务器
日晨难再2 小时前
Linux:终端(terminal)与终端管理器(agetty)
linux·运维·服务器
求学者1.02 小时前
网络编程问题解答
网络
MAMA66812 小时前
一个简单的基于C语言的HTTP代理服务器的案例
c语言·网络·http