select 未指示 pthread_create 启动的函数中数据到达

问题描述 投票:0回答:1

我编写了一个简单的多线程 TCP 回显服务器来试验线程。下面是我的主要功能的代码。

#include "server_lib.h"
#include <stdio.h>
#include <stdlib.h>

#include <pthread.h>
#include <errno.h>


int main(int argc, char *argv[]) {
  Server s = setup_socket();
  pthread_t clients[MAX_CLIENT];

  int connfd, rv = 0;

  client_data.client_number = 0;
  for(;;) {
    connfd = accept(s.sockfd, (struct sockaddr*)&s.sa, (socklen_t*)&s.addrlen);

    if (connfd < 0 && errno != EAGAIN)
      handle_error("accept failed");

    if (connfd > 0) {
      client_data.client_number++;
      if (client_data.client_number <= MAX_CLIENT) {
        socket_nonblocking(&connfd);
        disable_nagles_algo(&connfd);

        /* Send the client number to client first */
        rv = send(connfd, (void *)&client_data.client_number,
                  sizeof(client_data.client_number), 0);
        ThreadDataT *t = (ThreadDataT*)malloc(sizeof(ThreadDataT));
        t->fd = connfd;

        if (pthread_create(&clients[client_data.client_number-1], NULL, HandleMessage, (void*)t) != 0){
          handle_error("pthread_create failed");
        }
        /* Lets close our copy of connfd */
      }
        else {
          rv = send(connfd, "Max clients reached!\n", 21, 0);
          client_data.client_number--;
          close(connfd);
        }
    }

    usleep(100000);
  }

  close(s.sockfd);
}

这是通过 pthread_create 调用的 HandleMessage 函数的代码。

void* HandleMessage(void *data) {
  /* Lets detach first */
  pthread_detach(pthread_self());

  struct timeval timeout;
  timeout.tv_sec = 0;
  timeout.tv_usec = 0;

  ThreadDataT *t = (ThreadDataT*)data;
  fd_set testfd;

  FD_ZERO(&testfd); FD_SET(t->fd, &testfd);

  int rv = 0;

  for (;;) {
    int result = select(FD_SETSIZE, &testfd, NULL, NULL, &timeout);
    if (result < 0) {
      perror("select failed");
      pthread_exit(&result);
    }


    if (result > 0) {
      if(FD_ISSET(t->fd, &testfd)) {
        /* We have some data */
        rv = echo_content(&t->fd);
        if (rv < 0) {
          if (rv != -10)
            perror("echo_content failed");
          close(t->fd);
          free(t);
          pthread_exit(NULL);
        }
      }
    }

    usleep(1000);
  }

  return 0;
}

下面是评论中要求的 echo_content 函数的代码。

int echo_content(int *connfd) {
  unsigned char buffer[2048];
  int size = recv(*connfd, buffer, sizeof(buffer), 0);
  if (size < 0)
    handle_error("recv");

  if (size > 0) {
    if (strstr((const char*)buffer, "quit") != NULL){
      printf("Closing connection with client\n");
      send(*connfd, "bye\n", 4, 0);
      return -10;
    }

    size = send(*connfd, buffer, size, 0);
  } else
    printf("WARNING: Failed to recieve data\n");

  return size;
}

代码使用的数据结构定义如下。

#ifndef __SERVER_LIB_H__
#define __SERVER_LIB_H__

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>

#define handle_error(msg) \
  do { perror(msg); exit(EXIT_FAILURE); } while(0)

#define PORT 50000
#define MAX_CLIENT 2

typedef struct {
  int sockfd;
  struct sockaddr_in sa;
  int addrlen;
} Server;

typedef unsigned char ClientNumber;

typedef struct {
  ClientNumber clients[MAX_CLIENT];
  short client_number;
} ClientDataT;

ClientDataT client_data;;

typedef struct {
  int fd;
} ThreadDataT;

Server setup_socket(void);
int echo_content(int *);
void socket_nonblocking(int *);
void disable_nagles_algo(int *);
void* HandleMessage(void*);

#endif /* __SERVER_LIB_H__ */

编译上面的代码后,我可以使用 telnet 连接到它。我在telnet中没有看到服务器发送的客户端号码。接下来,我输入了一些消息并期待它回显,但那没有发生。

在调试时,我注意到无论我在客户端输入什么,HandleMEssage 中的 select 系统调用总是返回 0。只是为了测试,我然后设置 select 的 writefds 参数,然后 select 返回值 > 0 并指示套接字期望被写入。相同的代码适用于 fork 模型。 (基于分叉的代码是here)。

代码几乎与基于分支的代码类似,所以我有点不明白为什么它不起作用。任何人都可以指出我在这里可能做错了什么吗?.

c pthreads posix-select
1个回答
1
投票

可能还有其他问题,但尝试将 FD_SET() 移到循环内:

  for (;;) {
    FD_SET(t->fd, &testfd);
    int result = select(FD_SETSIZE, &testfd, NULL, NULL, &timeout);

AFIK,select()调用修改fd-sets,并且可能从集合中删除testfd。

另一个可能的问题是 select() 调用的零超时。 select 的手册页说:

如果两个字段 timeval 结构为零,然后 select() 立即返回。 (这 对于轮询很有用。)如果超时为 NULL(无超时),则 select() 可以无限期阻止。

您也可以尝试无限期超时:

  for (;;) {
    FD_SET(t->fd, &testfd);
    int result = select(FD_SETSIZE, &testfd, NULL, NULL, NULL);

这样你就可以消除丑陋的轮询睡眠:

usleep(1000);

© www.soinside.com 2019 - 2024. All rights reserved.