多路复用IO、TCP并发模型

时分复用

CPU单核在同一时刻只能做一件事情,一种解决办法是对CPU进行时分复用(多个事件流将CPU切割成多个时间片,不同事件流的时间片交替进行)。在计算机系统中,我们用线程或者进程来表示一条执行流,通过不同的线程或进程在操作系统内部的调度,来做到对CPU处理的时分复用。这样多个事件流就可以并发进行,不需要一个等待另一个太久,在用户看起来他们似乎就是并行在做一样。

PC寄存器  程序计数器 代码执行到哪里了  程序下一条要执行什么。

有没有一种可以在单线程/进程中处理多个事件流的方法呢?一种答案就是IO多路复用。

因此IO多路复用解决的本质问题是在用更少的资源完成更多的事。

IO模型

  •     1、阻塞IO  
  •     2、非阻塞IO  EAGAIN  忙等待 errno
  •     3、信号驱动IO  SIGIO 用的相对少(了解)
  •     4、并行模型 进程,线程
  •     5, IO多路复用  select、poll、epoll

1、阻塞IO ===》最常用 默认设置

没有数据到来时,可以让任务挂起,节省CPU资源消耗,提高系统效率。

以管道读写为例子:
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main(int argc, const char *argv[])
{
	int ret = mkfifo("fifo",0666);
	if(-1 == ret)
	{
		if(EEXIST == errno)
		{
			
		}

		else
		{
			perror("mkfifo error");
			exit(1);
		}
	}

	int fd = open("fifo",O_RDONLY);
	if(-1 == fd)
	{
		perror("open error");
		exit(1);
	}

	while(1)
	{
		char buf[100] = {0};
		read(fd,buf,sizeof(buf));
		printf("fifo:%s\n",buf);

		bzero(buf,sizeof(buf));
		fgets(buf,sizeof(buf),stdin);
		printf("terminal:%s",buf);
		fflush(stdout);

	}

	close(fd);

	return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
	int ret = mkfifo("fifo",0666);
	if(-1 == ret)
	{
		if(EEXIST == errno)
		{
		}
		else
		{
			perror("mkfifo failed");
			exit(1);
		}
	}
	
	int fd = open("fifo", O_WRONLY);
	if(-1 == fd)
	{
		perror("open failed");
		exit(1);
	}

	while(1)
	{
		char buf[100] = "hello,this is fifo test";
		write(fd,buf,strlen(buf));
		sleep(3);
	}
	close(fd);

	return 0;
}

2、非阻塞IO ===》在阻塞IO的基础上调整其为不再阻塞等待。


     在程序执行阶段调整文件的执行方式为非阻塞:
            ===》fcntl() ===>动态调整文件的阻塞属性

程序未接收到数据时一直执行,效率低。

fcntl()

#include <unistd.h>
#include <fcntl.h>
 

   int fcntl(int fd, int cmd, ... /* arg */ );


    功能:修改指定文件的属性信息。
    参数:fd 要调整的文件描述符
          cmd 要调整的文件属性宏名称
          ... 可变长的属性值参数。
    返回值:成功  不一定,看cmd
                  失败  -1;

int flag = fcntl(fd,F_GETFL);

fd 是要修改的文件描述符,可能是打开的文件、套接字或其他I/O设备。F_GETFL 是一个预定义的常量,告诉 fcntl 函数获取与文件描述符关联的标志。

fcntl(fd,F_SETFL ,flag | O_NONBLOCK );

 F_SETFL 常量用于设置文件描述符的标志。flag | O_NONBLOCK 是将获取到的当前标志与 O_NONBLOCK 常量进行按位或操作,目的是在现有标志的基础上添加非阻塞标志。

//noblock_fifo_r.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
	int ret = mkfifo("fifo",0666);
	if(-1 == ret)
	{
		if(EEXIST == errno)
		{
			
		}

		else
		{
			perror("mkfifo error");
			exit(1);
		}
	}

	int fd = open("fifo",O_RDONLY);
	if(-1 == fd)
	{
		perror("open error");
		exit(1);
	}

	int flag = fcntl(fd,F_GETFL);//获取当前文件描述符的标志:
	fcntl(fd,F_SETFL,flag|O_NONBLOCK);//设置文件描述符为非阻塞模式:

	flag = fcntl(0,F_GETFL);// 0 是标准输入的文件描述符 stdin
	fcntl(0,F_SETFL,flag|O_NONBLOCK);

	while(1)
	{
		char buf[100] = {0};
		if(read(fd,buf,sizeof(buf))>0)
		{
			printf("fifo:%s\n",buf);
		}


		bzero(buf,sizeof(buf));
		if(fgets(buf,sizeof(buf),stdin))
		{	
			printf("terminal:%s",buf);
			fflush(stdout);
		}

	}

	close(fd);
	return 0;
}
//noblock_fifo_w.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
	int ret = mkfifo("fifo",0666);
	if(-1 == ret)
	{
		if(EEXIST == errno)
		{
		}
		else
		{
			perror("mkfifo failed");
			exit(1);
		}
	}
	
	int fd = open("fifo", O_WRONLY);
	if(-1 == fd)
	{
		perror("open failed");
		exit(1);
	}

	while(1)
	{
		char buf[100] = "hello,this is fifo test";
		write(fd,buf,strlen(buf));
		sleep(3);
	}
	close(fd);

	return 0;
}

3.select

  1.   select监听的集合中的文件描述符有上限限制
  2.   select有内核层向用户层数据空间拷贝的过程,占用系统资源开销
  3.   select必须轮询检测产生事件的文件描述符
  4.   select只能工作在水平触发模式(低速模式),无法工作在边沿触发(高速模式)

本来是read和fgets分别监听fd和stdin , 轮回监听数据描述符。  

用select函数来动态检测有数据流动的文件描述符。

  1. 创建fd集合
  2. 加入fd
  3. select()
  4. 检测fd
  5. read/write
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

功能:完成指定描述符集合set中有效描述符的动态检测。
          该函数具有阻塞等待功能,在函数执行完毕后
          目标测试集合中将只保留最后有数据的描述符。

参数:nfds 描述符的上限值,一般是链接后描述符的最大值+1;
          readfds 只读描述符集
          writefds 只写描述符集
          exceptfds 异常描述符集
                以上三个参数都是 fd_set * 的描述符集合类型
          timeout  检测超时 如果是NULL表示一直检测不超时 。等待时常。

返回值:超时 0
            失败  -1
            成功 >0

放入set集合中


        为了配合select函数执行,有如下宏函数:
        void FD_CLR(int fd, fd_set *set);
        功能:将指定的set集合中编号为fd的描述符号删除。

        int  FD_ISSET(int fd, fd_set *set);
        功能:判断值为fd的描述符是否在set集合中,
              如果在则返回真,否则返回假。

        void FD_SET(int fd, fd_set *set);
        功能:将指定的fd描述符,添加到set集合中。

        void FD_ZERO(fd_set *set);
        功能:将指定的set集合中所有描述符删除。

不包含超时的情况: 

**** 

fgets(tmpbuff, sizeof(tmpbuff), 0); 会发生段错误

•将 0 替换为 stdin,即 fgets(tmpbuff, sizeof(tmpbuff), stdin);。

•stdin 是标准输入流,通常代表从键盘输入。

并且会把\n  回车也会被写入

#include "head.h"
int main(int argc, const char *argv[])
{
	int fd = 0;
	char tmpbuff[4096] = {0};

	mkfifo("myfifo",0664);

	fd = open("myfifo",O_WRONLY);
	if(-1 == fd)
	{
		perror("fail to open");
		return -1;
	}
	
	while(1)
	{
		fgets(tmpbuff,sizeof(tmpbuff),stdin);
		write(fd,tmpbuff,strlen(tmpbuff));
	}

	close(fd);

	return 0;
}

 read.c

#include "head.h"

int main(int argc, const char *argv[])
{
	int fd = 0;
	int flags = 0;
	char *pret = NULL;
	ssize_t nsize = 0;
	char tmpbuff[4096] = {0};
	fd_set rdfds;
	fd_set tmpfds;
	int ret = 0;

	mkfifo("myfifo",0664);

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

	FD_ZERO(&rdfds);
	FD_SET(fd,&rdfds);
	FD_SET(0,&rdfds);

	while(1)
	{
		tmpfds = rdfds;
		ret = select(fd+1,&tmpfds,NULL,NULL,NULL);
		if(-1 == ret)
		{
			perror("fail to select");
			return -1;
		}

		if(FD_ISSET(fd,&tmpfds))
		{
			memset(tmpbuff,0,sizeof(tmpbuff));
			read(fd,tmpbuff,sizeof(tmpbuff));
			printf("FIFO:%s",tmpbuff);
		}

		if(FD_ISSET(0,&tmpfds))
		{
			memset(tmpbuff,0,sizeof(tmpbuff));
			fgets(tmpbuff,sizeof(tmpbuff),stdin);
			printf("STDIN:%s",tmpbuff);
		}
	}

	close(fd);


	return 0;
}

select的缺点在于:
① 由于描述符集合set的限制,每个set最多只能监听FD_SETSIZE(在Linux上是1024)个句柄(不同机器可能不一样);
② 返回的可读集合是个fdset类型,需要对所有的监听读句柄一一进行FD_ISSET的测试来判断是否可读;
③ nfds的存在就是为了解决select的效率问题(select遍历nfds个文件描述符,判断每个描述符是否是自己关心的,对关心的描述符判断是否发生事件)。但是解决不彻底,比如如果只监听0和1000两个句柄,select需要遍历1001个句柄来检查事件。
 

4.epoll

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll 解决了select和poll的几个性能上的缺陷:

  • ①不限制监听的描述符个数(poll也是),只受进程打开描述符总数的限制;
  • ②监听性能不随着监听描述 符数的增加而增加,是O(1)的,不再是轮询描述符来探测事件,而是由描述符主动上报事件
  • ③使用共享内存的方式,不在用户和内核之间反复传递监听的描述 符信息;
  • ④返回参数中就是触发事件的列表,不用再遍历输入事件表查询各个事件是否被触发。

epoll显著提高性能的前提是:监听大量描述符,并且每次触发事件的描述符文件非常少。
epoll的另外区别是:①epoll创建了描述符,记得close;②支持水平触发(低俗模式)和边沿触发(高速模式)。

epoll创建的是内核监听事件表,所以只需要在内核空间完成数据的拷贝,也没有文件描述符上限的限制

为什么 TCP需要并发模型而UDP不需要?

tcp是面向连接的,其中的accept()和recv()如果未收到数据都是会阻塞。

TCP:

1. 面向连接:TCP是面向连接的协议,意味着在数据传输开始前,需要先建立连接,这通常涉及到三次握手的过程。每个TCP连接都是独立的,需要单独管理。

2. 可靠性:TCP提供了可靠的数据传输,它确保数据按顺序到达,且不会丢失。这通过序列号、确认应答、重传机制和流量控制等机制实现。

3. 拥塞控制:TCP具有拥塞控制机制,以防止网络拥塞。当网络拥堵时,TCP会减慢数据发送速率。

UDP:由于UDP的无连接和不可靠特性,通常不需要像TCP那样精细的并发模型。当一个UDP服务器需要处理多个客户端时,它可以简单地接收数据报,处理后回应,而不需要维护每个连接的状态。因此,即使在处理大量并发请求时,UDP服务器的实现通常也较为简单,可能只需要一个线程或进程来处理所有数据报。

多进程、多线程模型:

     多进程模型(Multi-process Model): 在这种模型中,每当一个新的连接请求到来时,服务器就会创建一个新的进程来处理这个连接。每个进程负责一个或多个连接,处理完后退出。这种方法的优点是每个连接都有独立的地址空间,错误不会互相影响,但是创建进程的开销较大,而且每个进程都需要消耗一定的系统资源,如内存和文件描述符。

     多线程模型(Multi-threaded Model): 与多进程模型类似,但使用线程代替进程。当新的连接请求到达时,服务器会在现有的线程池中选择一个线程来处理这个连接。线程的切换和上下文切换开销比进程要小,因此可以支持更高的并发数。但是线程共享相同的地址空间,所以需要小心处理线程安全问题。

    缺点:多进程和多线程的创建会带来资源开销,能够实现的并发量比较有限 。

 逻辑控制流在时间上的重叠叫做 并发。

多线程并发:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include<pthread.h>


void *th1(void *arg)
{
	int fd = *(int *)arg;
	while(1)
	{
		char buf[100] = {0};
		read(fd,buf,sizeof(buf));
		printf("fifo:%s\n",buf);
	}

	return NULL;
}

void *th2(void *arg)
{
	while(1)
	{
		char buf[100] ={0};
		fgets(buf,sizeof(buf),stdin);
		printf("terminal:%s",buf);
		fflush(stdout);
	}
	return NULL;
}

int main(int argc, const char *argv[])
{
	int ret = mkfifo("fifo",0666);
	if(-1 == ret)
	{
		if(EEXIST == errno)
		{
			
		}

		else
		{
			perror("mkfifo error");
			exit(1);
		}
	}

	int fd = open("fifo",O_RDONLY);
	if(-1 == fd)
	{
		perror("open error");
		exit(1);
	}

	pthread_t tid1,tid2;
	pthread_create(&tid1,NULL,th1,&fd);
	pthread_create(&tid2,NULL,th2,NULL);
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	

	close(fd);
	return 0;
}

TCP并发模型

1.创建多线程任务实现tcp客户端并发: 

#ifndef __HEAD_H__
#define __HEAD_H__

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#endif

服务器:

#include"head.h"

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

	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(-1 == sockfd)
	{
		perror("fail to socket");
		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)
	{
		perror("fail to bind");
		return -1;
	}

	ret = listen(sockfd,10);
	if(-1 == ret)
	{
		perror("fail to listen");
		return -1;
	}

	return sockfd;
}

void *HandleTcpClient(void *arg)
{
	char tmpbuff[4096] = {0};
	ssize_t nsize = 0;
    int confd = (int)(intptr_t)arg;
	while(1)
	{
		memset(tmpbuff,0,sizeof(tmpbuff));
		nsize = recv(confd,tmpbuff,sizeof(tmpbuff),0);
		if(-1 == nsize)
		{
			perror("fail to recv");
			return NULL;
		}
		else if(0 == nsize)
		{
			return NULL;
		}

		sprintf(tmpbuff,"%s ----echo",tmpbuff);
		nsize = send(confd,tmpbuff,strlen(tmpbuff),0);
		if(-1 == nsize)
		{
			perror("fail to send");
			return NULL;
		}
	}

	return NULL;
}

int main(int argc, const char *argv[])
{
	int sockfd = 0;
	int confd = 0;
	pthread_t tid;
	pthread_attr_t attr;

	sockfd = CreateListenSocket("192.168.95.131",50000);
	pthread_attr_init(&attr); //初始化线程属性
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	//设置线程的分离状态  
	//分离的线程不需要 显式的调用pthread_join来等待线程结束,线程结束会自动释放资源。

	while(1)
	{
		confd = accept(sockfd,NULL,NULL);
		if(-1 == confd)
		{
			perror("fail to accept");
			return -1;
		}
		void *confd_ptr = (void *)(intptr_t)confd;
		pthread_create(&tid,&attr,HandleTcpClient,confd_ptr);
	}

	close(sockfd);
	return 0;
}

客户端:

#include "head.h"

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

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

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

int main(int argc, const char *argv[])
{
	int sockfd = 0;
	char tmpbuff[4096] = {"hello world"};
	int cnt = 0;
	ssize_t nsize = 0;

	sockfd = CreateTcpClient("192.168.95.131",50000);

	while(1)
	{
		memset(tmpbuff,0,sizeof(tmpbuff));
		sprintf(tmpbuff,"BYBy----%d",cnt);
		cnt++;
		nsize = send(sockfd,tmpbuff,strlen(tmpbuff),0);
		if(-1 == nsize)
		{
			perror("fail to send");
			return -1;
		}

		memset(tmpbuff,0,sizeof(tmpbuff));
		nsize = recv(sockfd,tmpbuff,sizeof(tmpbuff),0);
		if(-1 == nsize)
		{
			perror("fail to recv");
			return -1;
		}

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

	close(sockfd);

	return 0;
}

2.使用select实现tcp客户端并发

服务器:

#include "head.h"

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

	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(-1 == sockfd)
	{
		perror("fail to socket");
		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)
	{
		perror("fail to bind");
		return -1;
	}

	ret = listen(sockfd,10);
	if(-1 == ret)
	{
		perror("fail to listen");
		return -1;
	}

	return sockfd;
}

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

	memset(tmpbuff,0,sizeof(tmpbuff));
	nsize = recv (confd,tmpbuff,sizeof(tmpbuff),0);
	if(-1 == nsize)
	{
		perror("fail to recv");
		return -1;
	}
	else if(0 == nsize)
	{
		return 0;
	}

	sprintf(tmpbuff,"%s ---echo",tmpbuff);
	nsize = send(confd,tmpbuff,strlen(tmpbuff),0);
	if(-1 == nsize)
	{
		perror("fail to send");
		return -1;
	}

	return nsize;
}

int main(int argc, const char *argv[])
{
	int sockfd = 0;
	int confd = 0;
	fd_set rdfds;
	fd_set tmpfds;
	int maxfd = 0;
	int ret = 0;
	int i =0;

	sockfd = CreateListenSocket("192.168.95.131",50000);

	FD_ZERO(&rdfds);
	FD_SET(sockfd,&rdfds);
	maxfd = sockfd;

	while(1)
	{
		tmpfds = rdfds;
		ret = select(maxfd+1,&tmpfds,NULL,NULL,NULL);
		if(-1 == ret)
		{
			perror("fail to select");
			return -1;
		}

		if(FD_ISSET(sockfd,&tmpfds))
		{
			confd = accept(sockfd,NULL,NULL);
			if(-1 == confd)
			{
				perror("fail to accept");
				FD_CLR(sockfd,&rdfds);
				close(sockfd);
				continue;
			}

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

		for(i = sockfd+1;i <= maxfd;i++)
		{
			if(FD_ISSET(i,&tmpfds))
			{
				ret = HandleTcpClient(i);
				if(-1 == ret)
				{
					fprintf(stderr,"handle client failed");
					FD_CLR(i,&rdfds);
					close(i);
					continue;
				}
				else if(0 == ret)
				{
					fprintf(stderr,"client disconnected");
					FD_CLR(i,&rdfds);
					close(i);
					continue;
				}
			}
		}
	}

	close(confd);
	close(sockfd);
	
	return 0;
}

客户端:

#include "head.h"

int CreateTcpClient(char *pip,int port)
{
	int ret = 0;
	int sockfd = 0;
	struct sockaddr_in seraddr;
	
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(-1 == sockfd)
	{
		perror("fail to socket");
		return -1;
	}

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

	return sockfd;
}

int main(int argc, const char *argv[])
{
	int sockfd = 0;
	char tmpbuff[4096]= {0};
	int cnt = 0;
	ssize_t nsize =0;

	sockfd = CreateTcpClient("192.168.95.131",50000);

	while(1)
	{
		memset(tmpbuff,0,sizeof(tmpbuff));
		sprintf(tmpbuff,"keykey_____%d",cnt);
		cnt++;
		nsize = send(sockfd,tmpbuff,strlen(tmpbuff),0);
		if(-1 == nsize)
		{
			perror("fail to send");
			return -1;
		}

		memset(tmpbuff,0,sizeof(tmpbuff));
		nsize = recv(sockfd,tmpbuff,sizeof(tmpbuff),0);
		if(-1 == nsize)
		{
			perror("fail to recv");
			return -1;
		}

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

	close(sockfd);
	
	return 0;
}

3.使用poll实现tcp客户端并发

服务器:

#include "head.h"

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

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
	{
		perror("fail to socket");
		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)
	{
		perror("fail to bind");
		return -1;
	}

	ret = listen(sockfd, 10);
	if (-1 == ret)
	{
		perror("fail to listen");
		return -1;
	}

	return sockfd;
}

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

	memset(tmpbuff, 0, sizeof(tmpbuff));
	nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
	if (-1 == nsize)
	{
		perror("fail to recv");
		return -1;
	}
	else if (0 == nsize)
	{
		return 0;
	}

	sprintf(tmpbuff, "%s ----echo", tmpbuff);
	nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
	if (-1 == nsize)
	{
		perror("fail to send");
		return -1;
	}

	return nsize;
}

int InitFds(struct pollfd *fds, int maxlen)
{
	int i = 0;

	for (i = 0; i < maxlen; i++)
	{
		fds[i].fd = -1;
	}

	return 0;
}

int AddFd(struct pollfd *fds, int maxlen, int fd, short env)
{
	int i = 0;

	for (i = 0; i < maxlen; i++)
	{
		if (fds[i].fd == -1)
		{
			fds[i].fd = fd;
			fds[i].events = env;

			break;
		}
	}

	if (i == maxlen)
	{
		return -1;
	}

	return 0;
}

int DeleteFd(struct pollfd *fds, int maxlen, int fd)
{
	int i = 0;

	for (i = 0; i < maxlen; i++)
	{
		if (fds[i].fd == fd)
		{
			fds[i].fd = -1;
			break;
		}
	}

	return 0;
}

int main(void)
{
	int sockfd = 0;
	int confd = 0;
	struct pollfd fds[1024];
	int nready = 0;
	int i = 0;
	int ret = 0;

	sockfd = CreateListenSocket("192.168.1.183", 50000);
	
	InitFds(fds, 1024);
	AddFd(fds, 1024, sockfd, POLLIN);

	while (1)
	{
		nready = poll(fds, 1024, -1);
		if (-1 == nready)
		{
			perror("fail to poll");
			return -1;
		}
		
		for (i = 0; i < 1024; i++)
		{
			if (fds[i].fd == -1)
			{
				continue;
			}

			if (fds[i].revents & POLLIN && fds[i].fd == sockfd)
			{
				confd = accept(sockfd, NULL, NULL);
				if (-1 == confd)
				{
					perror("fail to accept");
					DeleteFd(fds, 1024, sockfd);
					close(sockfd);
					continue;
				}
				
				AddFd(fds, 1024, confd, POLLIN);
			}
			else if (fds[i].revents & POLLIN && fds[i].fd != sockfd)
			{
				ret = HandleTcpClient(fds[i].fd);
				if (-1 == ret)
				{
					fprintf(stderr, "handle tcp client failed!\n");
					close(fds[i].fd);
					DeleteFd(fds, 1024, fds[i].fd);
					continue;
				}
				else if (0 == ret)
				{
					fprintf(stderr, "client disconnected!\n");
					close(fds[i].fd);
					DeleteFd(fds, 1024, fds[i].fd);
					continue;
				}
			}
		}
	}

	close(sockfd);

	return 0;
}

4.使用epoll实现tcp客户端并发

服务器:

#include "head.h"

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

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
	{
		perror("fail to socket");
		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)
	{
		perror("fail to bind");
		return -1;
	}

	ret = listen(sockfd, 10);
	if (-1 == ret)
	{
		perror("fail to listen");
		return -1;
	}

	return sockfd;
}

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

	memset(tmpbuff, 0, sizeof(tmpbuff));
	nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
	if (-1 == nsize)
	{
		perror("fail to recv");
		return -1;
	}
	else if (0 == nsize)
	{
		return 0;
	}

	sprintf(tmpbuff, "%s ----echo", tmpbuff);
	nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
	if (-1 == nsize)
	{
		perror("fail to send");
		return -1;
	}

	return nsize;
}

int AddFd(int epfd, int fd, uint32_t env)
{
	struct epoll_event tmpenv;
	int ret = 0;
	
	tmpenv.events = env;
	tmpenv.data.fd = fd;
	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &tmpenv);
	if (-1 == ret)
	{
		perror("fail to epoll_ctl");
		return -1;
	}

	return 0;
}

int DeleteFd(int epfd, int fd)
{
	int ret = 0;

	ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
	if (-1 == ret)
	{
		perror("fail to epoll_ctl");
		return -1;
	}

	return 0;
}

int main(void)
{
	int sockfd = 0;
	int confd = 0;
	int epfd = 0;
	int nready = 0;
	int i = 0;
	int ret = 0;
	struct epoll_event retenv[1024];

	sockfd = CreateListenSocket("192.168.1.183", 50000);
	
	epfd = epoll_create(1024);
	if (-1 == epfd)
	{
		perror("fail to epoll_create");
		return -1;
	}

	AddFd(epfd, sockfd, EPOLLIN);

	while (1)
	{
		nready = epoll_wait(epfd, retenv, 1024, -1);
		if (-1 == nready)
		{
			perror("fail to epoll_wait");
			return -1;
		}
		
		for (i = 0; i < nready; i++)
		{
			if (retenv[i].data.fd == sockfd)
			{
				confd = accept(sockfd, NULL, NULL);
				if (-1 == confd)
				{
					perror("fail to accept");
					DeleteFd(epfd, sockfd);
					close(sockfd);
					continue;
				}

				AddFd(epfd, confd, EPOLLIN);
			}
			else if (retenv[i].data.fd != sockfd)
			{
				ret = HandleTcpClient(retenv[i].data.fd);
				if (-1 == ret)
				{
					fprintf(stderr, "handle tcp client failed!\n");
					DeleteFd(epfd, retenv[i].data.fd);
					close(retenv[i].data.fd);
					continue;
				}
				else if (0 == ret)
				{
					fprintf(stderr, "tcp client disconnected!\n");
					DeleteFd(epfd, retenv[i].data.fd);
					close(retenv[i].data.fd);
					continue;
				}
			}
		}
	}

	close(epfd);
	close(sockfd);

	return 0;
}


 

select、poll、epoll之间的区别:

http://t.csdnimg.cn/WSHT7

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值