2

我正在建立自己的网络服务器。现在,我的极简代码是:

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>

#define SERVER_PORT 80

int main () {

    int nReqSocketId, nReqSize = 1024, nMainSocketId = socket(AF_INET, SOCK_STREAM, 0);
    char *sRequest = malloc(nReqSize);
    socklen_t nAddrLen;
    struct sockaddr_in oAddress;

    oAddress.sin_family = AF_INET;
    oAddress.sin_addr.s_addr = INADDR_ANY;
    oAddress.sin_port = htons(SERVER_PORT);

    if (nMainSocketId == 0) {
        fprintf(stderr, "Error during the creation of the socket\n");
        return 1;
    }

    if (bind(nMainSocketId, (struct sockaddr *) &oAddress, sizeof(oAddress))) {
        fprintf(stderr, "The port %d is busy\n", SERVER_PORT);
        close(nMainSocketId);
        return 1;
    }

    printf("HTTP server listening on port %d\n", SERVER_PORT);

    while (1) {

        if (listen(nMainSocketId, 10) < 0) {
            perror("server: listen");
            close(nMainSocketId);
            exit(1);
        }

        nReqSocketId = accept(nMainSocketId, (struct sockaddr *) &oAddress, &nAddrLen);

        if (nReqSocketId < 0) {
            perror("server: accept");
            close(nMainSocketId);
            exit(1);
        }

        recv(nReqSocketId, sRequest, nReqSize, 0);

        if (nReqSocketId > 0){
            printf("The Client is connected...\n\n%s\n", sRequest);
        }

        write(nReqSocketId, "HTTP/1.1 200 OK\n", 16);
        write(nReqSocketId, "Content-length: 50\n", 19);
        write(nReqSocketId, "Content-Type: text/html\n\n", 25);
        write(nReqSocketId, "<html><body><h1>Hello world!!!</h1></body></html>\n", 50);
        close(nReqSocketId);

    }

    printf("Goodbye!\n");

    close(nMainSocketId);

    return 0;

}

我可以创建一个“软关闭机制”让网络服务器打印“再见!” 位于无限循环之后的短语?例如,当我输入“q”字母时……</p>

4

7 回答 7

1

为什么不消除所有这些写函数而只使用一个 send() 呢?

您需要做的就是将您的响应存储在缓冲区中,然后发送缓冲区:

// Global
#define MAX 2048
char response[MAX]; // No need for char* 

// In Main
memset(response, 0, MAX); // **EDIT**
strcpy(response, "HTTP/1.1 200 OK\n");
strcat(response, "Content-length: 50\n");
strcat(response, "Content-Type: text/html\n\n");
strcat(response, "<html><body><h1>Hello world!!!</h1></body></html>\n");

// Now simply send the whole response in one go:
send(nReqSocketId, response, strlen(response), 0);

此外,您也可以简单地将其设为非持久连接,如下所示:

// Global
#define MAX 2048
char response[MAX];  // No need for char* 

// In Main
memset(response, 0, MAX); // **EDIT**
strcpy(response, "HTTP/1.1 200 OK\n");
strcat(response, "Content-Type: text/html\n\n");
strcat(response, "<html><body><h1>Hello world!!!</h1></body></html>\n");

// Now simply send the whole response in one go again:
send(nReqSocketId, response, strlen(response), 0);

// Shutdown the socket so it cannot write anymore:
shutdown(nReqSocketId,1);

// Then totally close it when you are ready:
close(nReqSocketId);

后者可能更适合您目前正在做的事情;因为无论如何您都没有在您的网络服务器中保持多个连接。

一旦您关闭服务器端的连接,客户端(即 Web 浏览器)就会知道停止期待内容并正确完成工作。

希望这可以帮助。

干杯!

PS-

当然,这是对您在此线程中最后一个问题的回答,而不是软终止部分。

我还必须建议您 memset(response, 0, MAX) 以便每次响应时都有一个干净的状态。

于 2013-08-31T16:17:26.197 回答
0

在 *IXish 操作系统下,信号会唤醒某些阻塞系统调用并让它们返回。

它们将指示错误并设置errnoEINTR。您可以使用此类机制来优雅地关闭服务器。

请在下面稍微调整/更正的答案中查看代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

#define SERVER_PORT 8080 /* Using a port>1024 one does not need to run this as root. */

void onInt()
{
  /* Do nothing. */
}

void onQuit()
{
  /* Do nothing. */
}

int main()
{
  int nMainSocketId = -1, nReqSocketId = -1;
  size_t nReqSize = 1024;
  char * sRequest = malloc(nReqSize); /* TODO: add error checking */
  socklen_t nAddrLen;
  struct sockaddr_in oAddress = {0};

  signal(SIGINT, onInt); /* TODO: add error checking */
  signal(SIGQUIT, onQuit); /* TODO: add error checking */

  printf("\n  W E L C O M E!\n\nPress CTRL-C or CTRL-\\ to quit.\n");

  oAddress.sin_family = AF_INET;
  oAddress.sin_addr.s_addr = INADDR_ANY;
  oAddress.sin_port = htons(SERVER_PORT);

  nMainSocketId = socket(AF_INET, SOCK_STREAM, 0);
  if (nMainSocketId < 0) /* 0 is a valid file/socket descriptor! */
  {
    perror("server: socket() failed");
    return 1;
  }

  if (bind(nMainSocketId, (struct sockaddr *) &oAddress, sizeof(oAddress)))
  {
    perror("server: bind() failed");
    close(nMainSocketId);
    return 1;
  }

  printf("HTTP server listening on port %d\n", SERVER_PORT);

  while (1)
  {
    int result = 0;

    if (listen(nMainSocketId, 10) < 0)
    {
      perror("server: listen() failed");
      close(nMainSocketId);
      return 1;
    }

    result = accept(nMainSocketId, (struct sockaddr *) &oAddress, &nAddrLen);
    if (result < 0)
    {
      if (EINTR == errno)
      {
        printf("Shutdown requested. Exiting ...\n");
        break;
      }

      perror("server: accept failed()");
      close(nMainSocketId);
      return 1;
    }

    nReqSocketId = result;

    result = recv(nReqSocketId, sRequest, nReqSize, 0); /* TODO: Check wether the request size was really read! */
    if (result < 0)
    {
      perror("server: recv() failed");
      close(nMainSocketId);
      return 1;
    }
    else if (result == 0)
    {
      printf("The client is disconnected. Waiting for another connection ...\n");
      close(nReqSocketId);
      nReqSocketId = -1;
      continue;
    }

    printf("The client is connected...\n\n%s\n", sRequest);

    /* TODO: add error checking for ALL write()s */
    write(nReqSocketId, "HTTP/1.1 200 OK\n", 16);
    write(nReqSocketId, "Content-length: 50\n", 19);
    write(nReqSocketId, "Content-Type: text/html\n\n", 25);
    write(nReqSocketId, "<html><body><h1>Hello world!!!</h1></body></html>\n", 50);

    close(nReqSocketId);
    nReqSocketId = -1;
  }

  printf("Goodbye!\n");

  close(nMainSocketId);

  return 0;
}

请注意,调用也recv()可能会阻塞等待数据,然后会SIGINT被中断。因此,与检查结果时所做的相同的逻辑accept()应该应用于recv().

于 2013-08-31T13:06:22.973 回答
0

好的,所以我的回复的第二部分解释了如何使您的 Web 服务器非持久化。顺便说一句,在 HTTP 1.1 之前,持久连接曾经被称为 Keep-Alive。由于我看到您每次发送后都会关闭响应套接字,因此您不妨使 Web 服务器“非持久化”。

这意味着您不必因为 shutdown() 而发送“Content-Length: X”。您可以通过三种不同的方式关闭套接字:

"The constants SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, respectively..."

通过执行shutdown(SOCKET, 1),我们实际上是在向客户端(浏览器)发送一个FIN ack,让它知道套接字已完成写入。因此,无需设置“Content-Length: X”标头响应。

现在,这不是 shutdown() VS close() 的事情。这是一个 shutdown() AND close() 的事情。您仍然必须关闭()套接字才能销毁它。关机不会为您做到这一点。

总之,如果您不需要跟踪多个连接,请考虑从标头中取出“Content-Length:”。只需使用 shutdown(SOCKET, 1) 作为“传输结束”机制,然后关闭以销毁套接字。

/---------------------------------------------------- -------------------------------------------------- -------------------------------------------/

至于创建整个 FILE* 只是为了发送字符串文字,我不确定是否值得。你有没有把这个放在板凳上并得到一些结果?无论如何,如果您正在寻找性能,我知道 strcat() [当然包括 strlen()] 和 strlen() 的其他方法。我不知道是这样的。

PS-strcat() 一开始非常快,只有在连接一个大缓冲区时它才开始增加它的复杂性。查找 SIMD 以及如何根据您的架构优化这些类型的功能。

于 2013-08-31T19:00:47.907 回答
0

@Medinoc

非常感谢!

阅读您的回复后,我在网上尝试了一下,发现了这个 GNU 页面:http ://www.gnu.org/software/libc/manual/html_node/Server-Example.html 。

因此,经过一些尝试,我已经能够将所有东西与我的“hello world”示例集成在一起。结果如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>

#define SERVER_PORT 80
#define REQUEST_MAX_SIZE 1024

char sRequest[REQUEST_MAX_SIZE];
int bListening = 1, nMainSocket;

void terminateServer () {
    if (bListening == 0) { return; }
    printf("\n\nTerminating server...\n");
    close(nMainSocket);
    bListening = 0;
}

void switchStdin () {
    if (strcmp(sRequest, "q\n") == 0) {
        terminateServer();
    } else {
        printf("Unknown request %s\n", sRequest);
    }
}

void helloWorld (const int nRequestId) {
    printf("The client is connected...\n\n%s\n", sRequest);
        write(nRequestId, "HTTP/1.1 200 OK\n", 16);
        write(nRequestId, "Content-length: 50\n", 19);
        write(nRequestId, "Content-Type: text/html\n\n", 25);
        write(nRequestId, "<html><body><h1>Hello world!!!</h1></body></html>\n", 50);
}

int main (void) {

    int nOldReqSock, nNewReqSock, nReqLen, nUninitLen = REQUEST_MAX_SIZE;
    fd_set oActiveFD, oReadFD;
    socklen_t nAddrLen;
    struct sockaddr_in oAddress;

    oAddress.sin_family = AF_INET;
    oAddress.sin_addr.s_addr = INADDR_ANY;
    oAddress.sin_port = htons(SERVER_PORT);

    /* Create the socket and set it up to accept connections. */

    nMainSocket = socket(AF_INET, SOCK_STREAM, 0);

    if (nMainSocket < 0) {
        perror("socket");
        return 1;
    }

    if (bind(nMainSocket, (struct sockaddr *) &oAddress, sizeof(oAddress))) {
        perror("bind");
        terminateServer();
        return 1;
    }

    if (listen(nMainSocket, 10) < 0) {
        perror("listen");
        terminateServer();
        return 1;
    }

    /* Initialize the set of active sockets plus STDIN. */

    FD_ZERO(&oActiveFD);
    FD_SET(STDIN_FILENO, &oActiveFD);
    FD_SET(nMainSocket, &oActiveFD);

    printf("\n  W E L C O M E!\n\nType \"q\" to quit.\n");

    while (bListening) {

        /* Block until input arrives on one or more active sockets. */

        oReadFD = oActiveFD;

        if (select(FD_SETSIZE, &oReadFD, NULL, NULL, NULL) < 0) {
            perror("select");
            terminateServer();
            return EXIT_FAILURE;
        }

        /* Service all the sockets with input pending. */

        for (nOldReqSock = 0; bListening && nOldReqSock < FD_SETSIZE; ++nOldReqSock) {

            if (FD_ISSET(nOldReqSock, &oReadFD)) {

                if (nOldReqSock == nMainSocket) {

                    /* Connection request on original socket. */

                    nAddrLen = sizeof(oAddress); /* why??? */
                    nNewReqSock = accept(nMainSocket, (struct sockaddr *) &oAddress, &nAddrLen);

                    if (nNewReqSock < 0) {
                        perror("accept");
                        terminateServer();
                        return EXIT_FAILURE;
                    }

                    FD_SET(nNewReqSock, &oActiveFD);

                } else {

                    /* Data arriving on an already-connected socket. */

                    nReqLen = read(nOldReqSock, sRequest, REQUEST_MAX_SIZE);

                    if (nReqLen < 0) {
                        /* Read error. */
                        perror("read");
                        terminateServer();
                        return EXIT_FAILURE;
                    } else if (nReqLen == 0) {
                        /* End-of-file. */
                        printf("End-of-file\n");
                        close(nOldReqSock); /* why??? */
                        FD_CLR(nOldReqSock, &oActiveFD); /* why??? */
                        continue;
                    } else {
                        /* Data read. */
                        if (nUninitLen > nReqLen) { memset(sRequest + nReqLen, 0, nUninitLen - nReqLen); }
                        nUninitLen = nReqLen;
                    }

                    if (nOldReqSock == STDIN_FILENO) {
                        /* Standard input received */
                        switchStdin(nReqLen);
                    } else {
                        /* TCP/IP request received */
                        helloWorld(nOldReqSock);
                    }

                }

            } 

        }

    }

    printf("Goodbye\n");

    return 0;

}

/* why??? */我还在我不理解的行附近添加了一些评论。我可以请您简要解释一下它们是什么吗?

于 2013-09-03T13:43:56.333 回答
0

@熊猫搜宝

我使用 fprintf() 和文件描述符删除了 write(),我认为这是最好的方法,因为不需要 strlen()... 看下面的代码:

#include <stdio.h>
#include <netinet/in.h>
#include <signal.h>

#define REQUEST_SIZE 1024
#define SERVER_PORT 80

int bExiting, nMainSocketId;

void terminateServer () {
    if (bExiting) { return; }
    printf("\n\nTerminating server...\n");
    close(nMainSocketId);
    bExiting = 1;
}

int main () {

    int nRecvResult = -1, nReqSocketId = -1;
    char sRequest[REQUEST_SIZE];
    socklen_t nAddrLen;
    struct sockaddr_in oAddress;
    FILE *nRespFD;

    printf("\n  W E L C O M E!\n\nPress CTRL-C or CTRL-\\ to quit.\n");

    signal(SIGINT, terminateServer);
    signal(SIGQUIT, terminateServer);

    oAddress.sin_family = AF_INET;
    oAddress.sin_addr.s_addr = INADDR_ANY;
    oAddress.sin_port = htons(SERVER_PORT);

    nMainSocketId = socket(AF_INET, SOCK_STREAM, 0);

    if (nMainSocketId < 0) {
        perror("server: socket() failed");
        return 1;
    }

    if (bind(nMainSocketId, (struct sockaddr *) &oAddress, sizeof(oAddress))) {
        perror("server: bind() failed");
        terminateServer();
        return 1;
    }

    printf("HTTP server listening on port %d\n", SERVER_PORT);

    while (bExiting == 0) {

        if (listen(nMainSocketId, 10) < 0) {
            perror("server: listen() failed");
            terminateServer();
            return 1;
        }

        nReqSocketId = accept(nMainSocketId, (struct sockaddr *) &oAddress, &nAddrLen);

        if (bExiting) { break; }

        if (nReqSocketId < 0) {
            perror("server: accept() failed");
            terminateServer();
            return 1;
        }

        nRecvResult = recv(nReqSocketId, sRequest, REQUEST_SIZE, 0);

        if (nRecvResult < 0) {
            perror("server: recv() failed");
            terminateServer();
            return 1;
        }

        if (nRecvResult == 0) {
            printf("The client is disconnected. Waiting for another connection...\n");
            close(nReqSocketId);
            continue;
        }

        printf("The client is connected...\n\n%s\n", sRequest);

        nRespFD = fdopen(nReqSocketId, "a+");

        fprintf(nRespFD, "HTTP/1.1 200 OK\n");
        fprintf(nRespFD, "Content-length: 50\n");
        fprintf(nRespFD, "Content-Type: text/html\n\n");
        fprintf(nRespFD, "<html><body><h1>Hello world!!!</h1></body></html>\n");

        fclose(nRespFD);

        close(nReqSocketId);

    }

    printf("Goodbye!\n");

    return 0;

}

关于 shutdown() 与 close(),你能更好地解释一下区别吗?

于 2013-08-31T17:05:14.943 回答
0

在@Medinoc、@someuser 和@Elchonon 的建议之后,我通过以下方式更正了我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <signal.h>

#define SERVER_PORT 80

int nStatus, nMainSocketId;

void onInt () {
    printf("You have pressed CTRL-C.\n");
    nStatus = 0;
    shutdown(nMainSocketId, 2);
}

void onQuit () {
    printf("You have pressed CTRL-\\.\n");
    nStatus = 0;
    shutdown(nMainSocketId, 2);
}

int main () {

    int nReqSocketId, nReqSize = 1024;
    nMainSocketId = socket(AF_INET, SOCK_STREAM, 0);
    char *sRequest = malloc(nReqSize);
    socklen_t nAddrLen;
    struct sockaddr_in oAddress;

    signal(SIGINT, onInt);
    signal(SIGQUIT, onQuit);
    oAddress.sin_family = AF_INET;
    oAddress.sin_addr.s_addr = INADDR_ANY;
    oAddress.sin_port = htons(SERVER_PORT);
    nStatus = 1;

    printf("\n  W E L C O M E!\n\nPress CTRL-C or CTRL-\\ to quit.\n");

    if (nMainSocketId == 0) {
        fprintf(stderr, "Error during the creation of the socket\n");
        return 1;
    }

    if (bind(nMainSocketId, (struct sockaddr *) &oAddress, sizeof(oAddress))) {
        fprintf(stderr, "The port %d is busy\n", SERVER_PORT);
        close(nMainSocketId);
        return 1;
    }

    printf("HTTP server listening on port %d\n", SERVER_PORT);

    while (nStatus) {

        if (listen(nMainSocketId, 10) < 0) {
            perror("server: listen");
            close(nMainSocketId);
            return 1;
        }

        nReqSocketId = accept(nMainSocketId, (struct sockaddr *) &oAddress, &nAddrLen);

        if (nReqSocketId < 0) {
            perror("server: accept");
            close(nMainSocketId);
            return 1;
        }

        recv(nReqSocketId, sRequest, nReqSize, 0);

        if (nReqSocketId > 0){
            printf("The Client is connected...\n\n%s\n", sRequest);
        }

        write(nReqSocketId, "HTTP/1.1 200 OK\n", 16);
        write(nReqSocketId, "Content-length: 50\n", 19);
        write(nReqSocketId, "Content-Type: text/html\n\n", 25);
        write(nReqSocketId, "<html><body><h1>Hello world!!!</h1></body></html>\n", 50);
        close(nReqSocketId);

    }

    printf("Goodbye!\n");

    close(nMainSocketId);

    return 0;

}

进程软终止,现在!!但是,不幸的是,套接字没有!:( ...所以每当我按下 CTRL-C 或 CTRL-\ 以退出时,我都会收到以下消息:

server: accept: Invalid argument

有什么建议可以解决吗?

编辑

@alk

谢谢您的回答!您建议我使用另一种方法来软终止我的网络服务器...:

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <signal.h>

#define SERVER_PORT 80

int bExiting, nMainSocketId;

void terminateServer () {
    if (bExiting) { return; }
    printf("\n\nTerminating server...\n");
    close(nMainSocketId);
    bExiting = 1;
}

int main () {

    int nRecvResult = -1, nReqSocketId = -1;
    size_t nReqSize = 1024;
    char * sRequest = malloc(nReqSize);
    socklen_t nAddrLen;
    struct sockaddr_in oAddress = {0};

    printf("\n  W E L C O M E!\n\nPress CTRL-C or CTRL-\\ to quit.\n");

    signal(SIGINT, terminateServer);
    signal(SIGQUIT, terminateServer);

    oAddress.sin_family = AF_INET;
    oAddress.sin_addr.s_addr = INADDR_ANY;
    oAddress.sin_port = htons(SERVER_PORT);

    nMainSocketId = socket(AF_INET, SOCK_STREAM, 0);

    if (nMainSocketId < 0) {
        perror("server: socket() failed");
        return 1;
    }

    if (bind(nMainSocketId, (struct sockaddr *) &oAddress, sizeof(oAddress))) {
        perror("server: bind() failed");
        terminateServer();
        return 1;
    }

    printf("HTTP server listening on port %d\n", SERVER_PORT);

    while (bExiting == 0) {

        if (listen(nMainSocketId, 10) < 0) {
            perror("server: listen() failed");
            terminateServer();
            return 1;
        }

        nReqSocketId = accept(nMainSocketId, (struct sockaddr *) &oAddress, &nAddrLen);

        if (bExiting) { break; }

        if (nReqSocketId < 0) {
            perror("server: accept failed()");
            terminateServer();
            return 1;
        }

        nRecvResult = recv(nReqSocketId, sRequest, nReqSize, 0);

        if (nRecvResult < 0) {
            perror("server: recv() failed");
            terminateServer();
            return 1;
        }

        if (nRecvResult == 0) {
            printf("The client is disconnected. Waiting for another connection...\n");
            close(nReqSocketId);
            continue;
        }

        printf("The client is connected...\n\n%s\n", sRequest);

        /* This is only a simple "Hello world"... */
        write(nReqSocketId, "HTTP/1.1 200 OK\n", 16);
        write(nReqSocketId, "Content-length: 50\n", 19);
        write(nReqSocketId, "Content-Type: text/html\n\n", 25);
        write(nReqSocketId, "<html><body><h1>Hello world!!!</h1></body></html>\n", 50);

        close(nReqSocketId);

    }

    free(sRequest);
    printf("Goodbye!\n");

    return 0;

}

你怎么看待这件事?我应该添加一些东西吗?

不过,我还有另一个问题。我可以将 fprintf() 与文件描述符结合使用来替代 write() 吗?如何?

于 2013-08-31T08:25:32.620 回答
0

等待标准输入和套接字

这是特定于 POSIX 的行为,在 Windows 上明确不起作用。它依赖于这样一个事实:在 *n*x 平台上,套接字是文件描述符,并且它使用该select函数。

这是一个简化的函数,它包装select以等待单个套接字。

/*Waits on a single socket and stdin.*/
int waitOnStdinAndSocket(
 int sockFd,             /*[in] Socket descriptor*/
 int *pInputOnStdin,     /*[out] Set to a nonzero value if there is input on stdin*/
 int *pInputOnSocket,    /*[out] Set to a nonzero value if there is input on the socket*/
 sturct timeval *timeout /*[in/opt] Timeout*/
 ) /*Returns a negative value on failure.*/
{
    int ret;
    fd_set fds;

    *pInputOnStdin = 0;
    *pInputOnSocket = 0;

    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds);
    FD_SET(sockFd, &fds);
    ret = select(sockFd+1, &fds, NULL, NULL, timeout);
    if(ret >= 0)
    {
        *pInputOnStdin = FD_ISSET(STDIN_FILENO, &fds);
        *pInputOnSocket = FD_ISSET(sockFd, &fds);
    }
    return ret;
}
于 2013-09-02T07:52:54.893 回答