【网络开发及应用--I/O多路复用创建简单服务器和客户端】

五种I/O模型:

1、阻塞式I/O:最流行的I/O模型,默认情况下所有套接字都是阻塞的,此模式对系统友好
2、非阻塞式I/O:不会进行休眠等待资源,返回一个错误继续运行后面程序,
内核会循环判断有没有相关资源,若没有则报错,较浪费内核资源,此模式对人比较友好
3、I/O复用:同时阻塞多个I/O,只要发现一个触发,都会执行后面的程序
4、信号驱动式I/O:内核在描述符就绪时会发送SIGIO子女好通知
5、给内核传递描述符、缓冲区指针、缓冲区大小和文件偏移。通知内核完成并返回通知

以上模型(除异步I/O外)在数据传输过程(将数据从内核空间复制到用户空间)中均为阻塞;例如:read在读取过程中为阻塞过程

select()函数:

原型:int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *excsptfds, struct timeout);
参数:
    nfds:最大文件描述符加1
    readfds:文件描述符读表
    writefds:文件描述符写表
    exceptfds:文件描述符异常处理表
    timeout:超时设置,-1为不设置定时,以微秒为单位
    一般程序员都会使用这个函数进行定时(比较好用)
此函数使用流程较为繁琐,且最大记录只有1024,推荐使用poll()函数

使用select()创建服务器

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>

#define BUF_N 1024
#define LISTEN_MAX 5
#define PORT 8888
#define SERVER_IP "192.168.113.129"
#define DEF_VAL 0
#define ERR_VAL -1
#define ERR_LOG(val) do{perror(val);exit(EXIT_FAILURE);}while(0);

int main(int argc, const char * argv[])
{
	int ret = 0;

	//创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, DEF_VAL);
	if (ERR_VAL == sockfd)
	{
		ERR_LOG("socket");
	}

	printf("====create socket successful	sockfd: %d\n",sockfd);

	struct sockaddr_in caddr;
	memset(&caddr, 0, sizeof(caddr));
	socklen_t caddr_len = sizeof(caddr);

	struct sockaddr_in saddr;
	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(PORT);
	saddr.sin_addr.s_addr = INADDR_ANY;

	//绑定本机地址与端口号
	ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
	if (ERR_VAL == ret)
	{
		ERR_LOG("bind");
	}

	printf("====bind sucessful\n");

	//设置监听套接字
	ret = listen(sockfd, LISTEN_MAX);	//将套接字转换为被动套接字
	if (ERR_VAL == ret)
	{
		ERR_LOG("listen");
	}
	printf("====listen successful\n");

	printf("wait client connect...\n");

	//创建位图并清空
	fd_set fds, tmp;
	FD_ZERO(&fds);
	FD_SET(sockfd, &fds);
	int nfds = sockfd + 1;
	tmp = fds;

	//超时
	struct timeval t = {5, 0};

	int connfd = 0;
	char dst[20] = {0};

	//循环等待客户端连接
	while(1)
	{
		fds = tmp;//更新位图
		
		//超时重置
		t.tv_sec = 5;
		t.tv_usec = 0;

		//阻塞检测监听可用套接字
		int ret = select(nfds, &fds, NULL, NULL, &t);
		if (-1 == ret)
		{
			ERR_LOG("select");
		}
		else if (0 == ret)
		{
			printf("Timeout!\n");
			continue;
		}

		//遍历返回的位图,找到位为1的套接字
		for (int i = 3; i < nfds; i++)
		{
			if (!FD_ISSET(i, &fds))
			{
				continue;	//未为0时跳过该位
			}

			//如果监听到的位为sockfd,建立一个新的通信连接
			if (i == sockfd)
			{
				//阻塞等待客户端连接、套接字必须为被动套接字,否则报错
				connfd = accept(sockfd, (struct sockaddr *)&caddr, &caddr_len);	
				if (ERR_VAL == connfd)
				{
					ERR_LOG("accept");
				}

				printf("connfd: %d\n", connfd);
				printf("====accept successful %s : %d\n",
						inet_ntop(AF_INET, &caddr.sin_addr, dst, sizeof(dst)),
						ntohs(caddr.sin_port));

				//添加connfd套接字到临时表
				FD_SET(connfd, &tmp);

				//判断并更新nfds
				if (connfd + 1 != nfds)
				{
					nfds = connfd + 1;
				}
				
			}
			//否则为connfd套接字,进行通信
			else
			{
				char buf[BUF_N] = {0};

				//数据发送与接收
				int len = read(i, buf, sizeof(buf));
				if (len <= DEF_VAL)
				{
					printf("client exit...\n");
					close(i);
					FD_CLR(i, &tmp);	//清空表
					if(i + 1 == nfds)
					{
						--nfds;
					}
					continue;	//退出后继续下一个通信
				}
				write(i, buf, strlen(buf));
				printf("client send massage: %s", buf);
			}
		}
	}

	close(sockfd);
	return 0;
}

poll()函数:

原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
    fds:需要监听的所有文件描述符结构体的数组
    nfds:数组内有效最大个数
    timeout:超时时间,-1为不设置超时,以毫秒为单位计时

使用poll()创建服务器

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <poll.h>

#define BUF_N 1024
#define LISTEN_MAX 5
#define PORT 8888
#define SERVER_IP "192.168.113.129"
#define DEF_VAL 0
#define ERR_VAL -1
#define ERR_LOG(val) do{perror(val);exit(EXIT_FAILURE);}while(0);

int main(int argc, const char * argv[])
{
	int ret = 0;

	//创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, DEF_VAL);
	if (ERR_VAL == sockfd)
	{
		ERR_LOG("socket");
	}

	printf("====create socket successful	sockfd: %d\n",sockfd);

	struct sockaddr_in caddr;
	memset(&caddr, 0, sizeof(caddr));
	socklen_t caddr_len = sizeof(caddr);

	struct sockaddr_in saddr;
	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(PORT);
	saddr.sin_addr.s_addr = INADDR_ANY;

	//绑定本机地址与端口号
	ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
	if (ERR_VAL == ret)
	{
		ERR_LOG("bind");
	}

	printf("====bind sucessful\n");

	//设置监听套接字
	ret = listen(sockfd, LISTEN_MAX);	//将套接字转换为被动套接字
	if (ERR_VAL == ret)
	{
		ERR_LOG("listen");
	}
	printf("====listen successful\n");

	printf("wait client connect...\n");

	//创建数组
	struct pollfd fds[20];

	//将sockfd加入到数组中
	fds[0].fd = sockfd;
	fds[0].events = POLLIN;
	int nfds = 1;

	int connfd = 0;
	char dst[20] = {0};

	//循环等待客户端连接
	while(1)
	{
		

		//阻塞检测监听可用套接字
		int ret = poll(fds, nfds, -1);
		if (-1 == ret)
		{
			ERR_LOG("poll");
		}

		//阻塞循环检测每一个位
		for (int i = 0; i < nfds; i++)
		{
			if (!(fds[i].revents & POLLIN))
			{
				continue;	//该为未置0,则跳过循环
			}

			//如果监听到的位为sockfd,建立一个新的通信连接
			if (fds[i].fd == sockfd)
			{
				//阻塞等待客户端连接、套接字必须为被动套接字,否则报错
				connfd = accept(sockfd, (struct sockaddr *)&caddr, &caddr_len);	
				if (ERR_VAL == connfd)
				{
					ERR_LOG("accept");
				}

				printf("connfd: %d\n", connfd);
				printf("====accept successful %s : %d\n",
						inet_ntop(AF_INET, &caddr.sin_addr, dst, sizeof(dst)),
						ntohs(caddr.sin_port));

				//添加connfd套接字
				fds[nfds].fd = connfd;

				//更新事件
				fds[nfds].events = POLLIN;
				nfds++;

				printf("nfds: %d\tconnfd: %d\n", nfds, connfd);
				
			}
			//否则为connfd套接字,进行通信
			else
			{
				char buf[BUF_N] = {0};

				//数据发送与接收
				int len = read(fds[i].fd, buf, sizeof(buf));
				if (len <= DEF_VAL)
				{
					printf("client exit...\n");
					//关闭套接字
					close(fds[i].fd);
					//将数组有效位的最后一个套接字移到退出位置的套接字
					fds[i] = fds[nfds - 1];
					fds[nfds].fd = -1;
					nfds--;
					continue;	//退出后继续下一个通信
				}
				write(fds[i].fd, buf, strlen(buf));
				printf("client send massage: %s", buf);
			}
		}
	}

	close(sockfd);
	return 0;
}

使用poll()创建客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <poll.h>

#define BUF_N 1024
#define DEF_VAL 0
#define ERR_VAL -1
#define ERR_LOG(val) do{perror(val);exit(EXIT_FAILURE);}while(0);

int main(int argc, const char * argv[])
{
	int ret = 0;

	if (argc != 3)
	{
		fprintf(stderr, "Usage %s filename\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	
	int port = atoi(argv[2]);

	//创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, DEF_VAL);
	if (ERR_VAL == sockfd)
	{
		ERR_LOG("socket");
	}

	printf("====create socket successful	sockfd: %d\n",sockfd);

	struct sockaddr_in saddr;
	memset(&saddr, 0, sizeof(saddr));

	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(port);
	ret = inet_pton(AF_INET, argv[1] ,&saddr.sin_addr);
	if (ERR_VAL == ret)
	{
		ERR_LOG("inet_pton");
	}

	//向服务器发起连接
	ret = connect(sockfd, (const struct sockaddr *) &saddr, sizeof(saddr));
	if (ERR_VAL == ret)
	{
		close(sockfd);
		ERR_LOG("connect");
	}
	printf("====connect successful\n");


	//创建监听文件描述符的数组
	struct pollfd fds[2];

	//设置标准输入文件描述符
	fds[0].fd = STDIN_FILENO;
	fds[0].events = POLLIN;

	//设置套接字文件描述符
	fds[1].fd = sockfd;
	fds[1].events = POLLIN;

	int nfds = 2;

	char buf[BUF_N] = {0};
	//数据发送接收
	while(1)
	{
		int ret = poll(fds, nfds, -1);
		if (ERR_VAL == ret)
		{
			ERR_LOG("poll");
		}
			if (fds[0].events & POLLIN)
			{
				read(STDIN_FILENO, buf, BUF_N);
				ret = send(sockfd, buf, strlen(buf), 0);
				if (ERR_VAL == ret)
				{
					ERR_LOG("send");
				}
			}
			if (fds[1].events & POLLIN)
			{
				memset(buf, 0, sizeof(buf));
				ret = recv(sockfd, buf, sizeof(buf), 0);
				if (ERR_VAL == ret)
				{
					ERR_LOG("recv");
				}
				printf("from server massage: %s", buf);
				memset(buf, 0, sizeof(buf));

			}
	}

	close(sockfd);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太阳请了个假

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值