Linux网络编程(3)

并发服务器

多路IO转接

单进程服务器会使客户端阻塞在输入上,可通过IO复用使其阻塞在某个函数上以达到多进程/多线程的效果。

一.select

1.相关函数
#include <sys/select.h>

//均为宏,fd_set类型为描述符集合
void FD_ZERO(fd_set *fdset) 				//清空fdset
void FD_SET(int fd, fd_set *fdset))			//在fdset中添加fd
void FD_ISSET(int fd, fd_set *fdset))		//判断对应位是否打开,就是判断对应事件是否发生
void FD_CLR(int fd, fd_set *fdset))			//从fdset中删去fd
struct timeval
{
	long tv_sec; 	//秒数
	long tv_usec;	//微秒数
}
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
调用select告知内核在某些情况发生时返回
nfds:最大描述符+1
readfds,wrirefds,exceptfds分别为读,写,异常事件描述符集合,且均为【传入传出参数】
timeout:等待时间
				永远等下去(仅在有一个描述符发生对应事件时返回):NULL
				等一段时间:指定timeout
				不等待:指定timeout中成员为0

调用select后内核会对指定的描述符集合进行测试,如果发生对应事件就将该描述符对应位打开,在通过FD_ISSET()判断对应事件是否发生

3.基本流程

socket,bind,listen --> select -->FD_ISSET(lfd) --> accept -->select
FD_ISSET(lfd) --> accept
FD_ISSET(cfd) --> read,write

4.简单应用
#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>

int main()
{
    int lfd, cfd, maxfd, n, ret;
    struct sockaddr_in saddr, caddr;
    fd_set rset, allset;
    char buf[BUFSIZ], str[INET_ADDRSTRLEN]; //BUFSIZ 1024, INET__ADDRSTRLEN 16
    socklen_t clen;

	bzero(&saddr, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);

    lfd = socket(AF_INET, SOCK_STREAM, 0);
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
    listen(lfd, 128);

    FD_ZERO(&allset);		//由于对应参数为传入传出所以需要设置总描述符集
    FD_SET(lfd, &allset);	//添加监听描述符
    maxfd = lfd;
    
    while(1)
    {
        rset = allset;		
        ret = select(maxfd+1, &rset, NULL, NULL, NULL);
        if(FD_ISSET(lfd, &rset))  			//检查是否有连接请求
        {
            clen = sizeof(caddr);
            cfd = accept(lfd, (struct sockaddr *)&caddr, &clen);
            
			printf("connection-- port:%d\tip:%s\n", ntohs(caddr.sin_port), inet_ntop(AF_INET, &caddr.sin_addr.s_addr, str, sizeof(str)));
			
            FD_SET(cfd, &allset);			

            if(maxfd < cfd)
                maxfd = cfd;

            if(ret == 1)  			//只有连接请求就不需要往下继续
                continue;
        }

        for(int i = lfd+1; i <= maxfd; i++)			//检查除了连接请求外是否有相应事件发生
        {
            if(FD_ISSET(i, &rset))
            {
                n = read(cfd, buf, sizeof(buf));
                if(n == 0)							//read返回0即收到断开连接请求
                {
                    close(i);
                    FD_CLR(i, &allset);
                }
                else
                {
                    write(STDOUT_FILENO, buf, n);
                    for(int j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    write(i, buf, n);
                }
            }
        }
    }
    close(lfd);
    return 0;
}

5.select优缺点

优点:跨平台
缺点:可监听的描述符受1024限制,只能轮询查看满足事件的描述符

二.selectt

通过增加一个client数组来提高检测效率

#include <stdio.h>
#include <sys/types.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>

int main()
{
	int cfd, lfd, sfd, maxfd, maxi, ret, n, i;
	struct sockaddr_in saddr, caddr;
	socklen_t clen;
	fd_set allset, rset;
	char buf[BUFSIZ], str[INET_ADDRSTRLEN];
	int client[FD_SETSIZE];

	bzero(&saddr, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(9999);
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);

	lfd = socket(AF_INET, SOCK_STREAM, 0);
	bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
	listen(lfd, 128);

	FD_ZERO(&allset);
	FD_SET(lfd, &allset);
	maxfd = lfd;
	maxi = -1;
	
	for(i = 0; i < 1024; i++)
		client[i] = -1;

	while(1)
	{
		rset = allset;
		ret = select(maxfd+1, &rset, NULL, NULL, NULL);
		if(FD_ISSET(lfd, &rset))
		{
			clen = sizeof(caddr);
			cfd = accept(lfd, (struct sockaddr *)&caddr, &clen);
			
			printf("connection-- port:%d\tip:%s\n", ntohs(caddr.sin_port), inet_ntop(AF_INET, &caddr.sin_addr.s_addr, str, sizeof(str)));

			FD_SET(cfd, &allset);
			
			for(i = 0; i < FD_SETSIZE; i++)
			{
				if(client[i] == -1)
					{
						client[i] = cfd;
						break;
					}
			}

			if(cfd > maxfd)
				maxfd = cfd;
			
			if(maxi < i)
				maxi = i;

			if(ret == 1) 
				continue;

		}
		for(i = 0; i < maxi+1; i++)
		{
			sfd = client[i];
			if(sfd < 0)
				continue;
			if(FD_ISSET(sfd, &rset))
			{
				n = read(sfd, buf, sizeof(buf));
				if(n  == 0)
				{
					close(sfd);
					FD_CLR(sfd, &allset);
					client[i] = -1;
				}
				else
				{
					for(int j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
					write(STDOUT_FILENO, buf, n);
					write(sfd, buf, n);
				}
			}
		}
	}

	close(lfd);
	return 0;
}

三.poll

相较于select优化了传入传出参数在一起的复杂情况,提升不大,且和select流程类似

1.相关函数
#include <poll.h>

struct pollfd
{
	int fd;
	short events;   //POLLIN/POLLOUT/POLLERR...
	short revents;
}

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
作用和select相同
fds:指向struct pollfd的数组
nfds:fds的大小
timeout:等待时间
				不等待:0
				等待指定毫秒数:>0
				永远等待:INFTIM(负值)/-1
2.基本流程

socket,bind,listen --> poll --> client[0].revents & POLLIN --> accept --> poll
client[0].revents & POLLIN --> accept
client[i].revents & POLLIN --> read,write

3.简单应用

跟select操作流程类似

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <poll.h>

#define OPEN_MAX 1024

int main()
{
	int cfd, lfd, sfd, ret, n, i, maxi;
	struct sockaddr_in caddr, saddr;
	char buf[BUFSIZ], str[INET_ADDRSTRLEN];
	struct pollfd client[OPEN_MAX];
	socklen_t clen;

	bzero(&saddr, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(9999);
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);

	lfd = socket(AF_INET, SOCK_STREAM, 0);
	bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
	listen(lfd, 128);

	for(i = 0; i < OPEN_MAX ; i++)
		client[i].fd = -1;

	client[0].fd = lfd;
	client[0].events = POLLIN;
	maxi = 0;

	while(1)
	{
		ret = poll(client, maxi+1, -1);
		if(client[0].revents & POLLIN)
		{
			clen = sizeof(caddr);
			cfd = accept(lfd, (struct sockaddr *)&caddr, &clen);
			
			printf("connection--port:%d\tip:%s\n", ntohs(caddr.sin_port), inet_ntop(AF_INET, &caddr.sin_addr.s_addr, str, sizeof(str)));

			for(i = 1; i < OPEN_MAX; i++)
			{
				if(client[i].fd < 0)
				{
					client[i].fd = cfd;
					client[i].events = POLLIN;
					break;
				}
			}

			if(maxi < i)
				maxi = i;

			if(ret == 1)
				continue;
		}

		for(i = 1; i < maxi+1; i++)
		{
			sfd = client[i].fd;
			if(client[i].revents & POLLIN)
			{
				n = read(sfd, buf, sizeof(buf));
				if(n == 0)
				{
					close(sfd);
					client[i].fd = -1;
				}
				else
				{
					for(int j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
					write(STDOUT_FILENO, buf, n);
					write(sfd, buf, n);
				}

				if(ret == 1)
					break;
			}
		}
	}
	close(lfd);
	return 0;
}
4.优缺点

优点:自带数组,监听集合和返回集合分离,可突破1024限制
缺点:不能跨平台,只能轮询查看满足事件的描述符

四.突破1024限制

五.epoll

相较于select和poll性能上提升较大,通过红黑树和双向链表使得在高并发情况下依然保有效率,且可以突破最大文件描述符1024的限制,理论上可达到无限但受到硬件限制。

1.相关函数
#include <sys/epoll.h>

struct epoll_event {
            __uint32_t events; // EPOLLIN/EPOLLOUT...
            epoll_data_t data; /* User data variable */
        };

typedef union epoll_data {
            void *ptr;
            int fd;
            uint32_t u32;
            uint64_t u64;
        } epoll_data_t;

int epoll_create(int size);

创建一个红黑树
size:红黑树大小
return 	红黑树句柄epfd:success
			-1:error 

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event)

对epfd所对应的红黑树进行增加、删除等操作
epfd:红黑树句柄
op:进行某些操作
	EPOLL_CTL_ADD:往树上添加
	EPOLL_CTL_MOD:改变某些设置
	EPOLL_CTL_DEL:从树上删除
fd:要操作的描述符
event:fd所关联的结构体
return 0:success
		  -1:error

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

监听事件发生
epfd:红黑树句柄
events:【传出参数】有对应事件发生的fd所关联的结构体数组
maxevents:最大描述符
timeout:等待时间
			-1:阻塞
			0:立即返回
			>0 :等待相应微秒
return 有对应事件发生的描述符总数: success
			-1: error		 
2.基本流程

socket,bind,listen --> epoll_create() --> epoll_ctl()添加监听描述符 --> epoll_wait()监听
有连接请求 --> accept()
有数据发送 --> read() --> write()

3.简单应用
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/epoll.h>

#define OPEN_MAX 1024

int main()
{
	int cfd, lfd, sfd, ret, n, i, maxi, efd, nready;
	struct sockaddr_in caddr, saddr;
	char buf[BUFSIZ], str[INET_ADDRSTRLEN];
	socklen_t clen;

	bzero(&saddr, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(9999);
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);

	lfd = socket(AF_INET, SOCK_STREAM, 0);
	bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
	listen(lfd, 128);

    struct epoll_event tep, ep[OPEN_MAX];

    efd = epoll_create(OPEN_MAX);

    tep.events = EPOLLIN;
    tep.data.fd = lfd;

    ret = epoll_ctl(efd, EPOLL_CTL_ADD, lfd, &tep);

    while(1)
    {
        nready = epoll_wait(efd, ep, OPEN_MAX, -1);
        for(i = 0; i < nready; i++)
        {
            if(!ep[i].events & EPOLLIN)
                continue;
            if(ep[i].data.fd == lfd)
            {
                clen = sizeof(caddr);
                cfd = accept(lfd, (struct sockaddr *)&caddr, &clen);

                printf("connection--port:%d\tip:%s\n", ntohs(caddr.sin_port), inet_ntop(AF_INET, &caddr.sin_addr.s_addr, str, sizeof(str)));

                tep.data.fd = cfd;
                tep.events = EPOLLIN;
                ret = epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &tep);
            }
            else
            {
                sfd = ep[i].data.fd;
                n = read(sfd, buf, sizeof(buf));
                if(n == 0)
                {
                    ret = epoll_ctl(efd, EPOLL_CTL_DEL, sfd, NULL);
                    close(sfd);
                }
                else
                {
                    for(int j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
					write(STDOUT_FILENO, buf, n);
					write(sfd, buf, n);
                }
            }
        }
    }
    close(lfd);
    return 0;
}
4.epoll 中的ET/LT模式

LT(水平触发):默认情况下为LT,当缓冲区有数据时,即使一次没有读完,也会一直触发epoll_wait()读数据直到读完为止
ET(边沿触发):当缓冲区有数据且一次没有读完时,不会继续读,会等下一次新的数据来时读上一次剩余数据,
epoll的et高效但只支持非阻塞模式

struct epoll_event tep;
tep.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep);
int flg = fctnl(cfd, F_GETFL)
flg |= O_NONBLOCK
fctnl(cfd, F_SETFL, flg);
5.优缺点

优点:高效,突破1024限制
缺点:不能跨平台,只支持linux

六.epoll反应堆模型(libevent 网络库核心思想)

epoll + 回调函数 + 自定义结构 + ET非阻塞

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值