【发布时间】:2014-01-14 16:17:46
【问题描述】:
我正在尝试使用 BSD 套接字创建一个简单的代理服务器,它在端口上侦听请求,然后将该请求传递到另一台服务器,然后再将服务器的响应发送回浏览器。
我可以使用以下代码从浏览器接收 REST 请求:
void *buffer = malloc(512);
long length = 0;
while (1) {
void *tempBuffer = malloc(512);
long response = recv(acceptedDescriptor, tempBuffer, 512, 0);
if (response == 0 || response < 512) {
free(tempBuffer);
printf("Read %lu bytes\n", length);
break;
}
memcpy(buffer + length, tempBuffer, response);
free(tempBuffer);
length += response;
realloc(buffer, length + 512);
}
然而,recv() 应该在连接被对等方(在本例中为浏览器)关闭时返回 0,但情况并非如此。我能够检测连接是否已关闭的唯一方法是检查响应是否小于从recv() 请求的最大数量,512 字节。这有时会出现问题,因为我看到的一些请求不完整。
如果没有更多数据要接收,recv() 会阻塞并且永远不会返回,并且将接受的描述符设置为非阻塞意味着读取循环将永远继续,永远不会退出。
如果我:
将监听套接字描述符设置为非阻塞,当我尝试
accept()连接时出现EAGAIN错误(资源暂时不可用)将接受的套接字描述符设置为非阻塞,
recv()永远不会返回 0 并且循环会一直持续下去将它们都设置为非阻塞,尝试
accept()连接时出现“错误文件描述符”错误不要将它们中的任何一个设置为非阻塞,循环永远不会退出,因为
recv()永远不会返回。
socket 本身是按如下方式创建的,但是由于它能够检测到请求,所以我看不出它的初始化有什么问题:
int globalDescriptor = -1;
struct sockaddr_in localServerAddress;
...
int initSocket() {
globalDescriptor = socket(AF_INET, SOCK_STREAM, 0);
if (globalDescriptor < 0) {
perror("Socket Creation Error");
return 0;
}
localServerAddress.sin_family = AF_INET;
localServerAddress.sin_addr.s_addr = INADDR_ANY;
localServerAddress.sin_port = htons(8374);
memset(localServerAddress.sin_zero, 0, 8);
int res = 0;
setsockopt(globalDescriptor, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res));
//fcntl(globalDescriptor, F_SETFL, O_NONBLOCK);
return 1;
}
...
void startListening() {
int bindResult = bind(globalDescriptor, (struct sockaddr *)&localServerAddress, sizeof(localServerAddress));
if (bindResult < 0) {
close(globalDescriptor);
globalDescriptor = 0;
perror("Socket Bind Error");
exit(1);
}
listen(globalDescriptor, 1);
struct sockaddr_in clientAddress;
int clientAddressLength = sizeof(clientAddress);
while (1) {
memset(&clientAddress, 0, sizeof(clientAddress));
clientAddressLength = sizeof(clientAddress);
int acceptedDescriptor = accept(globalDescriptor, (struct sockaddr *)&clientAddress, (socklen_t *)&clientAddressLength);
//fcntl(acceptedDescriptor, F_SETFL, O_NONBLOCK);
if (acceptedDescriptor < 0) {
perror("Incoming Connection Error");
exit(1);
}
void *buffer = malloc(512);
long length = 0;
while (1) {
void *tempBuffer = malloc(512);
long response = recv(acceptedDescriptor, tempBuffer, 512, 0);
if (response == 0) {
free(tempBuffer);
printf("Read %lu bytes\n", length);
break;
}
memcpy(buffer + length, tempBuffer, response);
free(tempBuffer);
length += response;
realloc(buffer, length + 512);
}
executeRequest(buffer, length, acceptedDescriptor);
close(acceptedDescriptor);
free(buffer);
}
}
...
startListening() 函数仅在initSocket() 返回 1 时调用:
int main(int argc, const char *argv[]) {
if (initSocket() == 1) {
startListening();
}
return 0;
}
我可能在这里做了一些愚蠢的事情,但如果您能提供有关此问题以及如何解决它的任何信息,我将不胜感激。
【问题讨论】:
-
recv()只会在对等端完全关闭连接时返回0。否则返回值为-1。最好检查recv()返回的值是否大于0。 -
@alvits 正如预期的那样,
recv()返回读取的字节数(通常 recv() 之前和之后放置一个printf(),那么在第二次迭代中,第二个printf()永远不会被调用。 -
如果发件人停止发送,
recv()将阻止。发件人如何发送?调用send()时使用什么标志?如果发送方使用标志 MSG_MORE,那么内核将不会发送,直到 MSG_EOR 内核接收到非 MSG_MORE 标志或内核接收到 MSG_EOR,然后内核才会将消息发送到您的服务器。当您收到小于您期望的大小 (512) 时,您已经收到了完整的消息。如果您期望的消息超过最大值 (>512),那么您将不得不多次执行 recv()。 -
换句话说,如果你正好收到 512,缓冲区中可能还有更多,因此需要使用
recv()从缓冲区中获取更多。如果你收到少于 512,那就结束了。
标签: c unix proxy recv berkeley-sockets