TCP并发服务器

1.阻塞IO

CPU占用率低,等待资源时将任务挂起,不占用CPU资源,等到拿到资源后继续向下执行

2.非阻塞IO

能够让任务不阻塞,效率低,因为没有数据时,CPU一直空转

fcntl(fd,F_GETFL);获得文件描述符的属性

flags |= O_NONBLOCK;在现有属性中加入非阻塞属性

fcntl(fd,F_SETFL,flags);将新属性设置回文件描述符

cpp 复制代码
#include "head.h"

int main(void)
{
    int fd = 0;
    char tmpbuff[4096] = {0};
    int flags;
    ssize_t nsize = 0;
    char *pret = NULL;

    mkfifo("/tmp/myfifo", 0777);
    fd = open("/tmp/myfifo", O_RDONLY);
    if (-1 == fd)
    {
        perror("fail to open");
        return -1;
    }

    /* 获得fd文件描述符的属性 */
    flags = fcntl(fd, F_GETFL);

    /* 在现有属性中加入非阻塞属性 */
    flags |= O_NONBLOCK;

    /* 将新属性设置回fd文件描述符 */
    fcntl(fd, F_SETFL, flags);

    flags = fcntl(0, F_GETFL);
    flags |= O_NONBLOCK;
    fcntl(0, F_SETFL, flags);

    while (1)
    {
        memset(tmpbuff, 0, sizeof(tmpbuff));
        nsize = read(fd, tmpbuff, sizeof(tmpbuff));
        if (nsize > 0)
        {
            printf("FIFO:%s\n", tmpbuff);
        }
        
        memset(tmpbuff, 0, sizeof(tmpbuff));
        pret = gets(tmpbuff);
        if (NULL != pret)
        {
            printf("STDIN:%s\n", tmpbuff);
        }  
    }
    
    close(fd);

    return 0;
}

3.异步IO

将一个文件描述符设定为异步IO,当IO有事件发生时,内核会向用户层发送SIGIO信号提醒用户层处理事件

signal(SIGIO,handle);

flag = fcntl(fd,F_GETFL);

flag |= O_ASYNC;//将fd设置为异步IO(文件描述符,发生可读事件时,会发送信号通知)

fcntl(fd,F_SETFL,flag);//把异步IO事件的接收进程设置为当前进程,通知给当前进程

fcntl(fd,F_SETOWN,getpid());

cpp 复制代码
#include "head.h"

int fd = 0;

void handler(int signo)
{
    char tmpbuff[4096] = {0};

    memset(tmpbuff, 0, sizeof(tmpbuff));
    read(fd, tmpbuff, sizeof(tmpbuff));
    printf("RECV:%s\n", tmpbuff);

    return;
}

int main(void)
{
    char tmpbuff[4096] = {0};
    int flags;

    signal(SIGIO, handler);

    mkfifo("/tmp/myfifo", 0777);
    fd = open("/tmp/myfifo", O_RDONLY);
    if (-1 == fd)
    {
        perror("fail to open");
        return -1;
    }

    flags = fcntl(fd, F_GETFL);
    flags |= O_ASYNC;
    //将fd设置为异步IO(文件描述符发生可以读的事件,会发送信号通知)
    fcntl(fd, F_SETFL, flags);
    //通知给当前进程
    fcntl(fd, F_SETOWN, getpid());

    while (1)
    {
        memset(tmpbuff, 0, sizeof(tmpbuff));
        gets(tmpbuff);
        printf("STDIN:%s\n", tmpbuff);
    }
    
    close(fd);

    return 0;
}

4.多路复用IO

select

监听文件描述符集合,将所有要监听的事件加入集合中,使用select监听所有事件,当集合中有事件发生, select不再阻塞,同时select会将产生事件的文件描述符留在集合中,而把没有产生事件的文件描述符从集合中踢出,所以留在集合中的文件描述即为产生事件的文件描述符,对其处理即可

注意:因为select会把没有产生事件的文件描述符从集合中踢出,所以要用一个临时变量tmp来使用select

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 功能: 监听文件描述符是否有事件发生 参数: nfds:最大文件描述符的值 + 1 readfds:读文件描述符集合 writefds:写文件描述符集合 exceptfds:异常文件描述符集合 timeout:超时时间 返回值: 成功返回产生事件的文件描述符个数 失败返回-1 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); 功能:将文件描述符集合清0 |

练习:

cpp 复制代码
#include "head.h"

int CreateTcpConnection(const char *pip, int port)
{
    int sockfd = 0;
    int ret = 0;
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(port);
    seraddr.sin_addr.s_addr = inet_addr(SER_IP);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        return -1;
    }

    ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if (-1 == ret)
    {
        return -1;
    }

    return sockfd;
}

int HandleConnection(int sockfd)
{
    char tmpbuff[4096] = {0};
    static int cnt = 0;
    ssize_t nsize = 0;

    sprintf(tmpbuff, "hello world --- %d", cnt);
    nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
    if (-1 == nsize)
    {
        return -1;
    }
    cnt++;

    memset(tmpbuff, 0, sizeof(tmpbuff));
    nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);  
    if (-1 == nsize)
    {
        return -1;
    }
    else if (0 == nsize)
    {
        return 0;
    }
    
    printf("RECV:%s\n", tmpbuff);

    return nsize;
}

int main(void)
{
    int sockfd = 0;
    int ret = 0;

    sockfd = CreateTcpConnection(SER_IP, SER_PORT);
    if (-1 == sockfd)
    {
        printf("连接服务器异常\n");
        return -1;
    }

    while (1)
    {
        ret = HandleConnection(sockfd);
        if (-1 == ret)
        {
            printf("连接出错!\n");
            break;
        }
        else if (0 == ret)
        {
            printf("连接关闭\n");
            break;
        }

        sleep(1);
    }
    
    close(sockfd);

    return 0;
}

server.c

cpp 复制代码
#include "head.h"

int CreateListenSocket(const char *pip, int port)
{
    int sockfd = 0;
    int ret = 0;
    struct sockaddr_in seraddr;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        return -1;
    }

    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(port);
    seraddr.sin_addr.s_addr = inet_addr(pip);
    ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if (-1 == ret)
    {
        return -1;
    }

    ret = listen(sockfd, 10);
    if (-1 == ret)
    {
        return -1;
    }

    return sockfd;
}

int HandleConnection(int confd)
{
    char tmpbuff[4096] = {0};
    ssize_t nsize = 0;

    nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
    if (-1 == nsize)
    {
        return -1;
    }
    else if (0 == nsize)
    {
        return 0;
    }

    printf("RECV:%s\n", tmpbuff);

    sprintf(tmpbuff, "%s --- echo", tmpbuff);

    nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
    if (-1 == nsize)
    {
        return -1;
    }

    return nsize;
}

int main(void)
{
    int sockfd = 0;
    int confd = 0;
    int ret = 0;
    fd_set rdfds;
    fd_set tmpfds;
    int nready = 0;
    int maxfd = 0;
    int i = 0;

    //创建监听套接字
    sockfd = CreateListenSocket(SER_IP, SER_PORT);
    if (-1 == sockfd)
    {
        printf("创建监听套接字失败\n");
        return -1;
    }

    //将sockfd加入监听集合中
    FD_ZERO(&rdfds);
    FD_SET(sockfd, &rdfds);
    maxfd = sockfd;

    while (1)
    {
        //开始监听
        tmpfds = rdfds;
        nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
        if (-1 == nready)
        {
            perror("fail to select");
            return -1;
        }

        //如果sockfd产生事件,处理新的连接请求,并将新的文件描述符加入集合,下次一起监听
        if (FD_ISSET(sockfd, &tmpfds))
        {
            confd = accept(sockfd, NULL, NULL);
            if (-1 == confd)
            {
                FD_CLR(sockfd, &rdfds);
                close(sockfd);
                continue;
            }   

            maxfd = maxfd > confd ? maxfd : confd;
            FD_SET(confd, &rdfds);
        }

        //遍历所有已经连接的客户端中是否有事件发生
        for (i = sockfd+1; i <= maxfd; i++)
        {
            if (FD_ISSET(i, &tmpfds))
            {
                ret = HandleConnection(i);
                if (-1 == ret)
                {
                    printf("连接异常\n");
                    FD_CLR(i, &rdfds);
                    close(i);
                    continue;
                }
                else if (0 == ret)
                {
                    printf("连接断开\n");
                    FD_CLR(i, &rdfds);
                    close(i);
                    continue;
                }
            }
        }
    }
    
    close(sockfd);

    return 0;
}
相关推荐
a_安徒生11 分钟前
linux安装TDengine
linux·数据库·tdengine
追风赶月、18 分钟前
【Linux】线程概念与线程控制
linux·运维·服务器
小字节,大梦想20 分钟前
【Linux】重定向,dup
linux
CP-DD31 分钟前
Docker 容器化开发 应用
运维·docker·容器
blessing。。1 小时前
I2C学习
linux·单片机·嵌入式硬件·嵌入式
2202_754421542 小时前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
努力的悟空2 小时前
国土变更调查拓扑错误自动化修复工具的研究
运维·自动化
运维&陈同学2 小时前
【zookeeper03】消息队列与微服务之zookeeper集群部署
linux·微服务·zookeeper·云原生·消息队列·云计算·java-zookeeper
旦沐已成舟3 小时前
DevOps-Jenkins-新手入门级
服务器
周末不下雨3 小时前
win11+ubuntu22.04双系统 | 联想 24 y7000p | ubuntu 22.04 | 把ubuntu系统装到1T的移动固态硬盘上!!!
linux·运维·ubuntu