本文最后更新于:2020年7月2日 晚上

* 趁着刚刚总结完select的内核源码。。。现在就来应用一下。。。→_→ *

深入理解select底层原理请戳传送门——IO复用——select内核源代码剖析

本次网络通信socket套接字是基于TCP协议的可靠传输,因为只开了一台虚拟机。。。所以服务器IP地址采用回环地址——127.0.0.1,服务器端口号port为6000

server

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAX_FD 10 //设定最多监听的文件描述符的数量为10
int fds[MAX_FD]; //存储多个文件描述符的数组

//初始化文件描述符数组
void fds_init()
{
	int i = 0;
	for(;i<MAX_FD;i++)
	{
		fds[i] = -1; //将所有的元素初始化为-1
	}
}

//在文件描述符数组中寻找第一个值为-1的元素,即此元素存放新添加的文件描述符
void fds_add(int fd)
{
	int i = 0;
	for(;i<MAX_FD;i++)
	{
		if(fds[i] == -1)
		{
			fds[i] = fd;
			break;
		}
	}
}

//在文件描述符数组中寻找指定值的元素,将其删除,即将该元素置为-1
void fds_del(int fd)
{
	int i = 0;
	for(;i<MAX_FD;i++)
	{
		if(fds[i] == fd)
		{
			fds[i] = -1;
			break;
		}
	}
}

int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1);

	struct sockaddr_in saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
	assert(res != -1);

	listen(sockfd,5);
	
	//这里只对读事件进行验证,所以只定义一个fd_set结构
	fd_set fdset;
	//初始化存放文件描述符的数组
	fds_init();

	fds_add(sockfd); //将socket也作为要监听的文件描述符
	
	//服务器开始进入while循环
	while(1)
	{
		//因为调用select后,有可能改变文件描述符在fdset位图中的标志,所以每一次开始监听之前都要重新轻灵标志
		FD_ZERO(&fdset);
		int maxfd = -1; //记录当前要监听的文件描述符的最大值

		int i = 0;
		for(; i < MAX_FD; ++i)
		{
			if(fds[i] != -1) //如果文件描述符数组中的元素不为-1,就将此元素设置到fds位图中
			{
				FD_SET(fds[i], &fdset);
				if(fds[i] > maxfd)
				{
					maxfd = fds[i];
				}
			}
		}
		//设置浅睡眠时间为5毫秒整
		struct timeval tv = {5,0};
		
		//返回值为一共就绪事件的总和
		int n = select(maxfd + 1, &fdset, NULL, NULL, &tv);
		if(n == -1) //如果返回值为-1,表示出错
		{
			perror("select error");
			continue;
		}
		else if(n == 0) //如果返回值为0,表示睡眠期间没有事件就绪,继续循环监听
		{
			printf("time out\n");
			continue;
		}
		else
		{
			int i = 0;
			//再一次遍历文件描述符数组,查看哪一个是就绪事件
			for(; i < MAX_FD; ++i)
			{
				if(fds[i] == -1)
				{
					continue;
				}
				//测试此文件描述符元素在位图中对应的位是否被设置
				if(FD_ISSET(fds[i], &fdset))
				{
					//如果就绪的是sockfd,表示有了新连接
					if(sockfd == fds[i])
					{
						int len = sizeof(caddr);
						int c = accept(sockfd, (struct sockaddr *)&caddr, &len);

						if(c < 0)
						{
							continue;
						}
						fds_add(c);
					}
					else //否则就读取就绪事件的数据
					{
						char buff[128] = {0};
						//如果返回值小于等于0,表示连接中断或出错
						if(recv(fds[i], buff, 127, 0) <= 0)
						{
							//此时需要关闭该连接
							close(fds[i]);
							//并从文件描述符数组中删除此元素
							fds_del(fds[i]);
							printf("one client over\n");
							continue;
						}
						printf("read:%s\n",buff);
						//读取全部数据后,向客户端发送"ok"
						send(fds[i], "ok", 2, 0);
					}
				}
			}
		}
	}

	return 0;
}

client

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

int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1);

	struct sockaddr_in saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);
	listen(sockfd,5);
	while(1)
	{
		printf("input:\n");
		//输入缓冲区
		char buff[128] = {0};
		fgets(buff,128,stdin);
	    
	    //如果输入的数据为"end",表示要关闭客户端,此时跳出循环
  		if(strncmp(buff,"end",3 ) == 0)
		{ 
			break;
		}
		//否则就发送所输入的数据
		send(sockfd,buff,strlen(buff),0);
		//再将缓冲区清零
		memset(buff,0,128);
		//接收服务器端的回复
		recv(sockfd,buff,127,0);
		printf("buff = %s\n",buff);
	}
	//关闭该连接
    close(sockfd); 
    return 0;
}

运行结果

IO复用select

* 其实这些代码是之前写的。。。一会就来总结poll机制。。。→_→ *