基本 C++ 套接字客户端

Basic C++ sockets client

我正在尝试重新创建一个 GUI 用于从头开始的工作,这将是我的第一个联网应用程序。为此,我一直在阅读我能找到的所有 C/C++ 套接字教程信息,但我已经用这些信息将自己编码成几百行无用的代码。

以下代码有两个问题(据我所知):

1) 一旦它连接到我应该监听的端口,它应该立即收到字符串 99999999%connect。到那时,我应该能够发送 0 并开始接收当天数据的开始。但是,我没有收到第一条消息。

2) 我不确定如何以不会导致 GTK UI 锁定的方式编写此代码。这是一些基本的东西,我知道,但这整件事只是对套接字的即兴第一次尝试。

无论如何,这是代码:

#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/time.h>

#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <gtk/gtk.h>

#define UNUSED(x) (void) x

typedef struct
{
  GtkWidget *window, *grid,
            *labelHost, *labelPort, *textFieldHost, *textFieldPort,
            *buttonConnect;
}uiWidgets;

int sockfd = 0;

gboolean on_close_cleanup();
void buttonConnect_on_clicked(GtkButton *button, uiWidgets* widgets);
void connect_to_logview(struct addrinfo *hostname_result);
void listen_for_logview();

int main()
{
  uiWidgets widgets;

  gtk_init(NULL, NULL);

  widgets.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(widgets.window), "SG GUI V3.00");
  g_signal_connect(widgets.window, "destroy", gtk_main_quit, nullptr);
  g_signal_connect(widgets.window, "delete-event", G_CALLBACK(on_close_cleanup),
                   nullptr);

  widgets.labelHost = gtk_label_new("Host");
  widgets.labelPort = gtk_label_new("Port");

  widgets.textFieldHost = gtk_entry_new();
  widgets.textFieldPort = gtk_entry_new();

  widgets.buttonConnect = gtk_button_new_with_label("Connect");
  g_signal_connect(widgets.buttonConnect, "clicked",
                   G_CALLBACK(buttonConnect_on_clicked),
                   &widgets);

  widgets.grid = gtk_grid_new();
  gtk_grid_set_row_spacing(GTK_GRID(widgets.grid), 5);
  gtk_grid_set_column_homogeneous(GTK_GRID(widgets.grid), true);
  gtk_grid_set_row_homogeneous(GTK_GRID(widgets.grid), true);

  gtk_grid_attach(GTK_GRID(widgets.grid), widgets.labelHost, 0, 0, 1, 1);
  gtk_grid_attach(GTK_GRID(widgets.grid), widgets.labelPort, 2, 0, 1, 1);
  gtk_grid_attach(GTK_GRID(widgets.grid), widgets.textFieldHost, 0, 1, 2, 1);
  gtk_grid_attach(GTK_GRID(widgets.grid), widgets.textFieldPort, 2, 1, 1, 1);
  gtk_grid_attach(GTK_GRID(widgets.grid), widgets.buttonConnect,  1, 2, 1, 1);

  gtk_container_add(GTK_CONTAINER(widgets.window), widgets.grid);
  gtk_widget_show_all(widgets.window);

  gtk_main();

  return 0;
}

gboolean on_close_cleanup()
{
  close(sockfd);
  return false;
}

void buttonConnect_on_clicked(GtkButton *button, uiWidgets* widgets)
{
  UNUSED(button);
  struct addrinfo hints, *hostname_result;
  struct sockaddr_in *hostname_address;
  const gchar *hostname = gtk_entry_get_text(GTK_ENTRY(widgets->textFieldHost));
  int return_value;
  gchar addr[INET_ADDRSTRLEN];

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_STREAM;

  if((return_value = getaddrinfo(hostname,
                          gtk_entry_get_text(GTK_ENTRY(widgets->textFieldPort)),
                          &hints, &hostname_result)) != 0)
  {
    g_print("getaddrinfo: %s\n", gai_strerror(return_value));
    return;
  }

  for(struct addrinfo *info_itr = hostname_result; info_itr != nullptr;
      info_itr = info_itr->ai_next)
  {
    hostname_address = (struct sockaddr_in *)info_itr->ai_addr;
    inet_ntop(AF_INET, &(hostname_address->sin_addr), addr, INET_ADDRSTRLEN);

    g_print("%s:%s\n", addr, gtk_entry_get_text(GTK_ENTRY(widgets->textFieldPort)));
  }

  connect_to_logview(hostname_result);

  freeaddrinfo(hostname_result);
}

void connect_to_logview(struct addrinfo *hostname_result)
{
  int rv = 0;
  sockfd = socket(hostname_result->ai_family, hostname_result->ai_socktype,
                      hostname_result->ai_protocol);

  if(sockfd == -1)
  {
    perror("socket");
  }
  else
  {
    rv = connect(sockfd, hostname_result->ai_addr, hostname_result->ai_addrlen);

    if(rv == -1)
    {
      perror("connect");
    }
    else
    {
      g_print("%s: Looks like we got something!\n", __FUNCTION__);
      listen_for_logview();
    }
  }
}

void listen_for_logview()
{
  fd_set fdset;
  gchar buf[256];
  int nbytes = 0;
  bool exit = false;
  struct timeval tv;

  tv.tv_sec = 2;
  tv.tv_usec = 0;

  FD_ZERO(&fdset);

  while(!exit)
  {
    if(select(sockfd+1, &fdset, NULL, NULL, &tv) == -1)
    {
      perror("select");
      exit = true;
      break;
    }

    if(FD_ISSET(sockfd, &fdset))
    {
      if((nbytes = recv(sockfd, buf, sizeof buf, 0)) <= 0)
      {
        perror("recv");
        close(sockfd);
        FD_CLR(sockfd, &fdset);
        exit = true;
        break;
      }
      else
      {
        g_print("!!!\n%s\n\n", buf);
        close(sockfd);
        FD_CLR(sockfd, &fdset);
        exit = true;
        break;
      }
    }
    else
    {
      g_print("Timed out!\n");
      tv.tv_sec = 2;
    }
  }
}

所以,这里出了什么问题?我猜答案是我只是滥用了我拥有的工具。除了上述两种不当行为之外,如果您还在这里看到任何其他 structural/technique 问题,我将不胜感激。

在此先感谢您的帮助。

您似乎使用了阻塞套接字。 connect 阻塞线程直到它连接或失败。

您需要使用非阻塞套接字,以便异步建立连接。

您正在将 FD 集归零并且从不向其中放入任何内容,因此您在一个空的 FD 集上进行选择,因此没有任何内容被选为可读、可写等,因此您永远不会真正阅读。您需要使用适当的 FD_* 宏将 sockfd 添加到 FD 集。

您还需要在每次循环时重新设置超时结构。

但是因为您使用的是阻塞模式,所以根本不需要使用 select()。只需执行 recv()。它会阻塞。

NB if recv() returns 0 表示流结束:对等方已断开连接。这不是错误,在这种情况下调用 perror() 只会打印随机错误消息。不要这样做。