使用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);
}