文章目录
- 01 | 宏接口定义
- 02 | 使用方法
- 03 | 服务端代码示例
学习socket编程的时候看到很多FD开头的宏定义和函数,这里记录一下这些宏定义和函数的含义及处理流程
01 | 宏接口定义
-
fd_set
fd_set 是一种表示文件描述符的集合类型,在socket编程中,这种类型有三种不同的集合(可读、可写、错误)
-
FD_ZERO() | FD_CLR()
-
FD_ZERO(fd_set* fdset)
:把当前描述符集合 fd_set 中所有位的数字都置为0 -
FD_CLR(int fd, fd_set* fdset)
:清楚所绑定的联系,将 fd 从 fd_set 集合中清除,类似于链表的节点删除(后续节点会填补被删除节点)
-
-
FD_SET()
FD_SET(int fd, fd_set* fdset)
:实现了句柄和描述符集合的联系,可以把已打开的 fd(句柄)加入到描述符集合 fd_set 中 -
select()
用于查询所有描述符(句柄)所处的状态,获取当中可读可写可用的描述符个数
int select(int maxfdpl, fd_set *restrict readfds, fd_set *restrict writefds,fd_set *restrict exceptfds, struct timeval *restrict typfr); // 返回值:准备就绪的描述符数目;若超时返回0,出错则返回-1
参数说明:
-
maxfdpl:最大描述符编号 + 1,即选择需要关注的描述符个数,一般为环境中可能打开的描述符个数加一
-
readfds | writefds | exceptfds:这三个指向描述符的指针,分别代表了所要关心的 可读描述符集 | 可写描述符集 | 出错描述符集
-
typfr:超时时间
-
-
FD_ISSET()
FD_ISSET(int fd, fd_set* fdset)
:判断加入的 fd 是否还存在于描述符集合 fd_set 中,即判断打开的 fd 是否可用。不存在或不可用则返回0
02 | 使用方法
总体使用流程就是:通过调用 FD_SERO()
将一个 fd_set 变量的所有位设置为0(如果要开启描述符集合中的一位,可以调用 FD_SET()
;如果要清楚刚才设置的位则可以调用 FD_CLR()
);再通过调用 select()
对描述符集合进行筛选,得到当前可用描述符个数;最后可以调用 FD_ISSET()
测试当前描述符集合中的一个指定位是否已打开可用
简要流程如下
-
初始化一个 fd_set 类型的描述符集合
fd_set server_fd; FD_ZERO(&server_fd);
-
把 socket调用打开的 fd 放入到已初始化的描述符集合中
FD_SET(socket_fd, &server_fd);
-
通过调用
select()
查询这个描述符集合中的所有 fd 状态,并清除描述符集合中 不可读 | 不可写 的 fd,或者说选出 可读 | 可写 的 fdint max_fd = -1; if (max_fd < socket_fd) { max_fd = socket_fd; } if (select(max_fd + 1, &server_fd, NULL, NULL, &timeout) < 0) { return; }
-
判断当前打开的 fd 是否还存在于描述符集合中
if (FD_ISSET(socketfd, &server_fd)) { // To do }
-
上述步骤完成后,还可以再进行错误处理判断,防止后续错误
int iRet = 0; int len = sizeof(iRet); if (getsockopt(m_sock, SOL_SOCKET, SO_ERROR, (char*)&iRet, &len) == SOCKET_ERROR) { return; }
03 | 服务端代码示例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
#include<errno.h>
int main()
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd < 0)
{
perror("socket error");
return -1;
}
struct sockaddr_in serv;
bzero(&serv, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
if(ret < 0)
{
perror("bind error");
return -1;
}
listen(lfd, 128);
fd_set readset;
fd_set tmpset;
FD_ZERO(&readset);
FD_SET(lfd, &readset);
int nready;
int maxfd = lfd;
int cfd;
int n;
char buf[1024];
while(1)
{
tmpset = readset;
nready = select(maxfd+1, &tmpset, NULL, NULL, NULL);
if(nready <= 0)
{
continue;
}
//有客户端连接请求到来
if(FD_ISSET(lfd, &tmpset))
{
cfd = accept(lfd, NULL, NULL);
FD_SET(cfd, &readset);
maxfd = cfd > maxfd? cfd : maxfd;
//说明只有监听描述符被置为1,后面可跳过
if(--nready == 0)
{
continue;
}
}
//有数据发来
//先有的lfd,后有的cfd
for(int i=lfd+1; i<=maxfd;i++)
{
if(FD_ISSET(i, &tmpset))
{
memset(buf, 0, sizeof(buf));
n = read(cfd, buf, sizeof(buf));
if(n <= 0)
{
close(i);
FD_CLR(i, &readset);
printf("read error or client close\n");
continue;
}
printf("n == [%d], buf == [%s]\n", n, buf);
for(int j=0; j<n; j++)
{
buf[j] = toupper(buf[j]);
}
write(i, buf, n);
}
}
}
close(lfd);
return 0;
}