我不熟悉C语言中的套接字编程,我想编写一个服务器 - 客户端原型,其中服务器将充当多个用户的一种文件托管服务器(使用TCP协议)。由于服务器和客户端都需要能够从stdin获取命令,因此我使用了select()函数。这是我程序的两个部分的代码,问题如下:
由于服务器和客户端都需要能够从stdin获取命令并处理它们,并且服务器必须发送对客户端请求的响应,所以程序在某一点阻塞,服务器发送它并且#39;仅在客户端发送另一个请求后才响应。我认为这是因为在 server.c 文件中同时使用send()和recv(),但我不完全确定。
server.c
while(1) {
if (select(maxfd+1,&tmpfds,NULL,NULL,NULL) == -1) {
error("Err in select");
}
for(i = 0; i < maxfd; i++) {
if(FD_ISSET(i,&tmpfds) {
if (i == listenfd) {
< add new client to list >
}
else if (i == 0) { /* keyboard input */
< parse server commands >
}
else {
/* This is where I think my problem is*/
recv(i,buffer,BUFLEN,0);
process(buffer);
send(i,buffer,BUFLEN,0);
}
}
}
client.c
while(1) {
if(select(fdmax+1,&tmpfds,NULL,NULL,NULL) == -1) {
error("Err in select");
}
if (FD_ISSET(0,&tmpfds)) {
fgets(buffer,BUFLEN,stdin);
process_request(buffer);
send(serverfd,buffer,BUFLEN,0);
}
else if (FD_ISSET(serverfd,&tmpfds)) {
recv(serverfd,buffer,BUFLEN,0);
process_response(buffer);
}
}
另外,请不要对我的编码风格或C习惯做出严厉的评论。我在任何情况下都不是C专家,也不是我自称的,我只是在学习,所以请帮助我。我做错了什么,如何在不改变(过多)程序行为的情况下避免这种情况?
答案 0 :(得分:1)
要使用select
作为正确的IO多路复用工具,您需要正确维护FD_SET
。由于每次select
返回,FD_SET
仅包含准备进行操作的fds,这意味着您必须在每次调用FD_SET
之前重新设置select
。< / p>
您的代码中还有另一个问题,您不能只在循环中的FD_SET
中添加新客户端,您需要保存它,然后在开头重新安装它们。
此外,您不需要检查集合中的每个FD,因为select
将返回准备用于IO的fd数。
尝试以下更改:
int clients[MAX_CLIENTS] = {0};
int I;
int maxfd;
int server_sock = <the listening fd>;
FD_SET readfds;
int ret;
while(1) {
// Setup SD_SET each time calling select
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
maxfd = STDIN_FILENO;
FD_SET(server_sock, &readfds);
maxfd = max(maxfd, server_sock);
for (I = 0; I < MAX_CLIENTS; I++) {
if (clients[I] >= 0) {
FD_SET(clients[I], &readfds);
maxfd = max(maxfd, clients[I]);
}
if ((ret = select(maxfd+1,&readfds,NULL,NULL,NULL)) == -1) {
error("Err in select");
}
for(i = 0; i < maxfd && ret; i++, ret--) {
if(FD_ISSET(i, &readfds) {
if (i == listenfd) {
// < add new client to clients array
}
else if (i == STDIN_FILENO) { /* keyboard input */
// < parse server commands >
}
else {
// one of the client is ready
int nread = recv(i,buffer,BUFLEN,0);
if (nread == 0) {
// client is closed, remove I from clients array
continue;
}
process(buffer);
send(i,buffer,BUFLEN,0);
}
}
}
}
最后但并非最不重要的是,作为对select
的改进,请在Linux上尝试类似epoll
的内容,它会为您保留状态,以便您不需要重新安装所有fds像select
那样。