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

news2024/10/7 4:30:19

文章目录

  • 0 包裹函数
  • 1 多进程服务器
    • 流程
    • 代码
  • 2 多线程服务器
  • 3 TCP状态转移
    • 半关闭
    • 心跳包
  • 4 端口复用
  • 5 IO多路复用技术
    • 高并发服务器
  • 6 select
    • 代码
    • 总结
  • 7 POLL
    • API
    • 代码
    • poll相对select的优缺点
  • 8 epoll(重点)
    • API
    • 监听管道
    • 代码
    • EPOLL 高并发服务器
  • 9 Epoll的两种工作方式
    • 边缘触发代码


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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1887108.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL备份】Percona XtraBackup加密备份实战篇

目录 1.前言 2.准备工作 2.1.环境信息 2.2.配置/etc/my.cnf文件 2.3.授予root用户BACKUP_ADMIN权限 2.4.生成加密密钥 2.5.配置加密密钥文件 3.加密备份 4.优化加密过程 5.解密加密备份 6.准备加密备份 7.恢复加密备份 7.1.使用rsync进行恢复 7.2.使用xtrabackup命令恢…

go Channel原理 (四)

Channel 设计原理 不要通过共享内存的方式进行通信&#xff0c;而是应该通过通信的方式共享内存。 在主流编程语言中&#xff0c;多个线程传递数据的方式一般都是共享内存。 Go 可以使用共享内存加互斥锁进行通信&#xff0c;同时也提供了一种不同的并发模型&#xff0c;即通…

终极指南:RNNS、Transformers 和 Diffusion 模型

一、说明 作为广泛使用这些工具和模型的人&#xff0c;我的目标是解开 RNN、Transformer 和 Diffusion 模型的复杂性和细微差别&#xff0c;为您提供详细的比较&#xff0c;为您的特定需求提供正确的选择。 无论您是在构建语言翻译系统、生成高保真图像&#xff0c;还是处理时间…

【ACM出版,马来西亚-吉隆坡举行】第四届互联网技术与教育信息化国际会议 (ITEI 2024)

作为全球科技创新大趋势的引领者&#xff0c;中国不断营造更加开放的科技创新环境&#xff0c;不断提升学术合作的深度和广度&#xff0c;构建惠及各方的创新共同体。这是对全球化的新贡献&#xff0c;是构建人类命运共同体的新贡献。 第四届互联网技术与教育信息化国际学术会议…

【CSAPP】-binarybomb实验

目录 实验目的与要求 实验原理与内容 实验设备与软件环境 实验过程与结果&#xff08;可贴图&#xff09; 操作异常问题与解决方案 实验总结 实验目的与要求 1. 增强学生对于程序的机器级表示、汇编语言、调试器和逆向工程等方面原理与技能的掌握。 2. 掌握使用gdb调试器…

【ONLYOFFICE】| 桌面编辑器从0-1使用初体验

目录 一. &#x1f981; 写在前面二. &#x1f981; 在线使用感受2.1 创建 ONLYOFFICE 账号2.2 编辑pdf文档2.3 pdf直接创建表格 三. &#x1f981; 写在最后 一. &#x1f981; 写在前面 所谓桌面编辑器就是一种用于编辑文本、图像、视频等多种自媒体的软件工具&#xff0c;具…

.NET周刊【6月第5期 2024-06-30】

国内文章 呼吁改正《上海市卫生健康信息技术应用创新白皮书》 C# 被认定为A 组件 的 错误认知 https://www.cnblogs.com/shanyou/p/18264292 近日&#xff0c;《上海市卫生健康“信息技术应用创新”白皮书》发布&#xff0c;提到医疗信创核心应用适配方法及公立医院信息系统…

thinksboard新建table表格

html文件 <div fxFlex fxLayoutAlign"left top" style"display: block"> <!-- <mat-card appearance"raised" style"max-height: 80vh; overflow-y: auto;">--><div><button mat-raised-button (click)&…

linux和mysql基础指令

Linux中nano和vim读可以打开记事文件。 ifdown ens33 ifup ens33 关闭&#xff0c;开启网络 rm -r lesson1 gcc -o code1 code1.c 编译c语言代码 ./code1 执行c语言代码 rm -r dir 删除文件夹 mysql> show databases-> ^C mysql> show databases; -------…

基于STM32的卫星GPS路径记录仪

目录 引言环境准备卫星GPS路径记录仪基础代码实现&#xff1a;实现卫星GPS路径记录仪 4.1 数据采集模块4.2 数据处理与分析4.3 存储系统实现4.4 用户界面与数据可视化应用场景&#xff1a;路径记录与分析问题解决方案与优化收尾与总结 1. 引言 卫星GPS路径记录仪通过使用STM…

第十四届蓝桥杯省赛C++B组A题【日期统计】题解(AC)

题目大意 给定的字符串中&#xff0c;有几个子序列是 2023 2023 2023 年的日期&#xff08;每个日期只能算一次&#xff09;。 法一 枚举所有长度为 8 8 8 的子序列&#xff0c;判断是否是有效日期并去重&#xff0c;时间复杂度 O ( C 100 8 ) O(C^8_{100}) O(C1008​)。…

秋招Java后端开发冲刺——并发篇1(线程与进程、多线程)

一、进程 1. 进程 进程是程序的一次动态执行过程&#xff0c;是操作系统资源分配的基本单位。 2. 进程和线程的区别 特性进程线程定义独立运行的程序实例&#xff0c;资源分配的基本单位进程中的一个执行单元&#xff0c;CPU调度的基本单位资源进程拥有独立的内存空间和资源线…

Java_多线程:线程池

1、线程池优点&#xff1a; 降低资源消耗&#xff1a;通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度&#xff1a;当任务到达时&#xff0c;任务可以不需要等到线程创建就能立即执行。提高线程的可管理性&#xff1a;线程是稀缺资源&#xff0c;如果无限…

c语言的烫烫烫烫烫??

当初学习C语言时&#xff0c;对于一些特殊的打印输出可能会感到困惑&#xff0c;比如会出现一堆乱码烫烫烫的情况。其实这是因为在C语言中&#xff0c;对于字符类型和数字类型之间的隐式转换可能会导致打印输出的结果不符合预期。这并不意味着程序员"烫"&#xff0c;…

鸿蒙开发Ability Kit(程序访问控制):【安全控件概述】

安全控件概述 安全控件是系统提供的一组系统实现的ArkUI组件&#xff0c;应用集成这类组件就可以实现在用户点击后自动授权&#xff0c;而无需弹窗授权。它们可以作为一种“特殊的按钮”融入应用页面&#xff0c;实现用户点击即许可的设计思路。 相较于动态申请权限的方式&am…

Dify入门指南

一.Dify介绍 生成式 AI 应用创新引擎&#xff0c;开源的 LLM 应用开发平台。提供从 Agent 构建到 AI workflow 编排、RAG 检索、模型管理等能力&#xff0c;轻松构建和运营生成式 AI 原生应用&#xff0c;比 LangChain 更易用。一个平台&#xff0c;接入全球大型语言模型。不同…

Google地图获取位置的前端代码与测试

test.html <script src"http://maps.google.com/maps/api/js?sensorfalse"></script> <script > if (navigator.geolocation) {  console.log(Geolocation is supported!);// var startPos;var geoSuccess function(position) {startPos p…

MySQL4(事务、函数、慢查询和索引)

目录 一、MySQL事务 1. 概念 2. 事务的ACID原则 3. MySQL实现事务的方法 4. MySQL实现事务的步骤 5. 事务的原子性、一致性、持久性 6. 事务的隔离性 7. MySql中的锁 1. 共享锁 2. 排他锁 3. 行级锁 4. 表级锁 5. 间隙锁 6. 临键锁 7. 记录锁 8. 意向共享锁…

236、二叉树的最近公共祖先

前提&#xff1a; 所有 Node.val 互不相同 。p ! qp 和 q 均存在于给定的二叉树中。 代码如下&#xff1a; class Solution { public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if (root q || root p || root NULL) return root;TreeN…

赛灵思FFT的IP核——非实时模式 Non real time

一、IP核配置 使用非实时模式配置如下 二、时序 是遵守AXIS协议的&#xff0c;不像real time mode&#xff0c;一旦开始吭哧吭哧的读数据&#xff0c;根本不管s_axis_data_tvalid信号了。 三、资源消耗 在implement查看两者的资源消耗差不多 四、仿真 4.1 中间不停顿 输入…