在C中,如何在不关闭/关闭套接字的情况下写入数据后指示EOF?

时间:2017-08-18 03:19:15

标签: c sockets keep-alive

我是C socket编程新手。我了解到,通过套接字发送数据后,应该closeshutdown套接字描述符,这会触发EOF发送到另一端。如果没有EOF,read / recv会阻止。

现在我想知道是否可以保持套接字打开以进行进一步的数据传输。从我读到的内容来看,这似乎是人们在建立保持活动的HTTP连接时所做的事情。但我无法弄清楚这是如何实现的。

以下代码显示了在客户write之后停留的方案。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define thread_printf(...) printf("[PID %d] ", getpid()); printf(__VA_ARGS__);

int create_socket()
{
  int socket_fd;
  socket_fd = socket(AF_INET, SOCK_STREAM, 0);
  if (socket_fd < 0)
    perror("Error opening socket");
  return socket_fd;
}

int listen_port(int portno)
{
  int socket_fd;
  struct sockaddr_in server_addr;

  socket_fd = create_socket();

  memset((void *) &server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(portno);
  server_addr.sin_addr.s_addr = INADDR_ANY;

  if (bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
    perror("Error on binding");

  listen(socket_fd, 5);
  return socket_fd;
}

int connect_port(const char *host, int portno)
{
  int socket_fd;
  struct sockaddr_in proxy_addr;
  struct hostent *proxy;

  socket_fd = create_socket();

  proxy = gethostbyname(host);
  if (proxy == NULL)
    perror("Error no such host");

  proxy_addr.sin_family = AF_INET;
  proxy_addr.sin_port = htons(portno);
  memcpy((void *) &proxy_addr.sin_addr.s_addr, (void *) proxy->h_addr, proxy->h_length);

  if (connect(socket_fd, (struct sockaddr *) &proxy_addr, sizeof(proxy_addr)) < 0)
    perror("Error connecting");

  return socket_fd;
}

int read_socket(int fd, char *buf, int bufsize)
{
  thread_printf("read_socket\n");
  int m = 0;                    /* total number of bytes received */
  int n = 0;                    /* number of bytes received in a single read */

  while (m < bufsize - 1)
  {
    n = read(fd, buf + m, bufsize - m - 1);
    if (n == -1)                /* socket read error */
    {
      perror("Error reading socket");
      break;
    }
    if (n == 0)                 /* socket is closed */
      break;
    m += n;
  }

  if (m >= bufsize)
    perror("Error buffer overflow");

  buf[m] = '\0';
  return m;
}

int write_socket(int fd, char *buf, int len)
{
  thread_printf("write_socket\n");
  int m = 0;
  int n = 0;

  while (m < len)
  {
    n = write(fd, buf + m, len - m);
    if (n == -1) {
      perror("Error socket send");
      break;
    }
    if (n == 0)                 /* socket is closed */
      break;
    m += n;
  }
  thread_printf("m = %d, len = %d\n", m, len);
  return m;
}

int main()
{
  int socket_fd = listen_port(3129);
  if (fork() == 0)              /* client */
  {
    close(socket_fd);
    int client_socket_fd = connect_port("127.0.0.1", 3129);
    int n;
    char *msg = "This is a client request\n";
    char buf[1024];
    memset((void *) buf, 0, 1024);
    n = write_socket(client_socket_fd, msg, strlen(msg));
    thread_printf("client sent: %s\n", msg);
    n = read_socket(client_socket_fd, buf, 1024);
    thread_printf("client received: %s\n", buf);
    close(client_socket_fd);
  }
  else                          /* server */
  {
    struct sockaddr_in client_addr;
    int client_len;
    client_len = sizeof(client_addr);
    int client_socket_fd = accept(socket_fd, (struct sockaddr *) &client_addr, &client_len);
    int n;
    char *msg = "This is a server response\n";
    char buf[1024];
    n = read_socket(client_socket_fd, buf, 1024);
    thread_printf("server received: %s\n", buf);
    n = write_socket(client_socket_fd, msg, strlen(msg));
    thread_printf("server sent: %s\n", msg);
    close(client_socket_fd);
  }

  return 0;
}

2 个答案:

答案 0 :(得分:4)

在服务器中,您的代码等待读取1024个字节,但在客户端中,您只发送26个字节作为strlen(msg),因此您的服务器永远不会从read_socket返回

对于任何套接字客户端 - 服务器应用程序,您需要为客户端和服务器定义自己的协议,因为何时通过确定固定长度数据包或通过将数据长度作为数据本身的一部分包括来读取所有数据

e.g。您可以决定您的数据将包括,前2个字节作为数据长度,后跟实际数据

答案 1 :(得分:3)

  

现在我想知道是否可以保持套接字打开   进一步的数据传输。

是的,绝对 - 一般情况下,您可以根据需要保持套接字打开,并且您可以发送(和/或接收)尽可能多的数据。

接收器在大多数情况下需要某种方式来明确地解析传入的数据 - 特别是它需要有一些方法来知道何时一个语义消息&#34; (*)结束,下一个开始。如果您只发送一条&#34;消息&#34;然后关闭TCP连接,避免了这个问题,因为EOF本身就是你的消息结束指示符,但是如果你打算为多个消息打开连接,你需要一些方法来接收器确定消息边界的位置。

这些信息需要在数据本身中进行编码,因为TCP协议没有承诺在每次调用recv()期间它将向接收进程传递多少字节;它只保证按照发送的顺序接收字节。

编码消息边界信息的常用方法包括:

  1. 在消息前面加上一个固定大小的标头,指示消息正文中需要多少字节
  2. 指定一个特殊的&#34;消息结束&#34;字符,如NUL字节或回车符(请注意,如果您这样做,您需要确保您的消息永远不会包含此字符作为有效负载数据的一部分,或者如果他们这样做,则适当地以某种方式转义,以便它不会被错误地解释为消息边界)
  3. SLIP encoding(或类似)
  4. 要求所有邮件都是众所周知的固定大小(这仅适用于某些用例)
  5. 我个人更喜欢方法(1),因为我发现它最容易正确有效地实现,但上述任何方法都可以。

    (*)&#34; message&#34;在此上下文中定义为&#34;接收器在以某种有用的方式对其含义做出反应之前需要读取的字节数&#34;