我编写了一个简单的多线程 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)。
代码几乎与基于分支的代码类似,所以我有点不明白为什么它不起作用。任何人都可以指出我在这里可能做错了什么吗?.
可能还有其他问题,但尝试将 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);
。