#sockets #select
#сокеты #выберите
Вопрос:
У меня есть следующая простая программа, одна — серверная, другая — клиентская, исходный код выглядит следующим образом (я удалил обработку ошибок, чтобы сделать код кратким):
server.c
int main(int argc, char *argv[])
{
int listenfd = 0, connfd = 0, connfd1 =0;
struct sockaddr_in serv_addr;
char sendBuff[1025];
time_t ticks;
int one = 1;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)amp;one, sizeof(one));
memset(amp;serv_addr, '0', sizeof(serv_addr));
memset(sendBuff, '0', sizeof(sendBuff));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(12345);
bind(listenfd, (struct sockaddr*)amp;serv_addr, sizeof(serv_addr));
listen(listenfd, 10);
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
ticks = time(NULL);
snprintf(sendBuff, sizeof(sendBuff), "%.24sn", ctime(amp;ticks));
write(connfd, sendBuff, strlen(sendBuff));
close(connfd);
close(listenfd);
printf("existing...n");
}
client.c:
int main(int argc, char *argv[])
{
int sockfd = 0, n = 0;
char recvBuff[1024];
struct sockaddr_in serv_addr;
fd_set rset;
fd_set wset;
fd_set eset;
struct timeval tv;
int nready;
int count;
if(argc != 2)
{
printf("n Usage: %s <ip of server> n",argv[0]);
return 1;
}
memset(recvBuff, '0',sizeof(recvBuff));
sockfd = socket(AF_INET, SOCK_STREAM, 0));
memset(amp;serv_addr, '0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(12345);
inet_pton(AF_INET, argv[1], amp;serv_addr.sin_addr);
connect(sockfd, (struct sockaddr *)amp;serv_addr, sizeof(serv_addr));
count = -1;
while(1){
FD_ZERO(amp;rset);
FD_ZERO(amp;eset);
FD_SET(sockfd, amp;rset);
FD_SET(sockfd, amp;eset);
memset(amp;tv, 0, sizeof(struct timeval));
tv.tv_sec = 1;
tv.tv_usec = 0;
sleep(1);
count ;
printf("loopcount is %dn", count);
nready = select(sockfd 1, amp;rset, 0, amp;eset, amp;tv);
if(nready < 0){
printf("error in select, exitn");
exit(-1);
}
if(nready == 0){
printf("nothing ready, continue...n");
continue;
}
if(FD_ISSET(sockfd, amp;rset)){
printf("socket readn");
n = read(sockfd, recvBuff, sizeof(recvBuff) - 1);
if(n < 0){
printf("read errorn");
}else if(n == 0){
printf("zero bytes, we assume it is EOFn");
}else{
printf("read n = %dn", n);
recvBuff[n] = 0;
printf("%sn",recvBuff);
}
}
if(FD_ISSET(sockfd, amp;eset)){
printf("socket exceptionn");
}
}
close(sockfd);
return 0;
}
Итак, код очень прост, сервер просто ждет первого клиента, затем записывает ему текущее время, затем сервер закроет все FD и завершит работу.
В клиенте он сначала подключается к серверу, затем он будет бесконечно выбирать этот sockfd для чтения и исключения.
Результат выполнения на моей тестовой машине:
./client 9.21.63.207
loopcount is 0
socket read
read n = 25
Fri Jun 13 02:50:53 2014
loopcount is 1
socket read
zero bytes, we assume it is EOF
loopcount is 2
socket read
zero bytes, we assume it is EOF
loopcount is 3
socket read
zero bytes, we assume it is EOF
Мы можем видеть, что после завершения работы сервера select на стороне клиента по-прежнему сообщает nReady> 0, и sockfd можно прочитать, хотя read возвращает 0, поскольку мы достигли EOF. Я думаю, это имеет смысл. Но когда я встраиваю этот простой код в более крупную систему (это продукт нашей компании, около 1,5 млн строк кода), после завершения работы сервера select на стороне клиента сообщит nReady ==0, обратите внимание, он просто возвращает 0, а не -1, что означает, что здесь не возникает ошибок….
Поскольку наш проект очень сложный, я не знаю, почему этот простой код ведет себя по-другому, когда он встроен в более крупную систему, а не в отдельное приложение. Я предполагаю, что, возможно, наш проект установил некоторые глобальные параметры сокета, которые влияют на поведение select? Я не знаю, кто-нибудь знает, почему? Спасибо.
Комментарии:
1. Это просто дикий выстрел в темноте, но создает ли ваш реальный сервер (т. Е. Тот, Который находится в проекте с 1,5 миллионами строк кода) какие-либо дочерние процессы? Если это так, то ваша проблема может заключаться в том, что один или несколько из этих дочерних процессов автоматически унаследовали копию файлового дескриптора сервера для своего сокета, и именно поэтому сокет остается открытым даже после завершения исходного серверного процесса. Если это проблема, вы можете избежать ее в Windows, вызвав SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, 0) для сокета, или в Unix / Posix-land, закрыв сокет внутри дочернего процесса.
2. @Jeremy: Ты гений! Это действительно причина, как вы указали, есть дочерний процесс, который не закрывает fd, большое спасибо!