Linux高并发服务器开发(九)Tcp状态转移和IO多路复用

发布于:2024-07-03 ⋅ 阅读:(16) ⋅ 点赞:(0)


0 包裹函数

用于创建socket,绑定端口ip和监听时,添加了错误时报错的包裹函数

warp.h

#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int tcp4bind(short port,const char *IP);
#endif

wrap.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

void perr_exit(const char *s)
{
	perror(s);
	exit(-1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int n;

again:
	if ((n = accept(fd, sa, salenptr)) < 0) {
		if ((errno == ECONNABORTED) || (errno == EINTR))
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}

int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = bind(fd, sa, salen)) < 0)
		perr_exit("bind error");

    return n;
}

int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = connect(fd, sa, salen)) < 0)
		perr_exit("connect error");

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

	if ((n = listen(fd, backlog)) < 0)
		perr_exit("listen error");

    return n;
}

int Socket(int family, int type, int protocol)
{
	int n;

	if ((n = socket(family, type, protocol)) < 0)
		perr_exit("socket error");

	return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ( (n = read(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ( (n = write(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

int Close(int fd)
{
    int n;
	if ((n = close(fd)) == -1)
		perr_exit("close error");

    return n;
}

/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{
	size_t  nleft;              //usigned int 剩余未读取的字节数
	ssize_t nread;              //int 实际读到的字节数
	char   *ptr;

	ptr = vptr;
	nleft = n;

	while (nleft > 0) {
		if ((nread = read(fd, ptr, nleft)) < 0) {
			if (errno == EINTR)
				nread = 0;
			else
				return -1;
		} else if (nread == 0)
			break;

		nleft -= nread;
		ptr += nread;
	}
	return n - nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	const char *ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}

		nleft -= nwritten;
		ptr += nwritten;
	}
	return n;
}

static ssize_t my_read(int fd, char *ptr)
{
	static int read_cnt;
	static char *read_ptr;
	static char read_buf[100];

	if (read_cnt <= 0) {
again:
		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
			if (errno == EINTR)
				goto again;
			return -1;
		} else if (read_cnt == 0)
			return 0;
		read_ptr = read_buf;
	}
	read_cnt--;
	*ptr = *read_ptr++;

	return 1;
}

ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t n, rc;
	char    c, *ptr;

	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
		if ( (rc = my_read(fd, &c)) == 1) {
			*ptr++ = c;
			if (c  == '\n')
				break;
		} else if (rc == 0) {
			*ptr = 0;
			return n - 1;
		} else
			return -1;
	}
	*ptr  = 0;

	return n;
}

int tcp4bind(short port,const char *IP)
{
    struct sockaddr_in serv_addr;
    int lfd = Socket(AF_INET,SOCK_STREAM,0);
    bzero(&serv_addr,sizeof(serv_addr));
    if(IP == NULL){
        //如果这样使用 0.0.0.0,任意ip将可以连接
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    }else{
        if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){
            perror(IP);//转换失败
            exit(1);
        }
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port   = htons(port);
    Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    return lfd;
}


1 多进程服务器

流程

因为read 和 write都是阻塞的,所以如果多个客户端进行连接时,会阻塞。
在这里插入图片描述
理念就是连接给一个专用的进程,然后这个进程来分配其他进程进行读写的操作

流程
创建套接字
绑定
监听

while(1)
{
提取连接
fork创建子进程
子进程中 关闭lfd 服务客户端(连接)
父进程关闭 cfd(读写),回收子进程资源

}

代码

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include "wrap.h"
#include<stdlib.h>
#include <signal.h>
#include<sys/wait.h>
#include<sys/types.h>

void *free_process(int signum)
{
    pid_t pid;

    while(1)
    {
        pid = waitpid(-1,NULL,WNOHANG);
        if(pid <= 0)    // 没有要回收的子进程
        {
            break;
        }
        else
        {
            printf("child pid =%d\n",pid);
        }
    }
 
}

int main()
{
    // 阻塞信号集,在子进程创建之前
    // 在创建子进程之后添加信号
    sigset_t set;
    sigemptyset(&set);

    sigaddset(&set, SIGCHLD);
    sigprocmask(SIG_BLOCK, &set, NULL);

    // 创建套接字,绑定 链接socket
    int lfd = tcp4bind(8000,NULL);
    // 监听
    Listen(lfd, 128);
    // 提取
    // 回射
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    while(1)
    {
        // 读取socket
        int cfd = Accept(lfd, (struct sockaddr*)&cliaddr,&len);
        char ip[16] = "";
        printf("new client ip= %s port = %d\n",
            inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),
            ntohs(cliaddr.sin_port));
        // fork 创建子进程
        pid_t pid;
        pid = fork();
        if(pid<0)
        {
            perror("");
            exit(0);
        }
        else if(pid == 0) // 子进程
        {
            // 关闭lfd
            close(lfd);
            while(1)
            {

            char buf[1024];
            int n = read(cfd,buf,sizeof(buf));
            if(n < 0)
            {
                perror("");
                close(cfd);
                exit(0);
            }
            else if(n == 0) // 对方关闭
            {
                printf("client close\n");
                close(cfd);
                exit(0);

            }
            else
            {
                printf("%s", buf);
                write(cfd,buf,n);
                // exit(0);

            }
            }


        }
        else
        {
            close(cfd); 
            // 回收
            // 注册信号回调
            struct sigaction act;
            act.sa_flags = 0;
            act.sa_handler = free_process;
            sigemptyset(&act.sa_mask);
            sigaction(SIGCHLD,&act,NULL);
            sigprocmask(SIG_UNBLOCK, &set, NULL);

        }

    }

    // 回收




    return 0;
}

2 多线程服务器

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include "wrap.h"
#include<stdlib.h>
#include <signal.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<pthread.h>

typedef struct c_info
{
    int cfd;
    struct sockaddr_in cliaddr;


}CINFO;

int main(int argc, char *argv[])
{


    if(argc < 2)
    {
        printf("argc < 2???\n ./a.out 8000\n");

    }

    // 初始化线程属性
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    short port = atoi(argv[1]);
    int lfd = tcp4bind(port, NULL);
    Listen(lfd, 128);
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    CINFO *info;
    while(1)
    {
        int cfd = Accept(lfd,(struct sockaddr*)&cliaddr,&len);
        char ip[16] = "";
        printf("new client ip = %s port = %d\n", 
            inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),
            ntohs(cliaddr.sin_port));
        pthread_t pthid;

        info = malloc(sizeof(CINFO));
        info->cfd = cfd;
        info->cliaddr = cliaddr;
        
        pthread_create(&pthid,NULL,client_fun,&info);
    }
    
    return 0;
}

void * client_fun(void *arg)
{
    CINFO *info = (CINFO *)arg;
    char ip[16] = "";
    printf("new client ip = %s port = %d\n", 
        inet_ntop(AF_INET,&(info->cliaddr.sin_addr.s_addr),ip,16),
        ntohs(info->cliaddr.sin_port));
    while(1)
    {
        char buf[1024] = "";
        int count = 0;
        count = read(info->cfd, buf, sizeof(buf));
        if(count < 0)
        {
            perror("");
            break;
        }
        else if(count == 0)
        {
            printf("client close");
            break;
        }
        else
        {   
            printf("%s", buf);
            write(info->cfd, buf, count);
        }
    }
    close(info->cfd);
    free(info);

}

3 TCP状态转移

在这里插入图片描述
TIME_WAIT -> CLOSE 2MML

半关闭

处于FIN_WAIT2时,处于半关闭状态,此时只能读数据不能收数据

手动半关闭
在这里插入图片描述

心跳包

每隔一段时间服务器向客户端发送一个包,客户端需要在一定时间内返回一个规定好的包,用于测试连接是否还存在,如果对方没有回复,则断开连接

在这里插入图片描述

4 端口复用

端口重新启用
使用 setsockopt设置端口重新使用
放在绑定之前
在这里插入图片描述

5 IO多路复用技术

高并发服务器

1.阻塞等待
一个进程 服务一个客户端
消耗资源

2.非阻塞忙轮询
重复查看 进程是否有需求,是否有新连接

3.多路io
通过监听多个文件描述符,监听文件描述符是否还在读写
内核有三种方式
select:windows使用 select select跨平台
poll: 少用
epoll: linux下使用

内核监听多个文件描述符的属性(读写缓冲区)变化,如果某个文件描述符的读缓冲区变化了,这个时候就是可以读了,将这个事件告知应用层

在这里插入图片描述在这里插入图片描述

6 select

使用select监听文件描述符
在这里插入图片描述
注意:变化的文件描述符,会存放在监听的集合中,未变化的文件描述符会被删除

代码

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"


#define PORT 8888
int main(int argc, char *argv[])
{
    // 创建套接字,绑定
    int lfd = tcp4bind(PORT,NULL);
    // 监听
    Listen(lfd, 128);
    int maxfd = lfd;    // 最大的文件描述符
    fd_set oldset,rset;
    // 清空集合
    FD_ZERO(&oldset);
    FD_ZERO(&rset);
    // 将lfd加入到oldset集合中
    FD_SET(lfd, &oldset);

    // while
    while(1)
    {
        rset = oldset;  // 将oldset赋值给需要监听的集合rset
        int n = select(maxfd + 1,&rset,NULL,NULL,NULL);
        
        if(n<0)
        {
            perror("");
            break;
        }
        else if(n==0)
        {
            continue;
        }
        else    // n>0  监听到了文件描述符
        {
            // lfd变化, 则进行提取
            if(FD_ISSET(lfd,&rset))
            {
                struct sockaddr_in cliaddr;
                socklen_t len = sizeof(cliaddr);
                char ip[16] = "";
                // 提取新的连接
                int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, & len);
                printf("new client ip = %s, port = %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr,ip,16),
                        ntohs(cliaddr.sin_port));
                // 将cfd添加到oldset集合中,下次进行监听
                FD_SET(cfd,&oldset);

                // 更新maxfd
                if(cfd > maxfd)
                    maxfd = cfd;
                // 如果只有lfd变化,continue
                if(--n == 0)
                    continue;
                
            }

            // cfd  遍历cfd,看lfd之后的文件描述符是否在rset,如果在则cfd变化
            for(int i = lfd+1;i<=maxfd;i++)
            {
                // 如果i文件描述符在rset集合中
                if(FD_ISSET(i,&rset))
                {
                    char buf[1024] = "";
                    int ret = Read(i,buf,sizeof(buf));
                    if(ret < 0) //出错,将cfd关闭,从oldset删除cfd
                    {
                        perror("");
                        close(i);
                        FD_CLR(i,&oldset);
                    }
                    else if(ret == 0)
                    {
                        printf("client close\n");
                        close(i);
                        FD_CLR(i,&oldset);
                    }
                    else
                    {
                        printf("%s\n", buf);
                        Write(i,buf,ret);
                    }
                }
            }
        }
    }
    // select监听

    return 0;
}

总结

优缺点
优点:跨平台
缺点:文件描述符1024的限制 由于FD_SETSIZE的限制
只是返回变化的文件描述符的个数,具体哪个变化需要遍历
每次都需要将需要监听的文件描述集合由应用层拷贝到内核

7 POLL

在这里插入图片描述

API

在这里插入图片描述

代码

//IO多路复用技术poll函数的使用 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include "wrap.h"

int main()
{
	int i;
	int n;
	int lfd;
	int cfd;
	int ret;
	int nready;
	int maxfd;
	char buf[1024];
	socklen_t len;
	int sockfd;
	fd_set tmpfds, rdfds;
	struct sockaddr_in svraddr, cliaddr;
	
	//创建socket
	lfd = Socket(AF_INET, SOCK_STREAM, 0);

	//允许端口复用
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));

	//绑定bind
	svraddr.sin_family = AF_INET;
	svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	svraddr.sin_port = htons(8888);
	ret = Bind(lfd, (struct sockaddr *)&svraddr, sizeof(struct sockaddr_in));

	//监听listen
	ret = Listen(lfd, 128);

	struct pollfd client[1024];
	for(i=0; i<1024; i++)
	{
		client[i].fd = -1;
	}		

	//将监听文件描述符委托给内核监控----监控读事件
	client[0].fd = lfd;
	client[0].events = POLLIN;

	maxfd = 0; //maxfd表示内核监控的范围

	while(1)
	{
		nready = poll(client, maxfd+1, -1);
		if(nready<0)
		{
			perror("poll error");
			exit(1);
		}
		
		//有客户端连接请求
		if(client[0].fd==lfd && (client[0].revents & POLLIN))
		{
			cfd = Accept(lfd, NULL, NULL);

			//寻找client数组中的可用位置
			for(i=1; i<1024; i++)
			{
				if(client[i].fd==-1)
				{
					client[i].fd = cfd;
					client[i].events = POLLIN;
					break;
				}
			}

			//若没有可用位置, 则关闭连接
			if(i==1024)
			{
				Close(cfd);
				continue;
			}

			if(maxfd<i)
			{
				maxfd = i;
			}
			
			if(--nready==0)
			{
				continue;
			}
		}

		//下面是有数据到来的情况
		for(i=1; i<=maxfd; i++)
		{
			//若fd为-1, 表示连接已经关闭或者没有连接
			if(client[i].fd==-1)	
			{
				continue;
			}
			
			sockfd = client[i].fd;
			memset(buf, 0x00, sizeof(buf));
			n = Read(sockfd, buf, sizeof(buf));
			if(n<=0)
			{
				printf("read error or client closed,n==[%d]\n", n);
				Close(sockfd);
				client[i].fd = -1; //fd为-1,表示不再让内核监控
			}
			else
			{
				printf("read over,n==[%d],buf==[%s]\n", n, buf);
				write(sockfd, buf, n);
			}

			if(--nready==0)
			{
				break;
			}
		}

	}


	Close(lfd);
	return 0;
}

poll相对select的优缺点

优点:相对于select没有最大1024文件描述符限制
请求和返回是分离

缺点:
每次都需要将需要监听的文件描述符从应用层拷贝到内核
每次都需要将数组中的元素遍历一遍才知道哪个变化
大量并发、少量活跃率低

8 epoll(重点)

1.创建红黑树
2.将监听的文件描述符上树
3.监听

特点:
没有文件描述符1024的限制
以后每次监听都不需要在此将需要监听的文件描述符拷贝在内核
返回的是已经变化的文件描述符,不需要遍历树

工作原理:
在这里插入图片描述

在这里插入图片描述

API

1.创建红黑树
在这里插入图片描述

2.上树 下树 修改节点

在这里插入图片描述
在这里插入图片描述
3. 监听

在这里插入图片描述

监听管道

在这里插入图片描述

代码

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"
#include<sys/epoll.h>

int main()
{

    int fd[2];
    pipe(fd);

    pid_t pid;
    pid = fork();
    if(pid < 0)
        perror("");
    else if(pid == 0)
    {
        close(fd[0]);
        char buf[5];
        char ch = 'a';
        while(1)
        {
            sleep(3);
            memset(buf,ch,sizeof(buf));
            write(fd[1],buf,5);
        }


    }
    else
    {
        close(fd[1]);
        // 创建树
        int epfd = epoll_create(1);
        struct epoll_event ev, evs[1];
        ev.data.fd = fd[0];
        ev.events = EPOLLIN;
        epoll_ctl(epfd,EPOLL_CTL_ADD,fd[0],&ev);

        while(1)
        {
            int n = epoll_wait(epfd, &evs[1],-1,-1);
            if(n == 1)
            {   
                char buf[128] = "";
                int ret = read(fd[0],buf,sizeof(buf));
                if(ret <= 0)
                {
                    close(fd[0]);
                    epoll_ctl(epfd,EPOLL_CTL_DEL,fd[0],&ev);
                    break;
                }
                else
                {
                    printf("%s\n",buf);
                }
            }
        }
    }


    return 0;




}

EPOLL 高并发服务器

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"
#include<sys/epoll.h>

#define PORT 8000
int main()
{

    // 创建套接字
    int lfd = tcp4bind(PORT,NULL);
    // 监听
    Listen(lfd,128);
    // 创建树
    int epfd = epoll_create(1);
    // 将lfd上树
    struct epoll_event ev,evs[1024];
    ev.data.fd = lfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);


    // while循环监听
    while(1)
    {
        int nready = epoll_wait(epfd,evs,-1,-1);
        if (nready < 0)
        {
            perror("");
            break;
        }
        else if(nready == 0)
        {
            continue;
        }
        else   // nread > 0 文件描述符有变化
        {
            for(int i =0;i<nready;i++)
            {
                // 判断lfd变换,并且是读事件变换
                if(evs->data.fd == lfd && evs[i].events & EPOLLIN)
                {
                    struct sockaddr_in cliaddr;
                    char ip[16] = "";
                    socklen_t len = sizeof(cliaddr);
                    int cfd = Accept(lfd,(struct sockaddr *)&cliaddr, &len);

                    printf("new client ip = %s port =%d\n",
                            inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),
                            ntohs(cliaddr.sin_port));
                    
                    // 将cfd上树
                    ev.data.fd = cfd;
                    ev.events = EPOLLIN;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);

                }
                else if(evs[i].events & EPOLLIN) // cfd 变换,而且是读事件变换
                {
                    char buf[1024] = "";

                    int n = read(evs[i].data.fd,buf,sizeof(buf));
                    if(n < 0)   // 出错
                    {
                        perror("");
                        epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);
                    }
                    else if(n == 0) // 客户端关闭,下树
                    {
                        printf("client close]\n");
                        close(evs[i].data.fd); // 关闭cfd
                        epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]); 
                        
                    }
                    else    // 服务端进行处理
                    {
                        printf("%s\n",buf);
                        write(evs[i].data.fd,buf,n);
                    }

                }
            }
        }

    }
    
    return 0;
}

9 Epoll的两种工作方式

在这里插入图片描述

  1. 监听读缓存区的变化
    水平触发
    只要缓存区有数据,就会触发epoll_wait
    边缘触发
    数据来一次,epoll_wait只触发一次

2.监听写缓存区的变化
水平触发:只要可以写,就会触发
边沿触发:数据从有到无就会触发

边缘触发
在这里插入图片描述
在这里插入图片描述
触发一次的时候只读4位,但发送了10位,所以虽然只读一次,但是读不完

设置为一次读完
设置cfd为非阻塞

在这里插入图片描述
在这里插入图片描述

因为设置水平触发,只要缓存区有数据epoll_wait就会被触发,epoll_wait 是一个系统调用,尽量少调用边缘触发,边缘触发数据来一次只触发一次,这个时候要求一次性将数据读完,所以while循环读,堵到最后read默认带阻塞,不能让read阻塞,因为不能再去监听,设置cfd为非阻塞,read堵到最后一次返回值为-1,判断errno的值为eagain,则代表数据读干净、

工作中 边缘触发 + 非阻塞 = 高速模式

边缘触发代码

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"
#include<sys/epoll.h>
#include <fcntl.h>

#define PORT 8000
int main()
{

    // 创建套接字
    int lfd = tcp4bind(PORT,NULL);
    // 监听
    Listen(lfd,128);
    // 创建树
    int epfd = epoll_create(1);
    // 将lfd上树
    struct epoll_event ev,evs[1024];
    ev.data.fd = lfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);


    // while循环监听
    while(1)
    {
        int nready = epoll_wait(epfd,evs,-1,-1);
        printf("epoll wait");
        if (nready < 0)
        {
            perror("");
            break;
        }
        else if(nready == 0)
        {
            continue;
        }
        else   // nread > 0 文件描述符有变化
        {
            for(int i =0;i<nready;i++)
            {
                // 判断lfd变换,并且是读事件变换
                if(evs->data.fd == lfd && evs[i].events & EPOLLIN)
                {
                    struct sockaddr_in cliaddr;
                    char ip[16] = "";
                    socklen_t len = sizeof(cliaddr);
                    int cfd = Accept(lfd,(struct sockaddr *)&cliaddr, &len);

                    printf("new client ip = %s port =%d\n",
                            inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),
                            ntohs(cliaddr.sin_port));
                    
                    
                    // 获得cfd的标志位
                    int flag = fcntl(cfd, F_GETFL); 
                    // 设置为非阻塞
                    flag |= O_NONBLOCK;
                    fcntl(cfd,F_SETFL,flag);
                    // 将cfd上树
                    ev.data.fd = cfd;
                    ev.events = EPOLLIN | EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);

                }
                else if(evs[i].events & EPOLLIN) // cfd 变换,而且是读事件变换
                {
                    while(1)
                    {

                    char buf[4] = "";
                    // 如果读一个缓冲区,缓冲区域没有数据,如果是阻塞,就阻塞等待
                    // 是非阻塞,返回值等于 -1,并且会将errorno值设置为EAGAIN   

                    int n = read(evs[i].data.fd,buf,sizeof(buf));
                    if(n < 0)   // 出错
                    {   
                        // 如果缓冲区读干净了,这个时候应该跳出while循环,继续监听
                        if(errno == EAGAIN)
                        {
                            break;

                        }

                        // 普通错误
                        perror("");
                        close(evs[i].data.fd); // 关闭cfd
                        epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);
                        break;
                    }
                    else if(n == 0) // 客户端关闭,下树
                    {
                        printf("client close]\n");
                        close(evs[i].data.fd); // 关闭cfd
                        epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]); 
                        break;
                    }
                    else    // 服务端进行处理
                    {
                        // printf("%s\n",buf);
                        write(STDOUT_FILENO,buf,4);
                        write(evs[i].data.fd,buf,n);
                    }

                    }
                    
                }
            }
        }

    }
    
    return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到