IO复用(c)

其他关联文章@丶4ut15m:

TCP网络编程(c)

UDP网络编程(c)

多进程并发服务器(c)

多线程并发服务器(c)

IO复用

我的作业照片之前删掉了.并且之前也没有特地给IO复用写复习用的东西,只剩下一个给别人讲IO复用时候写的草稿和代码(所幸我注释还挺多)了,看的重点放代码上吧.

select函数,fd_set

fd_set存放所有的套接字

fd_set all_set;

listenfd

FD_SET(listenfd,&all_set);

already = select(nfds,read_set,write_set,exception_set,timeout);

fd_set存放所有的套接字

用select来获取可操作的套接字

当listenfd可读时候,说明有新连接到达

当connectfd可读的时候,说明有新的数据到达

typedef struct {
	int connectfd;
	struct sockaddr_in client;
	char *cli_name;
	char *cli_data;
}CLIENT;

while(1){

	read_set =all_set;

对于监听套接字:
if(FD_ISSET(listenfd,&read_set)){
	//接收连接
	
	FD_SET(connectfd,&all_set);
}

for(i=0;i<FD_SETSIZE;i++){//遍历所有的套接字

if(FD_ISSET(i,&read_set)){
	//和客户端交互
	process_cli();
}

}

}

实例如下

服务器:

	服务器等待接收客户的连接请求,一旦连接成功则显示客户地址;
	接着接收客户端的名称并显示;
	然后接收来自该客户的字符串,对接收的字符串按分组进行加密(分组长度为个人学号,密钥为个人序号,分组不够补0),再将加密后的字符发回客户端;
	之后继续等待接收该客户的信息,直到客户关闭连接,服务器将每个连接的用户所发来的所有数据存储起来;
	当连接终止后,服务器将显示客户的名字及相应的所有数据。
	要求服务器具有同时处理多个客户请求的能力。

客户端:

	客户首先与相应的服务器建立连接;
	接着接收用户输入的客户端名称,并将其发送给服务器;
	然后继续接收用户输入的字符,再将字符串发送给服务器,同时接收服务器发回的加密后的字符串并显示。
	之后,继续等待用户输入字符串,指导用户输入的是quit,则关闭连接并退出。

使用IO复用实现

服务器代码如下

#include <stdio.h>						//基本输入输出
#include <stdlib.h>						//exit函数所在头文件
#include <string.h>						//字符串处理函数
#include <sys/socket.h>					//套接字函数所在头文件
#include <arpa/inet.h>					//地址结构所在头文件
#include <unistd.h>						//close函数所在头文件
#include <sys/time.h>					//select函数所在头文件
#include <netinet/in.h>

#define PORT 3333
#define MAXDATASIZE 1000

typedef struct {
	int connectfd;				//存放已连接套接字
	char *cli_name;				//char指针类型,用以指向后面malloc的区域
	char *cli_data;
	struct sockaddr_in addr;	//客户端地址结构
}CLIENT;

//声明函数
void process_cli(CLIENT *client,char *recvbuf,int len);
void savedata(char *recvbuf,int len,char *data);
void encode(char *data,int len,int connectfd);

int main(void){
	int listenfd,connectfd,sockfd;			//前者为监听套接字,中者为已连接套接字,后者用来暂存已连接套接字
	int maxfd,maxi,i;						//前者始终存放最大文件描述符(套接字),中者存放已连接数量,后者用来作为遍历结构体的索引
	int already,num;						//前者用来存放select返回的可操作文件描述符数量,后者用来存放接收数据的大小
	char recvbuf[100];						//暂存客户端发送的数据
	struct sockaddr_in server;				//地址结构
	fd_set all_set,read_set;				//前者为文件描述符集合,后者为可读文件描述符集合
	CLIENT client[FD_SETSIZE];				//客户端结构体,新连接的客户端信息存放在该结构体中
	socklen_t addrlen;					

	//创建套接字
	if((listenfd = socket(AF_INET,SOCK_STREAM,0)) ==-1){
		perror("Socket error!\n");
		exit(0);
	}

	//地址重用
	int opt = SO_REUSEADDR;
	setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	//配置地址结构体
	memset(&server,'\0',sizeof(server));
	server.sin_port = htons(PORT);
	server.sin_addr.s_addr = htonl(INADDR_ANY);
	server.sin_family = AF_INET;

	//绑定地址结构
	if(bind(listenfd,(struct sockaddr *)&server,sizeof(server))){
		perror("Bind error!\n");
		exit(0);
	}

	//监听套接字
	if(listen(listenfd,5)){
		perror("Listen error!\n");
		exit(0);
	}

	//初始化select函数参数
	maxfd = listenfd;						//maxfd始终等于最大的文件描述符
	maxi = -1;								//还未取得任何连接,故使maxi等于-1
	for(i = 0; i < FD_SETSIZE; i++){
		//遍历所有客户端结构体,并将之已连接套接字置为-1以代表该结构体未被使用
		client[i].connectfd = -1;
	}

	FD_ZERO(&all_set);						//将文件描述符集合清零
	FD_SET(listenfd,&all_set);				//将listenfd添加进all_set中

	addrlen = sizeof(struct sockaddr_in);	//存放结构体sockaddr_in的大小,下面取得连接时有用

	while(1){
		struct sockaddr_in addr;			//用来暂存取得连接时候的客户端地址结构
		read_set = all_set;					//拷贝all_set的内容给read_set,使用read_set取监听文件描述符的可读情况
		
		/*select函数原型
		* int select(nfds,readfds,writefds,exceptfds,timeout);
		* 第一个参数为最大文件描述符的值+1,第二个参数为可读文件描述符集合,
		* 第三个为可写文件描述符集合,第四个参数为异常文件描述符集合
		* 最后一个参数为select函数超时时间
		*/
		already = select(maxfd+1, &read_set,NULL,NULL,NULL);
		/*select的作用为:调用select函数之后它会阻塞,时长为timeout,
		* 当readfds中任一(也可以多个)文件描述符可读,或者
		* writefds文件描述符集合中任一(可以多个)文件描述符可写,或者
		* exceptfds文件描述符集合中任一(可以多个)文件描述符异常之时
		* select函数结束阻塞并且返回可操作(可操作便是可读或者可写或者异常)文件描述符的个数
		* 如果设置timeout为NULL则select函数不会阻塞,调用之后便会直接返回结果
		* 正是select函数这个可以判断多个描述符的功能,才使得我们可以单进程单线程的处理多个请求
		*/
		printf("haode\n");	
		//先判断listenfd是否可读
		if(FD_ISSET(listenfd,&read_set)){
			//如果listenfd可读则代表有新连接到达,可以处理新的连接请求
			if((connectfd = accept(listenfd,(struct sockaddr *)&addr,&addrlen)) ==-1){
				//如果出错了,不退出,进行下一次循环
				perror("Accept error!\n");
				continue;
			}

			for(i = 0; i < FD_SETSIZE; i++){
				//接受了新连接,将取得连接的套接字和客户端地址结构存放在CLIENT结构体中
				if(client[i].connectfd < 0){
					//如果client[i].connectfd <0就代表这个结构体还未被使用,可以存放新连接数据
					client[i].connectfd = connectfd;
					client[i].cli_name = (char *)malloc(sizeof(char)*MAXDATASIZE);		//malloc一段空间用来存放客户端名称
					client[i].cli_data = (char *)malloc(sizeof(char)*MAXDATASIZE);		//malloc一段空间用来存放客户端发送的所有数据
					client[i].addr = addr;					//存放客户端地址结构
					client[i].cli_name[0] = '\0';			//对申请的空间初始化
					client[i].cli_data[0] = '\0';			

					//打印客户端的ip
					printf("Got a connection from %s.\n",inet_ntoa(client[i].addr.sin_addr));
					//存放好数据之后不用再遍历客户端结构体,退出循环
					break;
				}
			}//for遍历所有客户端结构体 end

			if(i == FD_SETSIZE){
				//说明已经达到连接上限
				printf("Too many connections\n");
			}
			//将已连接套接字添加进all_set中
			FD_SET(connectfd,&all_set);
			
			if(connectfd > maxfd){
				//maxfd始终等于最大文件描述符
				maxfd = connectfd;
			}

			if(i > maxi){
				//maxi为已连接个数
				maxi = i;
			}

			if(--already <= 0){
				//说明可操作套接字已经处理完毕,开始下一次while循环
				continue;
			}
		}//listenfd可读判断 end
		
		//如果listenfd不可读或者listenfd套接字已经处理完毕,则说明有客户端发送消息过来
		for(i = 0;i<= maxi;i++){
			//遍历当前所有已连接客户端
			if((sockfd = client[i].connectfd) < 0){
				//说明这个客户端没有存放连接,继续查找下一个客户端
				continue;
			}
			if(FD_ISSET(sockfd,&read_set)){
				if((num = recv(sockfd,recvbuf,MAXDATASIZE,0)) == 0){
					//关闭客户端
					close(sockfd);
					//打印客户端信息
					printf("Client %s closed connection. All message as follow:%s\n",client[i].cli_name,client[i].cli_data);
					//将已连接套接字从文件描述符集合中清除
					FD_CLR(sockfd,&all_set);
					//将该客户端结构体中的已连接套接字置为-1说明该客户端结构体现在是空的
					client[i].connectfd = -1;
					//释放前面malloc的两段空间
					free(client[i].cli_name);
					free(client[i].cli_data);
				}
				else process_cli(&client[i], recvbuf, num);
				if(--already <=0){
					//说明可操作套接字已经处理完毕,不再遍历
					break;
				}
			}//判断已连接套接字是否可读

		}//for遍历当前已连接的客户端

	}//while end


	close(listenfd);						//关闭监听套接字
	return 0;
}

void process_cli(CLIENT *client,char *recvbuf,int len){
	recvbuf[len] = '\0';

	if(strlen(client->cli_name) == 0){
		//如果当前结构体的cli_name为空则说明是新建立的连接,客户端第一次发送过来的数据为客户端名称
		memcpy(client->cli_name,recvbuf,len);
		//打印客户端名称
		printf("The client's name is %s.\n",client->cli_name);
		//名称处理完毕回到main函数
		return;
	}
	if(strcmp(recvbuf,"quit")){
	//执行到此处说明客户端发送的不是名称而是消息,将消息进行保存
	//不是quit就保存
	savedata(recvbuf,len,client->cli_data);
	}
	//调用加密函数
	encode(recvbuf,len,client->connectfd);
}

void savedata(char *recvbuf,int len,char *data){
	//上次存放的结束为这次存放的开始
	int start = strlen(data);

	for(int i=0;i<len;i++){
		data[start +i] = recvbuf[i];
	}
}

void encode(char *data,int len,int connectfd){
	int i,j;
	int snum[] = {2,0,1,7,1,2,2,1,1,9};		//密钥
	char miwen[100];						//data用以接收用户输入的数据,miwen用以存放密文
	j = 0;

	//清空密文数组
	memset(&miwen,'\0',sizeof(miwen));

	//密钥长度为学号长度,能整除该长度就说明不需要在字符串后面补零可以直接加密
	for(i = 0; i<strlen(data); i++){
		if((data[i] >= 'a' && data[i] <='z') || (data[i] >= 'A' && data[i] <='Z')){
			//字符加密与数字加密需要分开,因为字符移位可能会越过字母的区域,需要另做操作
			if(((data[i]+snum[j]) > 'z') && (data[i] >= 'a') && (data[i] <= 'z')){
				//越界情况一
				miwen[i] = data[i] + snum[j]-26;
			}
			else if(((data[i]+snum[j])>'Z') && (data[i] >= 'A') && (data[i] <= 'Z')){
					//越界情况二
					miwen[i] = data[i] + snum[j] -26;
			}
			else{
				//这是正常情况执行的代码
				miwen[i] = data[i] + snum[j];
			}
			//每次加密结束,j增加移至下一个密钥
			j++;
			if(j %10 == 0){
				//如果j%10等于0,就说明已经加密完一轮,该让密钥回到第一个值的位置了
				j = 0;
			}
		}
	}

	if(strlen(data) % 10 != 0){
		//如果不能整除则需要填补。

		for(i=0;i<(10-strlen(data)%10);i++){

			miwen[strlen(data)+i] += 48;
			miwen[strlen(data)+i] += snum[j];
			j++;
			if(j %10 == 0){
			//如果j%10等于0,就说明已经加密完一轮,该让密钥回到第一个值的位置了
			j = 0;
			}
		}

	}
	send(connectfd,miwen,sizeof(miwen),0);				//发送密文
}


客户端代码如下

#include <stdio.h>						//基本输入输出
#include <netinet/in.h>					//sockaddr_in结构体所在头文件
#include <sys/socket.h>					//socket函数所在头文件
#include <netdb.h>						//hostent结构体所在头文件
#include <string.h>						//memset等函数所在头文件
#include <unistd.h>						//close等函数所在头文件
#include <stdlib.h>						//exit等函数所在头文件

#define PORT 3333

int main(int argc,char *args[]){
	int sockfd,num;				//定义套接字描述符
	struct sockaddr_in server;
	struct hostent *he;
	socklen_t server_len;
	char data[100];


	if(argc != 2){
		printf("Usage:%s <IP Address>\n",args[0]);
		exit(0);
	}

	//创建套接字
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		perror("Socket error!\n");
		exit(0);
	}
	
	//获取服务器信息
	if((he = gethostbyname(args[1])) == NULL){
		perror("Gethostbyname error!\n");
		exit(0);
	}

	memset(&server,'\0',sizeof(server));
	//配置服务器地址结构
	server.sin_family = AF_INET;							//配置协议族为IPv4
	server.sin_port = htons(PORT);							//配置服务器端口
	server.sin_addr = *((struct in_addr *)he->h_addr);		//服务器地址
	server_len = sizeof(server);
	
	//而后可以进行连接
	if((connect(sockfd,(struct sockaddr *)&server,sizeof(server))) ==-1){
		perror("Connect error!\n");
		exit(0);
	}

	printf("Plz enter your name:");
	memset(&data,'\0',sizeof(data));		//每次输入信息前先清空缓存,防止前次数据影响后面输入的数据
	scanf("%s",&data);
	if(send(sockfd,data,strlen(data),0) ==-1){
		//send函数原型send(int sockfd,const char FAR *datafer,int dataferlen,int flags)
		//第二个参数为要发送的数据,第三个参数为发送的大小也即是长度,第四个参数通常置0代表其功能与write相同
		printf("Send error!\n");
		exit(1);
	}

	//连接之后便可开始数据的发送与接收
	while(1){
		
		memset(&data,'\0',sizeof(data));
		printf("Now,you can enter something what you want to encode.\n");
		scanf("%s",&data);
		send(sockfd,data,sizeof(data),0);

		if(!strcmp(data,"quit")){
			break;
		}
		else{	
		memset(&data,'\0',sizeof(data));
		if((recv(sockfd,data,100,0) ==-1)){
				//recv函数原型recv(int sockfd,char *datafer,int dataferlen,int flags)
				//第二个参数为将接收的数据暂存的位置,第三个参数为接收的数据大小,第四个参数通常置0代表其功能与read相同
				printf("Recv error!\n");
				exit(1);
		}
		printf("The code message:%s\n",data);

	}

}
	/*
		if((num = recv()) == -1){
			perror("Recv error!\n");
			exit(0);
			}
			*/
	printf("Bye~\n");
	close(sockfd); <a name="#marker" id="marker"></a>
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以回答关于C语言实现epoll IO复用的问题。以下是一个基本的epoll IO复用示例代码: ``` #include <sys/epoll.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/socket.h> #define MAX_EVENTS 10 #define PORT 8080 int main () { int server_fd, new_socket, epoll_fd, n, i; struct sockaddr_in address; struct epoll_event event, events[MAX_EVENTS]; // Create a socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("Failed to create socket"); exit(EXIT_FAILURE); } // Set socket options if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { perror("Failed to set socket options"); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); // Bind the socket to a port if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("Failed to bind socket"); exit(EXIT_FAILURE); } // Listen on the socket if (listen(server_fd, 3) < 0) { perror("Failed to listen on socket"); exit(EXIT_FAILURE); } // Create epoll instance if ((epoll_fd = epoll_create1(0)) < 0) { perror("Failed to create epoll instance"); exit(EXIT_FAILURE); } // Add server socket to epoll decriptor event.events = EPOLLIN; event.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) { perror("Failed to add server socket to epoll"); exit(EXIT_FAILURE); } while (1) { n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (i = 0; i < n; i++) { if (events[i].data.fd == server_fd) { // Server socket event if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { perror("Failed to accept incoming connection"); exit(EXIT_FAILURE); } // Add new socket to epoll descriptor event.events = EPOLLIN; event.data.fd = new_socket; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &event) < 0) { perror("Failed to add new socket to epoll"); exit(EXIT_FAILURE); } } else { // Client socket event // Handle read events on the client sockets and process the // incoming data. Also handle disconnect events. } } } return 0; } ``` 希望能对您有所帮助!算了,这是一个关于蒟蒻程序员的笑话:为什么程序员总喜欢喝奶茶?因为他们喜欢借用框架!哈哈!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值