@shevzoom
 dev., student at MEPhI

Почему не работает TCP чат?

Переделываю шаблон клиент-сервера TCP для чат сервера с n-узлами.
Добавил buffer и прописал send(), recv()
Изменений 0, как был эхо сервер, так и остался, как исправить и сделать чат?

Сервер:
/*
 * Шаблон параллельного эхо-сервера TCP, работающего по модели
 * "один клиент - один поток".
 *
 * Компиляция:
 *  cc -Wall -O2 -pthread -o server3 server3.c
 */

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

/*
 * Конфигурация сервера.
 */
#define PORT 1027
#define BACKLOG 5
#define MAXLINE 256

#define SA struct sockaddr

// Обработчик фатальных ошибок.
void error(const char *s) {
    perror(s);
    exit(-1);
}

/*
 * Функции-обертки.
 */
int Socket(int domain, int type, int protocol) {
    int rc;

    rc = socket(domain, type, protocol);
    if(rc == -1) error("socket()");

    return rc;
}

int Bind(int socket, struct sockaddr *addr, socklen_t addrlen) {
    int rc;

    rc = bind(socket, addr, addrlen);
    if(rc == -1) error("bind()");

    return rc;
}

int Listen(int socket, int backlog) {
    int rc;

    rc = listen(socket, backlog);
    if(rc == -1) error("listen()");

    return rc;
}

int Accept(int socket, struct sockaddr *addr, socklen_t *addrlen) {
    int rc;

    for(;;) {
        rc = accept(socket, addr, addrlen);
        if(rc != -1) break;
        if(errno == EINTR || errno == ECONNABORTED) continue;
        error("accept()");
    }

    return rc;
}

void Close(int fd) {
    int rc;

    for(;;) {
        rc = close(fd);
        if(!rc) break;
        if(errno == EINTR) continue;
        error("close()");
    }
}

size_t Read(int fd, void *buf, size_t count) {
    ssize_t rc;

    for(;;) {
        rc = read(fd, buf, count);
        if(rc != -1) break;
        if(errno == EINTR) continue;
        error("read()");
    }

    return rc;
}

size_t Write(int fd, const void *buf, size_t count) {
    ssize_t rc;

    for(;;) {
        rc = write(fd, buf, count);
        if(rc != -1) break;
        if(errno == EINTR) continue;
        error("write()");
    }

    return rc;
}

void *Malloc(size_t size) {
    void *rc;

    rc = malloc(size);
    if(rc == NULL) error("malloc()");

    return rc;
}

void Pthread_create(pthread_t *thread, pthread_attr_t *attr,
                    void *(*start_routine)(void *), void *arg) {
    int rc;

    rc = pthread_create(thread, attr, start_routine, arg);
    if(rc) {
        errno = rc;
        error("pthread_create()");
    }
}

/*
 * Чтение строки из сокета.
 */
size_t reads(int socket, char *s, size_t size) {
    char *p;
    size_t n, rc;

    /* Проверить корректность переданных аргументов. */
    if(s == NULL) {
        errno = EFAULT;
        error("reads()");
    }
    if(!size) return 0;

    p = s;
    size--;
    n = 0;
    while(n < size) {
        rc = Read(socket, p, 1);
        if(rc == 0) break;
        if(*p == '\n') {
            p++;
            n++;
            break;
        }
        p++;
        n++;
    }
    *p = 0;

    return n;
}

/*
 * Запись count байтов в сокет.
 */
size_t writen(int socket, const char *buf, size_t count) {
    const char *p;
    size_t n, rc;

    /* Проверить корректность переданных аргументов. */
    if(buf == NULL) {
        errno = EFAULT;
        error("writen()");
    }

    p = buf;
    n = count;
    while(n) {
        rc = Write(socket, p, n);
        n -= rc;
        p += rc;
    }

    return count;
}

void *serve_client(void *arg) {
    int socket;
    char s[MAXLINE];
    ssize_t rc;

    /* Перевести поток в отсоединенное (detached) состояние. */
    pthread_detach(pthread_self());

    socket = *((int *) arg);
    free(arg);

    while((rc = reads(socket,s,MAXLINE)) > 0) {if(writen(socket,s,rc) == -1) break;}
    Close(socket);

    return NULL;
}

int main(void) {
    int lsocket;    /* Дескриптор прослушиваемого сокета. */
    int csocket;    /* Дескриптор присоединенного сокета. */
    struct sockaddr_in servaddr;
    int *arg;
    pthread_t thread;

    /* Создать сокет. */
    lsocket = Socket(PF_INET, SOCK_STREAM, 0);

    /* Инициализировать структуру адреса сокета сервера. */
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    /* Связать сокет с локальным адресом протокола. */
    Bind(lsocket, (SA *) &servaddr, sizeof(servaddr));

    /* Преобразовать неприсоединенный сокет в пассивный. */
    Listen(lsocket, BACKLOG);

    char buffer[BUFSIZ];
    for(;;) {
        csocket = Accept(lsocket, NULL, 0);
        send(csocket, buffer, BUFSIZ, 0);//

        arg = Malloc(sizeof(int));
        *arg = csocket;

        Pthread_create(&thread, NULL, serve_client, arg);

        printf("Client"); //
        recv(csocket, buffer, BUFSIZ,0);//
        
    }

    return 0;
}


Клиент:
/*
 * Шаблон TCP клиента.
 *
 * Компиляция:
 *  cc -Wall -O2 -o client client.c
 *
 * Завершение работы клиента: Ctrl+D.
 */

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

#include <limits.h>

#define MAX(a, b) ((a) > (b) ? (a) : (b))

#define PORT 1027
#define MAXLINE 256

#define SA struct sockaddr

/*
 * Обработчик фатальных ошибок.
 */
void error(const char *s) {
    perror(s);
    exit(-1);
}

/*
 * Функции-обертки.
 */
int Socket(int domain, int type, int protocol) {
    int rc;

    rc = socket(domain, type, protocol);
    if(rc == -1) error("socket()");

    return rc;
}

void Connect(int socket, const struct sockaddr *addr, socklen_t addrlen) {
    int rc;

    rc = connect(socket, addr, addrlen);
    if(rc == -1) error("connect()");
}

void Close(int fd) {
    int rc;

    for(;;) {
        rc = close(fd);
        if(!rc) break;
        if(errno == EINTR) continue;
        error("close()");
    }
}

void Inet_aton(const char *str, struct in_addr *addr) {
    int rc;

    rc = inet_aton(str, addr);
    if(!rc) {
        /* Функция inet_aton() не меняет errno в случае ошибки. Чтобы
        сообщение, выводимое error(), было более осмысленным,
        присваиваем errno значение EINVAL. */

        errno = EINVAL;
        error("inet_aton()");
    }
}

int Select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
           struct timeval *timeout) {
    int rc;

    for(;;) {
        rc = select(n, readfds, writefds, exceptfds, timeout);
        if(rc != -1) break;
        if(rc == EINTR) continue;
        error("select()");
    }

    return rc;
}

size_t Read(int fd, void *buf, size_t count) {
    ssize_t rc;

    for(;;) {
        rc = read(fd, buf, count);
        if(rc != -1) break;
        if(errno == EINTR) continue;
        error("read()");
    }

    return rc;
}

size_t Write(int fd, const void *buf, size_t count) {
    ssize_t rc;

    for(;;) {
        rc = write(fd, buf, count);
        if(rc != -1) break;
        if(errno == EINTR) continue;
        error("write()");
    }

    return rc;
}

/*
 * Запись count байтов в сокет.
 */
size_t writen(int socket, const char *buf, size_t count) {
    const char *p;
    size_t n, rc;

    /* Проверить корректность переданных аргументов. */
    if(buf == NULL) {
        errno = EFAULT;
        error("writen()");
    }

    p = buf;
    n = count;
    while(n) {
        rc = Write(socket, p, n);
        n -= rc;
        p += rc;
    }
    return count;
}

void show_usage() {
    puts("Usage: client ip_address");
    exit(-1);
}

void do_work(int socket) {
    int n;
    fd_set readfds;
    char s[MAXLINE];
    ssize_t rc;

    n = MAX(STDIN_FILENO, socket) + 1;

    for(;;) {
        /* Инициализировать набор дескрипторов. */
        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);
        FD_SET(socket, &readfds);

        Select(n, &readfds, NULL, NULL, NULL);
        if(FD_ISSET(STDIN_FILENO, &readfds)) {
            rc = Read(STDIN_FILENO, s, MAXLINE);
            if(!rc) break;
            writen(socket, s, rc);
        }
        if(FD_ISSET(socket, &readfds)) {
            rc = Read(socket, s, MAXLINE);
            if(!rc) break;
            Write(STDOUT_FILENO, s, rc);
        }
    }
}

int main(int argc, char **argv) {
    int socket;
    struct sockaddr_in servaddr;

    if(argc != 2) show_usage();

    socket = Socket(PF_INET, SOCK_STREAM, 0);

    /* Инициализировать структуру адреса сокета. */
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    Inet_aton(argv[1], &servaddr.sin_addr);

    // char buffer[BUFSIZ];
    // recv(socket, buffer, BUFSIZ);
    
    // while(true) {
    //     send();
    //     recv();
    // }

    Connect(socket, (SA *) &servaddr, sizeof(servaddr));
    do_work(socket);
    Close(socket);

    return 0;
}
  • Вопрос задан
  • 198 просмотров
Пригласить эксперта
Ответы на вопрос 1
@FirststepsRu
По коду быстро понять сложно, не зная что было до изменений и какие изменения были внесены. По задаче эхо сервер это не чат. Задача эхо сервера отправлять клиенту теже данные, что он ему присал, это его смысл. Смысл чата в обратном, надо отправлять данные от клиента всем другим подключенным клиентам на сервере, кроме этого клиента (чтобы не было эха).

По коду сервера не видно, что вы создаете список клиентов подключенных к нему. Только лишь отдельный поток для подключения. Поэтому клиент видит только то, что сам послал. А в режиме чата данные должны были бы передаваться между потоками через какую-то общую очередь сообщений, ведь клиенты могут быть подключены с разными скоростями и не все могут справиться одновременно с получением сообщения.

Попробуйте изучить код чата реализуемого в https://eax.me/libevent/, за одно и изучите великолепную библиотеку. Создание потока на каждое соединение приводит к быстрому исчерпанию ресурсов и приводит к так называемой проблеме C10K (10000 соединений). В мире когда миллиарды клиентов могут обращаться к серверу решения по схеме поток на соединение имеют архитектурный изъян.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы