我正在尝试用 C 语言为自己构建一个 Web 应用程序。到目前为止,我已经让 HTTP 服务器正常工作了,但是由于某种原因,当我有需要其他 CSS 的 HTML 文件和 JavaScript 文件时,浏览器( Firefox)会非常快地执行多个请求,导致服务器和客户端之间的套接字连接损坏。我尝试了许多不同的方法来让 HTTP 服务器正常工作而不生成损坏的 TCP 连接。
目前,我有一个循环调用
accept
,并为每个新连接创建一个新线程:
for(;;)
{
int newsockfd = accept(sockfd, (struct sockaddr *)&host_addr, (socklen_t *)&host_addrlen);
if(newsockfd < 0)
{
sendf(stderr, LOG_ERROR, "Failed to accept connection...\n");
continue;
}
pthread_t thread;
if(pthread_create(&thread, NULL, handler, (void *)&newsockfd) != 0)
{
sendf(stderr, LOG_ERROR, "Failed to create a new thread...\n");
return -4;
}
}
我创建线程,同时将套接字文件描述符(表示客户端和服务器之间的新连接)发送到新线程。
处理新创建线程的函数如下所示:
void * handler(void * argument)
{
int sockfd = *((int *)argument);
struct sockaddr_in client_addr;
int client_addrlen = sizeof(client_addr);
int sockn = getpeername(sockfd, (struct sockaddr *)&client_addr, (socklen_t *)&client_addrlen);
if(sockn < 0)
{
sendf(stderr, LOG_ERROR, "Failed to get client's address, got \x1b[35msockn\x1b[0m \x1b[33;3m%d\x1b[0m...\n", sockn);
pthread_exit(NULL);
}
char * input = malloc(sizeof(char) * BUFFER_SIZE);
int valread = read(sockfd, input, BUFFER_SIZE);
if(valread < 0)
{
sendf(stderr, LOG_ERROR, "Failed to read from socket, got \x1b[35mvalread\x1b[0m \x1b[33;3m%d\x1b[0m...\n", valread);
free(input);
pthread_exit(NULL);
}
char * method = malloc(sizeof(char) * BUFFER_SIZE);
char * url = malloc(sizeof(char) * BUFFER_SIZE);
char * version = malloc(sizeof(char) * BUFFER_SIZE);
sscanf(input, "%s %s %s", method, url, version);
sendf(stdout, LOG_DEBUG, "Client \x1b[33;3m%s:%u\x1b[0m has sent a \x1b[33;3m%s\x1b[0m request to \x1b[33;3m%s\x1b[0m!\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), method, url);
...
当连接损坏时,我得到:
[ERROR]: Failed to get client's address, got sockn -1...
使用一些调试工具,然后检查并发现
method
、url
和 verison
中的第一个字节设置为 NULL
。函数 getpeername
也返回 -1。
函数 sendf
只是基本的类似 printf 的函数,但似乎不是问题的原因。
有人发现这里有什么问题吗?
newsockfd
是循环的本地变量,您将指向它的指针发送到线程,但该指针在循环结束时(紧随 pthread_create
之后)将无效。
for(;;)
{
int newsockfd = accept(...);
pthread_create(..., (void *)&newsockfd);
} // <- here &newsockfd is a "dangling" pointer
因此在线程中
int sockfd = *((int *)argument);
不能保证工作。
您需要永久存储套接字描述符,以便指向它的指针保持有效 - 或者通过不让它超出范围来同步对它的访问,直到线程确认它已读取该值。
另一种解决方法是将
int
转换为 void*
并通过这样做将文件描述符按值传递给 pthread_create
:
pthread_create(..., (void *)newsockfd);
void *handler(void * argument) {
int sockfd = (int)argument;
//
}
您正在向线程处理程序传递一个指向局部变量的指针,当线程开始运行时,该变量不再在范围内。
尝试将 newsockfd
的
value传递给处理程序,而不是向其传递 pointer,例如:
int newsockfd = ...;
...
pthread_t thread;
pthread_create(..., (void *)newsockfd)
void * handler(void * argument)
{
int sockfd = (int)argument;
...
}