RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1519358
Accepted
kuskas112
kuskas112
Asked:2023-05-13 04:44:16 +0000 UTC2023-05-13 04:44:16 +0000 UTC 2023-05-13 04:44:16 +0000 UTC

如何用 C 语言组织客户端-服务器应用程序的服务器线程的工作?

  • 772

挑战是编写一个简单的网络聊天。语言没有根本区别,但我决定用 C 编写。服务器必须能够同时处理多个用户,而用户又必须能够请求所有活动用户的列表并向他们写入消息。如果用户退出网络,他/她将相应地与服务器断开连接并从活动用户列表中删除。

当我添加注册和发布活跃用户列表的功能时,我遇到了一个问题,在新创建的线程中访问服务器不会出现问题,但是如果您从以前创建的线程访问,服务器会卡住。
我认为重点是同时使用内存,尽管这不太可能,因为我是从一台机器上单独输入所有内容的,但为了以防万一我添加了互斥体,它没有帮助。

我还认为它可能是输入缓冲区堵塞,但在某些地方清除它的功能要么什么都不做,要么只会让情况变得更糟。
我最后的猜测是我的实现根本不适合 pthread 线程,我需要使用共享内存和 fork() 重写所有内容,但我想避免这种情况。

这是服务器代码
:

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

#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_CONNECTIONS 5
#define REGISTRATION '1'
#define CHECKINFO '2'
#define SENDMESSAGE '3'
#define EXIT '4'

struct Node {
    char name[30];
    struct Node* next;
};

struct Node* createNode(char* name) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    for(int i = 0; i < 30; i++)
    {
      newNode->name[i] = name[i];
    }
    newNode->next = NULL;
    return newNode;
}

void CleanStdin()
{
  char c;
  while ((c = getchar()) != '\n' && c != EOF) {}
}


void pushBack(struct Node** headRef, char* name) {
    struct Node* newNode = createNode(name);
    if (*headRef == NULL) {
        *headRef = newNode;
        return;
    }
    struct Node* temp = *headRef;
    while (temp->next != NULL) {
        temp = temp->next;
    }
    temp->next = newNode;
}

void Delete(struct Node** headRef, char* name)
{
  struct Node* temp = *headRef;
  struct Node* prev = temp;
  if (strcmp(temp->name, name) == 0) 
  {
    *headRef = NULL;
    return;
  }
  while (temp != NULL) 
  {
    if (strcmp(temp->name, name) == 0) 
    {
        prev->next = temp->next;
        free(temp);
        return;
    }
    prev = temp;
    temp = temp->next;
  }
}

int Find(struct Node** headRef, char* name)
{
  struct Node* temp = *headRef;
  while (temp != NULL) 
  {
    if (strcmp(temp->name, name) == 0) 
    {
        return 1;
    }
    temp = temp->next;
  }
  return 0;
}

void PrintUsers(struct Node** headRef, int sock, int usersVal)
{
  struct Node* temp = *headRef;
  for (int i = 0; i < usersVal; i++) 
  {
    send(sock, temp->name, 30, 0);
    temp = temp->next;
  }
}


  pthread_t thread_id;
  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  struct Node* head = NULL;
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE];
    int usersVal = 0;

void* mainFunc()
{
  char name[30];
  // Чтение сообщения от клиента
    while(1)
    {
      //CleanStdin();
      memset(buffer, (char)0, BUFFER_SIZE);
    char operation;
    
    if ( operation == REGISTRATION)
    {
      read(new_socket, name, 30);
      int RegCode;
      if (Find(&head, name) == 1)
      {
        printf("User already registered: %s", name);
        RegCode = 1;
      }
      else
      {
        printf("User successfully registered: %s", name);
        pushBack(&head, name);
        pthread_mutex_lock(&mutex);
        usersVal++;
        pthread_mutex_unlock(&mutex);
        RegCode = 0;
      }
      send(new_socket, &RegCode, 4, 0);
    }
    else if (operation == CHECKINFO) 
    {
      pthread_mutex_lock(&mutex);
      printf("\nUserval = %d\n", usersVal);
      send(new_socket, &usersVal, 4, 0);
      pthread_mutex_unlock(&mutex);
      //PrintUsers(&head, new_socket, usersVal);
    }
    else if (operation == EXIT)
    {
      close(new_socket);
      printf("\nExited!\n");
      //Delete(&head, name);
      pthread_mutex_lock(&mutex);
      usersVal--;
      pthread_mutex_unlock(&mutex);
      CleanStdin();
      break;
    }
    read(new_socket, &operation, 1);
  }
  return NULL;
}

int main(int argc, char const *argv[]) {
  
    // Создание сокета
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
      
    // Присваивание опции сокету (можно повторно использовать адрес)
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons( PORT );
      
    // Привязка сокета к адресу и порту
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

// Ожидание входящих соединений
    if (listen(server_fd, MAX_CONNECTIONS) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    // Принятие входящего соединения
    while(1)
    {
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) 
    {
        perror("accept");
        exit(EXIT_FAILURE);
    }
    
    printf("\nConnected!\n");
    pthread_create(&thread_id, NULL, mainFunc, NULL);
  }
    return 0;

}

客户:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024
#define REGISTRATION '1'
#define CHECKINFO '2'
#define SENDMESSAGE '3'
#define EXIT '4'

int Socket(int domain, int type, int protocol)
{
  int sock = socket(domain, type, protocol);
  if (sock < 0) {
        perror("Failed to create socket");
        exit(EXIT_FAILURE);
    }
    return sock;
}

void Inet_pton(int af, const char *src, void *dst)
{
  if (inet_pton(af, src, dst) <= 0) {
        perror("Invalid server address");
        exit(EXIT_FAILURE);
    }
    
}

void Connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
  if (connect(sockfd, addr, addrlen) < 0) {
        perror("Failed to connect to server");
        exit(EXIT_FAILURE);
    }
}

void Send(int sockfd, const void *buf, size_t len, int flags)
{
  if (send(sockfd, buf, len, flags) < 0) 
    {
      perror("Failed to send message to server");
      exit(EXIT_FAILURE);
    }
}

void Recv(int sockfd, void *buf, size_t len, int flags)
{
  if (recv(sockfd, buf, len, flags) < 0) {
        perror("Failed to receive message from server");
        exit(EXIT_FAILURE);
    }
}

// Создание сокета и подключение к серверу
int Conn()
{
  int sock = Socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(SERVER_PORT);
    Inet_pton(AF_INET, SERVER_ADDRESS, &server_address.sin_addr);
    Connect(sock, (struct sockaddr*) &server_address, sizeof(server_address));
    
    return sock;
}

void CleanStdin()
{
  char c;
  while ((c = getchar()) != '\n' && c != EOF) {}
}

int main() {
  char buffer[BUFFER_SIZE];
    char symb, operation;
    int sock = Conn();
    // Первоначальная регистрация
    operation = REGISTRATION;
  Send(sock, &operation, 1, 0);
  printf("\nEnter Name: ");
  fgets(buffer, 30, stdin);
  Send(sock, buffer, 30, 0);
  int RegCode;
  Recv(sock, &RegCode, 4, 0);
  if (RegCode == 1)
  {
    printf("You was registered before");
  }
  else if (RegCode == 0)
  {
    printf("You were successfully registered");
  }
    while(1)
    {
    CleanStdin();
    memset(buffer, (char)0, BUFFER_SIZE);
    printf("\n1. Get users info");
    printf("\n3. Exit\n");
    symb = getchar();
    switch (symb)
    {
    // Регистрация пользователя
      case '1':
      {
        operation = CHECKINFO;
        Send(sock, &operation, 1, 0);
        int usersVal;
        Recv(sock, &usersVal, 4, 0);
        printf("\nAmount of users = %d\n", usersVal);
        /*for(int i = 0; i < usersVal; i++)
        {
          char name[30];
          Recv(sock, name, 30, 0);
          printf("\nUser: %s\n", name);
        }*/
        break;
      }
      case '3':
      {
        operation = EXIT;
        printf("Exiting...\n");
        Send(sock, &operation, 1, 0);
        exit(0);
      }
      
    }
  }
    
    return 0;
}

不严格判断,我自己知道这一切的样子,我写了一天不停,尽我所能集体耕种,只是为了完成。

我评论了我认为可能包含错误的行,但没有区别。如果您按顺序执行以下操作,则会发生错误:

  1. 启动服务器
  2. 在另一个终端窗口中运行客户端应用程序(我正在运行 Ubuntu)
  3. 输入名字
  4. 在第三个终端窗口中启动一个新的客户端应用程序而不关闭旧的
  5. 输入名称并退出
  6. 转到第二个窗口并尝试获取活跃用户列表(现在有一个存根只显示活跃用户的数量)

是否可以修复此问题,或重做已经存在的内容以使其正常工作?如果有任何建议,我将不胜感激。下面是对该问题的更详细描述。

有必要实施 - “CHAT”。

Необходимо реализовать с использованием сетевых сокетов две программы: Клиент и Сервер. Сервер должен быть один. Клиентов может быть много.

Сервер.

После запуска работает в бесконечном цикле слушая закрепленный за ним сокет, пока не поступит команда на его завершения.

К серверу через сокет, могут поступать следующие виды запросов от клиента:

  1. Запрос на регистрацию (сервер должен вести список зарегистрированных клиентов). При поступлении данного запроса проверяется данный список, если клиент там не зарегистрирован, то происходит его регистрация. Клиенту отправляется ответ с подтверждением регистрации или сообщением о том, что он уже зарегистрирован.

  2. Запрос на получение списка зарегистрированных клиентов. В ответ сервер отправляет Имена клиентов, зарегистрированных в данный момент.

  3. Запрос на передачу сообщения клиенту. (в запросе указывается имя клиента получателя и текст сообщения). Если клиент получатель не зарегистрирован в чате, то отправителю сообщения отправляется об этом информация, иначе сообщение пересылается клиенту получателю.

  4. Запрос на отключение от чата. В этом случае клиент удаляется из чата.

Клиент.

После запуска запрашивает параметры сервера (адрес и порт), а также имя клиента и пытается подключиться к серверу – отправляет запрос на регистрацию. Если подключение невозможно (не получен ответ), то завершает свою работу.

Иначе либо ждет команду от пользователя, либо сообщение от сервера.

При поступлении сообщения, выводит имя отправителя и сам текст сообщения.

Пользователь может ввести следующие команды:

Получить список зарегистрированных в чате. В этом случае клиент запрашивает этот список на сервере и выводит его на экране.

Отправить сообщение. Указывается имя клиента получателя и текст сообщения.

Команда на завершение работы. Отправляется соответствующий запрос на сервер и программа завершает свою работу.

При выполнении лабораторной работы обязательно необходимо для взаимодействия использовать сокеты операционной системы.

c
  • 1 1 个回答
  • 39 Views

1 个回答

  • Voted
  1. Best Answer
    nick_n_a
    2023-05-13T05:54:47Z2023-05-13T05:54:47Z

    Думаю 99%, что виснет сокет на попытке считать что-то, чего в нем нету, что бы это вылечить - сделайте размер пакета одинаковым. Переписать на pipe не поможет, особенность виснуть - прикол локального сокета/pipe, сетевой будет выбивать по timeout. Я с этим не раз сталкивался.

    У вас вижу число 30, например выберем размер пакета 30, тогда... я бы поправил

    void Send(int sockfd, const void *buf, size_t len, int flags)
    {
      char packet[30] = {0,}; // Резервируем буфер
      if (len > 30) return; // страховка, пересывемый размер не должен быть больше чем условились, можно использовать assert, можно бить на несколько пакетов, так делаем что бы было проще
      memcpy(packet, buf, len); // len не должна быть больше длинны пакета! 
      if (send(sockfd, packet, 30, flags) < 0) 
      {
        perror("Failed to send message to server");
        exit(EXIT_FAILURE);
      }
    }
    
     void Recv(int sockfd, void *buf, size_t len, int flags)
     {
        char packet[30] = {0,}; // Резервируем буфер
        if (len > 30) return; // страховка, пересывемый размер не должен быть больше чем условились, можно использовать assert, можно бить на несколько пакетов, так делаем что бы было проще
    
      if (recv(sockfd, packet, 30, flags) < 0) { // Читаем пакет.
          perror("Failed to receive message from server");
          exit(EXIT_FAILURE);
        } 
     memcpy(buf, packet, len); // Достаём из пакета нужное к-во байт
     }
    

    = {0,}; нужен что бы хвост пакета содержал нули, а не мусор, так пррще отлаживать, в рилизе можно убрать.

    Пакеты длинее 30 будут отбрасываться return и не пересылаться, нужно или дописывать дробление пакета на мелкие, либо увеличить размер пакета, либо после того как ошибка будет найдена - вернуть код этой функции как было, если удастся понять где ошибка в "протоколе верхнего уровня", т.е. в правилах обмена REGISTRATION, EXIT и т п.

    На сервере получение пакета можно логировать в консоль, выводить send/recv и первый байт например: printf("recv %c" , packet[0]) или printf("recv %i" , packet[0]) или printf("recv %x" , packet[0]) если пакеты ASCII то первое, если двоичные или смешанные то лучше второе.... а дальше думаю заработает и будет видно где затык.


    Ещё ошибки

    1. read для сокета... если работает то тоже обвернуть как Recv, если не работает - заменить на recv. По документации надо recv кажись использовать.

    2. Второй клиент будет перетирать new_socket и... первый клиент отвалится и т д. Нужно...

    а) самый простой вариант создаём thread, в процедуре thread присваиваем new_socket из глобальной области в стековую переменную, но в материнском thread делаем sleep 100 милисекунд, что бы thread успел её считать. Т.е. void* mainFunc(){int new_socket = ::new_socket; либо int new_socket2 = new_socket; и перебивать имя сокета. Не очень надёжно... можно прилепить semaphore/event и т п для нажёжности, либо увеличивать время sleep.

    б) лучший вариант, правим void* mainFunc() на void* mainFunc(void * arg) из arg читаем сокет socket void* mainFunc(void * arg) { int new_socket = (int)arg; } если не ест то int new_socket = reinterpret_cast<int>(arg), аргумент передаём pthread_create(&thread_id, NULL, mainFunc, /*тут*/ (void*)new_socket) касты разобраться. Можно погуглить примеры серверов типа "простой http сервер на си" и там это подглянуть как сделано.

    https://jameshfisher.com/2017/02/28/tcp-server-pthreads/ - вот так правильно, хотя не везде соберётся.

    Брать адрес глобальной переменной нельзя, т.к. она будет стёрта сделующим запросом, некоторые такую ошибку делают. malloc - тоже так себе вариант.

    в) Если не получилось a и б - создаем массив, делаем счётчик. В массив материнский thread складывает new_socket обязательно с mutex и до создания thread, а новый thread обязательно с этим же mutex забирает через счётчик new_socket назад себе и сохраняет это значение обязательно в стековую переменную, её и подставлять в recv/send. Массив на хотя бы 8 элементов, при переполнении массива - ждать, если не проверять массив - приложение упадёт если много клиентов одновременно зайдут.

    • 2

相关问题

  • free 出于某种原因不会从内存中删除数组

  • 请帮助代码

  • 为什么 masm 对字符串或文本文字太长发誓,为什么在结构中设置 db 或 dw?

  • 如何将数字拆分为位并将其写入 C 中的数组?

  • 如何以给定的角度移动物体?

  • 解决“子集和问题”的时效算法

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5