【问题标题】:Strange program behaviour after closing a Linux socket关闭 Linux 套接字后的奇怪程序行为
【发布时间】:2013-05-02 14:52:37
【问题描述】:

我正在研究 Linux/UNIX 套接字,因此我基于它编写了一个非常简单的“游戏”,称为“21 匹配”。有一个由 21 场比赛组成的堆,每个玩家从中取一场、两场或三场比赛。拿下最后一场比赛的人输掉比赛。

显然,获胜的关键是在对手的回合中最多补充 4 场比赛,因此他必须参加最后一场比赛(只有在您完成第一回合时才有效)。因此,客户端连接到服务器并与服务器“玩”直到他输了。可以连接多个客户端,因此我限制了连接的数量,让我的主机拒绝任何更多的连接。

唯一我无法解决或解释的问题是,当有人被拒绝时,第一个客户立即输掉游戏,即使他没有进行任何转弯。如果我让新客户端接触任何客户端的堆,这可能会得到解释,但我没有!我还跟踪了缓冲区数组,但它没有受到任何损害。

代码如下:

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <poll.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

void die (const char *s, int errcode);
int mkservsock();
void acceptclient();
void dropclient (int client);
void make_turn (int client);

enum
{
  port   = 3333,
  buf_s  = 100,
  limit  = 3
};

void die (const char *s, int errcode)
{
  perror (s);
  exit (errcode);
}

struct pollfd fds[limit + 1];  // client socket descriptors array
int left[limit + 1];           // how many matches left
int nfd = 1;                   // number of the next client
char buffer[buf_s];            // buffer for communicating

// creates a single socket to poll
int mkservsock()
{
  int s = socket (AF_INET, SOCK_STREAM, 0);
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons (port);
  addr.sin_addr.s_addr = INADDR_ANY;
  if (bind (s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    die ("Can't bind socket", 1);

  if (listen (s, 1) == -1)
    die ("Can't start listening", 1);

  return s;
}

// adds new client to descriptors array or rejects it, if number of connections exceeds the limit
void acceptclient()
{
  int c = accept (fds[0].fd, 0, 0);
  fds[nfd].fd = c;
  fds[nfd].events = POLLIN;
  if (nfd == limit + 1)
  {
    sprintf (buffer, "Server is busy, try again later\n");
    send (fds[nfd].fd, buffer, strlen (buffer), 0);
    close (fds[nfd].fd);
    return;
  } else
  {
    left[nfd] = 21;
    sprintf (buffer, "Matches available: %d\nTake 1, 2 or 3!\n", left[nfd]);
    send (fds[nfd].fd, buffer, strlen (buffer), 0);
    nfd++;
  }
}

// disconnects a client in case of match ending or inappropriate data sent
void dropclient (int client)
{
  int i, j;
  close (fds[client].fd);
  for (i = client; i < nfd - 1; i++)
  {
    fds[i] = fds[i + 1];
    left[i] = left[i + 1];
  }
  nfd--;
}


void make_turn (int client)
{
  int n = recv (fds[client].fd, buffer, buf_s, 0);
  if (n == 0) {
    dropclient (client);
    return;

  } else if (n > 3)
  {
    // input counts as incorrect if it contains more than 1 symbol,
    // since we expect a single digit and nothing else
    // (yep, we get two extra bytes when receiving a message)
    sprintf (buffer, "I can break rules, too. Goodbye.\n");
    send (fds[client].fd, buffer, strlen (buffer), 0);
    dropclient (client);
    return;
  }

  // way to extract a digit from the character
  int received = buffer[0] - '0';  
  if (received > 3 || received <= 0)
  {
    sprintf (buffer, "You're allowed to take 1, 2 or 3 matches only\n");
    send (fds[client].fd, buffer, strlen (buffer), 0);
    return;

  } else if (received > left[client])
  {
    sprintf (buffer, "You can't take more than %d\n", left[client]);
    send (fds[client].fd, buffer, strlen (buffer), 0);
    return;

  } else  
  {
    // obviously, it happens only when there's the only match,
    // and the client has to take it
    if (left[client] == received)
    {
      sprintf (buffer, "You lost!\n");
      send (fds[client].fd, buffer, strlen (buffer), 0);
      dropclient (client);
      return;

    } else
    {
      // sort of "keeping the game up"
      left[client] -= 4;
      sprintf (buffer, "Matches left: %d (I took %d)\n", left[client], 4 - received);
      send (fds[client].fd, buffer, strlen (buffer), 0);
      return;
    }  
  }
}

int main (int argc, char *argv[])
{
  fds[0].fd = mkservsock ();
  fds[0].events = POLLIN;

  for (;;)
  {
    int status, i;
    status = poll (fds, nfd, -1);
    if (status == -1)
      die ("Error while polling", 1);

    if (fds[0].revents & POLLIN)
      acceptclient();

    for (i = 1; i < nfd; i++)
    {
      if (fds[i].revents & POLLERR)
      {
        printf ("Got troubles on %d\n", i);
        continue;
      }

      if (fds[i].revents & POLLIN)
        make_turn (i);
    }
  }
}

这是第一个客户端在达到最大值后发生的事情。连接数:

Matches available: 21
Take 1, 2 or 3!

(不要拿任何东西,同时有人连接并被拒绝)

(在此之后,发布任何数字,它会说堆中只有一个匹配项)

1
You lost!

请注意,如果且您的输入等于剩余的匹配数,则您会输。那么,发生了什么?

【问题讨论】:

  • 你确定你在写 C++ 吗?
  • C 或 C++,在这个问题上并不重要。无论如何,将标签更改为 C 并清理此代码以防止误导
  • 您是否尝试过在调试器中逐行逐行执行,看看在这种情况下会发生什么?
  • 是的,什么都没有(也许我在找错东西)。
  • @user1823304:当然很重要

标签: c linux sockets


【解决方案1】:

当您调用acceptclient() 时覆盖left[],并且不检查fds 中是否有空间来存储新连接。

您首先初始化新客户端fds,然后拒绝连接,但为时已晚,因为您在数组之外写入了一个条目。除了数组之外,还有left[] 数组,它现在存储了一个 1 或 0。因此,无论第一个客户如何回应,他总是选择最后一个。

【讨论】:

  • 不,我只有在有足够空间容纳新客户时才会重写它,否则新人会被踢掉。
  • 是的,但是您首先将c 存储在fds[nfd] 中,这已经是一个条目。您必须首先检查客户数量并拒绝或在fds 中再保留一个空间,即fds[limit + 2]
  • 错了!伙计,你为我节省了很多时间。
猜你喜欢
  • 1970-01-01
  • 2017-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多