I/O复用——select(二)

使用select方法实现一个简单的TCP服务器端

在服务器端采用单链表来存储监听套接字和连接套接字,以便再次调用select时方便对readfds进行初始化,在此份代码中将只关注可读事件,且timeout为NULL,即永久堵塞

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/select.h>


/*
**定义一个单链表来存储文件描述符
*/
typedef struct ListNode
{
	int fd;
	struct ListNode *next;
}ListNode;

ListNode Head; // 头节点

// 初始化
void InitList()
{
	Head.next = NULL;
}

// 尾插
void Push_back(int fd)
{
	ListNode *p = &Head;
	while (p->next != NULL)
	{
		p = p->next;
	}

	ListNode *s = (ListNode*)malloc(sizeof(ListNode));
	s->fd = fd;
	s->next = NULL;

	p->next = s;
}

// 按fd删除
void DeleteNode(int fd)
{
	ListNode *p = &Head;
	while (p != NULL)
	{
		if (p->next != NULL && p->next->fd == fd)
		{
			ListNode *q = p->next;
			p->next = q->next;
			free(q);
			break;
		}

		p = p->next;
	}
}

// 销毁
void DestoryLink()
{
	ListNode *p = &Head;
	while (p->next != NULL)
	{
		ListNode *q = p->next;
		p->next = q->next;
		free(q);
	}
}


/*
** select只能关注三种事件类型:可读  可写  异常
** select在线修改fd_set结构  -->  每次调用select都需要重新设置fd_set结构体变量
*/

// 设置sockfd套接字:socket()  bind()  listen()
int InitSocket()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1)
	{
		return - 1;
	}

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

	int res = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
	if (res == -1)
	{
		return -1;
	}

	res = listen(sockfd, 5);
	if (res == -1)
	{
		return -1;
	}

	return sockfd;
}

// 将用户程序记录的所有文件描述符设置到fds上,并且记录最大的文件描述符返回
int SetFds(fd_set *fds)
{
	FD_ZERO(fds);  // 将fds的所有位全清空
	int maxfd = -1;

	ListNode *p = Head.next;
	while (p != NULL)
	{
		FD_SET(p->fd, fds);  // 填充fds
		if (p->fd > maxfd)  // 找到最大的文件描述符
		{
			maxfd = p->fd;
		}

		p = p->next;
	}

	return maxfd; // 将最大的文件描述符的值返回
}

// 处理就绪的事件
void DealFinishEvent(fd_set *fds, int sockfd)
{
	ListNode *p = Head.next;
	while (p != NULL)
	{
		if (FD_ISSET(p->fd, fds)) // 有事件就绪
		{
			if (p->fd == sockfd) // 有新的客户端链接
			{
				// 将c添加到用户链表中
				struct sockaddr_in cli;
				socklen_t len = sizeof(cli);
				int c = accept(sockfd, (struct sockaddr*)&cli, &len); // c:链接套接字
				if (c < 0)
				{
					continue;
				}

				Push_back(c);
			}
			else    // 此客户端链接套接字有数据到达
			{
				// 接收数据
				char buff[128] = { 0 };
				int n = recv(p->fd, buff, 127, 0);
				if (n <= 0)  // 出错或者客户端关闭
				{
					close(p->fd);
					DeleteNode(p->fd);
					continue;
				}

				printf("%d: %s\n", p->fd, buff);
				send(p->fd, "OK", 2, 0);
			}
		}

		p = p->next;
	}
}

int main()
{
	InitList();

	int sockfd = InitSocket(); // 监听套接字,有客户端连接时会触发读事件
	assert(sockfd != -1);

	Push_back(sockfd); // 将sockfd添加到用户链表中

	fd_set readfds; // 只关注可读事件

	while (1)
	{
		int maxfd = SetFds(&readfds);
		int n = select(maxfd+1, &readfds, NULL, NULL, NULL);
		if (n <= 0)
		{
			printf("select error\n");
			continue;
		}

		DealFinishEvent(&readfds, sockfd);
	}

	close(sockfd);
	DestoryLink();
	exit(0);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值