我试图建立一个基本的非阻塞聊天客户端,但我真的不明白select()
和FD_ISSET()
。我试图用下面的代码听插座,但它不会工作,它不会打印任何东西,为什么不呢?
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
int main(int argc, char const* argv[])
{
fd_set readfs;
char sendline[100];
char str[100];
char *some_addr;
int listen_fd, comm_fd;
struct sockaddr_in servaddr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
//Socket error
if (listen_fd == -1) {
printf("Error on getting socket, Exiting!\n");
return 1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htons(INADDR_ANY);
servaddr.sin_port=htons(22000);
bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listen_fd, 10);
comm_fd = accept(listen_fd, (struct sockaddr *) NULL, NULL);
FD_ZERO(&readfs);
FD_SET(comm_fd, &readfs);
while (1)
{
select(listen_fd,&readfs, NULL, NULL, NULL);
if(FD_ISSET(listen_fd,&readfs))
{
bzero(str,100);
read(listen_fd,str,100);
printf("%s", str);
/* write(listen_fd, "read!", strlen(str)+1); */
}
}
return 0;
}
编辑: 我的代码试图连接到服务器:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include<string.h>
int main(int argc,char **argv)
{
int sockfd,n;
char sendline[100];
char recvline[100];
struct sockaddr_in servaddr;
sockfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof servaddr);
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(22000);
inet_pton(AF_INET,"127.0.0.1",&(servaddr.sin_addr));
connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
while(1)
{
bzero( sendline, 100);
bzero( recvline, 100);
fgets(sendline,100,stdin); /*stdin = 0 , for standard input */
write(sockfd,sendline,strlen(sendline)+1);
read(sockfd,recvline,100);
printf("%s\n",recvline);
}
return 0;
}
答案 0 :(得分:1)
这里有四个主要问题:
您的select()
来电和读/写循环应该使用comm_fd
,而不是listen_fd
。如果您在select()
上致电listen_fd
,当有accept()
连接可用时,它会返回,但您想等待已连接的套接字输入,请使用{{ 1}}。
comm_fd
的第一个参数应该是集合中的最高文件描述符加一个。由于您只有一个文件描述符,因此它应该是select()
。
您应该在comm_fd + 1
循环中移动FD_ZERO
和FD_SET
个宏,并在每次while
调用之前执行这些宏,因为select()
将修改你传递给它的那些fd集。
您不会检查系统调用的返回错误。你应该。
其他要点:
select()
已经从POSIX移除了很长一段时间,现在,您应该使用标准bzero()
。
您不应该memset()
通过INADDR_ANY
,只需按原样使用它。
这只是您计划中的评论,但htons()
可能是STDIN_FILENO
,0
是stdin
指针,而不是FILE
}。
答案 1 :(得分:0)
但我真的不明白select()和FD_ISSET()
fd_set就像一个位数组。数组中的每个位代表一个套接字或文件描述符。
FD_ISSET()是一个宏或函数,它告诉您是否在位数组(fd_set)中设置了给定的套接字描述符(例如4)。 FD_SET()允许你自己设置一个位,而FD_CLR()可以让你清空一点。
这些位不是神奇地设置,你使用select()
要求操作系统内核相应地设置或清除fd_set中的每个位,然后用FD_ISSET()检查每个位并相应地采取行动。在调用select()
之前,您必须设置集合以通过使用FD_SET()设置fd_set
中的位或者如果要设置大量套接字/位来告诉内核您有兴趣轮询哪些描述符,使用主fd_set并将整个内容复制到您的读,写或错误集。为了提高效率,我通常会选择后者。这些是通常从0到N的整数(前3个通常不是套接字,所以你通常轮询3 .. N)。选择返回后,必须检查位。如果在readfds
中设置了一个位,则可以进行读取。
select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
支持的状态是&#34;准备好阅读&#34;,&#34;准备好写&#34;和&#34;错误条件&#34;
如果您未在集合中设置特定位,则内核不会向您报告其状态。
同样,如果您未将nfds
参数(最大描述符值)设置得足够高,则将忽略高于最大值的任何描述符。描述符不必是连续的,只是在nfds
范围内。
所有这些逻辑都假设系统调用成功返回值。如果系统调用返回错误状态,您甚至不会考虑该调用的数据结构,必须进行适当的恢复或处理。
在您的代码中跳出来的主要问题是您选择的电话的第一个参数。它不会检查comm_fd是否comm_fd低于listen_fd。
我建议您保留一个max_desc
的int值,并且每次接受一个新套接字时,设置max_desc = MAX(max_desc, new_fd+1)
,当关闭套接字时,您需要向下调整它。我总是喜欢保留一个单独的fd_set来跟踪我的进程打开的描述符(从不将它传递给select()只是用它来记账)。