linux网络编程(10)fcntl函数实现IO多路转换

前面介绍的函数如recv、send、read和write等函数都是阻塞性函数,若资源没有准备好,则调用函数的进程进入阻塞状态,使用fcntl函数可以实现IO的非阻塞,也就是所谓的IO多路转换。

主线程负责接收客户端请求,收到请求后将对应的套接字描述符存放在动态数组中,一个子线程负责遍历该套接字描述符的动态数组,并处理对应套接字的客户端请求。

动态数组头文件vector_fd.h:

#ifndef __VECTOR_H__
#define __VECTOR_H__

typedef struct 
{
	int *fd; //套接字描述符的指针
	int counter; //当前套接字描述符的个数
	int max_counter; //套接字描述符的最大存放个数
}VectorFD;

extern VectorFD* create_vector_fd(void);
extern void      destroy_vector_fd(VectorFD *);
extern int       get_fd(VectorFD *, int index);
extern void      remove_fd(VectorFD *, int fd);
extern void      add_fd(VectorFD *, int fd);


#endif

动态数组源文件vector_fd.cpp

#include "vector_fd.h"
#include <malloc.h>
#include <assert.h>
#include <stdlib.h>
#include <memory.h>


/*扩展动态数组*/
static void encapacity(VectorFD *vfd)
{
	//动态数组的数量大于动态数组的最大值
	if (vfd->counter >= vfd->max_counter)
	{
		//默认每次扩展5个
		int *fds = (int*)calloc(vfd->counter + 5, sizeof(int));
		assert(fds != NULL);
		//将原来动态数组的内容拷贝到新的动态数组中
		memcpy(fds, vfd->fd, sizeof(int) * vfd->counter);
		//销毁原来套接字描述符的动态数组
		free(vfd->fd);
		//新的动态数组赋值
		vfd->fd = fds;
		//动态数组最大容量重置
		vfd->max_counter += 5;
	}
}

/*获取套接字描述符的索引*/
static int indexof(VectorFD *vfd, int fd)
{
	int i = 0;
	for (; i < vfd->counter; i++)
	{
		if (vfd->fd[i] == fd)
			return i;
	}
	//没有找到返回-1
	return -1;
}

/*创建动态数组*/
VectorFD* create_vector_fd(void)
{
	VectorFD *vfd = (VectorFD*)calloc(1, sizeof(VectorFD));
	assert(vfd != NULL);
	//创建存放套接字描述符的动态数组
	vfd->fd = (int*)calloc(5, sizeof(int));
	assert(vfd != NULL);
	vfd->counter = 0;
	vfd->max_counter = 0;
	return vfd;
}

/*释放动态数组*/
void destroy_vector_fd(VectorFD *vfd)
{
	assert(vfd != NULL);
	free(vfd->fd);
	free(vfd);
}

/*根据索引获取套接字描述符*/
int get_fd(VectorFD *vfd, int index)
{
	assert(vfd != NULL);
	if (index < 0 || index > vfd->counter - 1)
		return 0;
	return vfd->fd[index];
}

/*删除指定的套接字描述符*/
void remove_fd(VectorFD *vfd, int fd)
{
	assert(vfd != NULL);
	//获取套接字描述符的索引
	int index = indexof(vfd, fd);
	int i = index;
	//将索引后面的套接字描述符向前移动一个位置
	for (; i < vfd->counter - 1; i++)
	{
		vfd->fd[i] = vfd->fd[i + 1];
	}
	//数量减一
	vfd->counter--;
}

/*将套接字描述符添加到套接字描述符的动态数组中*/
void add_fd(VectorFD * vfd, int fd)
{
	assert(vfd != NULL);
	//扩展动态数组
	encapacity(vfd);
	vfd->fd[vfd->counter++] = fd;

}

服务器代码:

#include <netdb.h>  
#include <sys/socket.h>  
#include <time.h>  
#include <unistd.h>  
#include <memory.h>  
#include <signal.h>  
#include <string.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <arpa/inet.h>  
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include "vector_fd.h"
#include <fcntl.h>

VectorFD *vfd;
int sockfd;

/*信号处理函数*/
void sig_handler(int signo)
{
	if (signo == SIGINT)
	{
		printf("server close!\n");
		/*关闭socket*/
		close(sockfd);
		/*销毁动态数组*/
		destroy_vector_fd(vfd);
		exit(1);
	}
}

//fd对应某个连接的客户端,和某个连接的客户端进行通信(非阻塞)
void do_service(int fd)
{
	/*与客户端进行读写操作*/
	char buff[512];
	
	memset(buff, 0, sizeof(buff));

	//采用非阻塞方式,读不到数据直接返回,直接服务下一个客户端,
	//不需要判断小于0的情况
	size_t size = read(fd, buff, sizeof(buff));
	if (size == 0)
	{
		//客户端已经关闭连接
		char info[] = "client closed";
		//不建议用标准IO,因为标准IO有缓存功能
		write(STDOUT_FILENO, info, sizeof(info));
		//删除动态数组中的fd,
		remove_fd(vfd, fd);
	    //关闭对应客户端的socket
		close(fd);
	}
	else if(size > 0)
	{
		write(STDOUT_FILENO, buff, sizeof(buff));
		if (write(fd, buff, size) < 0)
		{
			//假如客户端关闭,
			if (errno == EPIPE)
			{
				perror("write error");
				remove_fd(vfd, fd);
				close(fd);
			}
		}
	}
}

/*线程函数*/
void* th_fn(void *arg)
{
	int i = 0;
	while (1)
	{
		i = 0;
		//遍历动态数组中的socket描述符
		for (; i < vfd->counter; i++)
		{
			do_service(get_fd(vfd, i));
		}
	}

	return (void*)0;
}


void out_addr(struct sockaddr_in *clientaddr)
{
	char ip[16];
	memset(ip, 0, sizeof(ip));
	int port = ntohs(clientaddr->sin_port);
	inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
	printf("%s(%d) connected!\n", ip, port);
}

int main(int argc, char* argv[])
{
	/*输入端口号*/
	if (argc < 2)
	{
		printf("usage: %s #port\n", argv[0]);
		exit(1);
	}

	/*注册SIFINT信号SIGINT,“ctrl + c”终止程序运行*/
	if (signal(SIGINT, sig_handler) == SIG_ERR)
	{
		perror("signal sigint error");
		exit(1);
	}

	/*1.创建socket
	* 注:socket创建在内核中,是一个结构体
	* AF_INET:IPV4
	* SOCK_STREAM:tcp协议
	* */
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1)
	{
		perror("socket error");
		exit(1);
	}

	/*2.将socket和地址(ip、port)进行绑定*/
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	//地址中填入ip、port、internet地质族类型  
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons((uint16_t)atoi(argv[1]));//转换为网络字节序
	serveraddr.sin_addr.s_addr = htons(INADDR_ANY);//响应所有的网卡请求  

	if (bind(sockfd, (struct sockaddr*)(&serveraddr), sizeof(serveraddr)) < 0)//注意地址要强制转换为通用地址  
	{
		perror("bind error");
		exit(1);
	}

	/*3.调用listen函数启动监听(指定的端口监听)
	* 通知系统去接收来自客户端的连接请求
	* (将接收到的客户端的请求放置到对应的队列中)
	*第二个参数:指定队列的长度
	* */
	if (listen(sockfd, 10) < 0)
	{
		perror("listen error");
		exit(1);
	}
	
	/*创建存放套接字描述符的动态数组*/
	vfd = create_vector_fd();
	
	pthread_t th;
	int err;
	/*设置线程的分离属性*/
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

	//以分离状态启动子进程
	if ((err = pthread_create(&th, &attr, th_fn, (void*)0)) != 0)
	{
		perror("pthread create error");
	}

	//主控线程销毁线程属性
	pthread_attr_destroy(&attr);

	/*
	* 1)主控线程获得客户端的连接,将新的socket描述符放置到动态数组中;
	* 2)启动的子线程负责遍历动态数组的socket描述符并和对应的客户端进行通信(采用非阻塞的读写)
	*/

	struct sockaddr_in clientaddr;
	socklen_t len = sizeof(clientaddr);

	while (1)
	{
		//主控线程负责调用accept去获得客户端的连接
		int fd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
		if (fd < 0)
		{
			perror("accept error");
			continue;
		}

		out_addr(&clientaddr);

		//将读写修改为非阻塞方式
		int var;
		//获得原来套接字状态标志,放在var中
		fcntl(fd, F_GETFL, &var);
		var |= O_NONBLOCK; //在原来状态标志基础上添加非阻塞标志
		fcntl(fd, F_SETFL, var);//设置非阻塞
		
		//将返回的新的套接字描述符加入到动态数组中
		add_fd(vfd, fd);
	}

	return 0;
}

客户端代码可参考之前博文的代码。

猜你喜欢

转载自blog.csdn.net/weicao1990/article/details/80868237