сокеты linux: сервер выходит перед клиентом - PullRequest
1 голос
/ 27 июня 2010

У меня есть эта прога, просто скелет для простого подключения сервера и клиента. Я сделаю это в чате. (не возражайте против функции потока и сигналов ..)

сервер:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>


#define BUFFERSIZE 512
#define TIMESIZE 32
#define QUIT "!quit"


// thread pou diavazei
void *readthread(void *argp);
// katharizei ligo prin kleisei to programma
void progreset();
// kleinei to prog me ctrl-c
void sigexit();


int sock, endchat;
char username1[50];
pthread_t thrread;


int main(int argc, char** argv) {
    int port, s;
    char username[50];
    struct sockaddr_in server, client;
    struct sockaddr *serverptr, *clientptr;
    unsigned int clientlen;
    char buf[BUFFERSIZE];
    int len;
    time_t sec;
    char timestr[TIMESIZE];


    signal(SIGPIPE, SIG_IGN);
    signal(SIGINT, sigexit);


    if (argc != 3) {
        printf("Error: Wrong arguments\n");
        printf("Usage: %s <username> <port>\n", argv[0]);
        return -1;
    }

    strcpy(username, argv[1]);
    port = atoi(argv[2]);

    // ftiaxno to socket
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Failed to create socket");
        return -1;
    }
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    server.sin_port = htons((short)port);
    if (bind(s, (struct sockaddr *)&server, sizeof(server)) < 0) {
        perror("Failed to bind socket");
        return -1;
    }

    if (listen(s, 5) != 0) {
        perror("Error in listen()");
        return -1;
    }

    clientptr = (struct sockaddr *)&client;
    clientlen = sizeof(client);

    // perimeno sindesi apo ton pelati
    printf("Accepting connections on port %d..\n", port);
    if ((sock = accept(s, clientptr, &clientlen)) < 0) {
        perror("Error in accept()");
        return -1;
    }

    // pairno to ip tou pelati
    if (getpeername(sock, (struct sockaddr *)&client, &clientlen) < 0) {
        printf("Accepted connection\n");
    } else {
        printf("Accepted connection from %s\n", inet_ntoa(client.sin_addr));
    }

    // stelno kai pairno ta usernames
    bzero(buf, sizeof(buf));
    strcpy(buf, username);
    if (write(sock, buf, sizeof(buf)) < 0) {
        perror("write1");
        return -1;
    }

    bzero(buf, sizeof(buf));
    if (read(sock, buf, sizeof(buf)) < 0) {
        perror("read1");
        return -1;
    }
    strcpy(username1, buf);

    printf("Chatting with %s..\n\n", username1);








    progreset();
    return 0;
}


void *readthread(void *argp) {
    char buf[BUFFERSIZE];
    char timestr[TIMESIZE];
    int len;
    time_t sec;
    struct tm *timeinfo;


    while (1) {

    }



    endchat = 1;
    pthread_exit(0);
}


void progreset() {
    printf("\nExiting..\n");
    close(sock);
}


void sigexit() {
    printf("test\n");
    close(sock);
    signal(SIGINT, SIG_DFL);
    kill(getpid(),SIGINT);
    printf("ok\n");

    exit(0);
}

клиент

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


#define BUFFERSIZE 512
#define TIMESIZE 32
#define QUIT "!quit"


// thread pou diavazei
void *readthread(void *argp);
// katharizei ligo prin kleisei to programma
void progreset();
// kleinei to prog me ctrl-c
void sigexit();


int sock, endchat;
char username1[50];
pthread_t thrread;


int main(int argc, char** argv) {
    int port;
    char username[50];
    struct sockaddr_in server, client;
    struct sockaddr *serverptr, *clientptr;
    unsigned int serverlen;
    struct hostent *rem;
    char buf[BUFFERSIZE];
    int len;
    time_t sec;
    char timestr[TIMESIZE];


    signal(SIGPIPE, SIG_IGN);
    signal(SIGINT, sigexit);


    if (argc != 4) {
        printf("Error: Wrong arguments\n");
        printf("Usage: %s <username> <ip address> <port>\n", argv[0]);
        return -1;
    }

    strcpy(username, argv[1]);
    port = atoi(argv[3]);

    // ftiaxno to socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Failed to create socket");
        return -1;
    }
    rem = gethostbyname(argv[2]);
    server.sin_family = AF_INET;
    bcopy((char*)rem->h_addr, (char*)&server.sin_addr, rem->h_length);
    server.sin_port = htons((short)port);
    serverptr = (struct sockaddr *)&server;
    serverlen = sizeof(server);

    // kano connect me to server
    if (connect(sock, serverptr, serverlen) < 0) {
        perror("Failed to connect");
        return -1;
    }

    // pairno kai stelno ta usernames
    bzero(buf, sizeof(buf));
    if (read(sock, buf, sizeof(buf)) < 0) {
        perror("read1");
        return -1;
    }
    strcpy(username1, buf);

    bzero(buf, sizeof(buf));
    strcpy(buf, username);
    if (write(sock, buf, sizeof(buf)) < 0) {
        perror("write1");
        return -1;
    }

    printf("Chatting with %s..\n\n", username1);







    sleep(1);










    progreset();
    return 0;
}


void *readthread(void *argp) {
    char buf[BUFFERSIZE];
    char timestr[TIMESIZE];
    int len;
    time_t sec;
    struct tm *timeinfo;

    while (1) {
    }
    endchat = 1;
    pthread_exit(0);
}


void progreset() {
    printf("\nExiting..\n");
    close(sock);
}


void sigexit() {
    printf("test\n");
    close(sock);
    signal(SIGINT, SIG_DFL);
    kill(getpid(),SIGINT);
    printf("ok\n");

    exit(0);
}

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

когда я запускаю это, например ./server server 1234 а затем ./client клиент localhost 1234 оба выходят нормально, но когда я запускаю сервер во второй раз, он говорит, что не удалось связать сокет: Адрес уже используется.

что не так? сервер должен всегда выходить после клиента?

бонус В: Я хочу, чтобы в каждой программе было два потока: один для чтения и один для записи. Могут ли они работать в одном сокете?

Большое спасибо

1 Ответ

1 голос
/ 27 июня 2010

Убедитесь, что вы вызываете closesocket (s) на сокете перед выходом.
Также попробуйте setsockopt SO_REUSEADDR, прежде чем открывать.

Здесь - хорошее руководство по сокетам Linux.

Конечно, если вы разместите некоторый код, это облегчит его работу.

Вы можете прочитать изапись в тот же сокет.

Я подозреваю, что вы получаете "Ошибка использования адреса" (EADDRINUSE)
Вы можете использовать функцию API связывания, чтобы связать адрес (интерфейс и порт) с конечной точкой сокета,Вы можете использовать эту функцию в настройках сервера, чтобы ограничить интерфейсы, из которых возможны входящие соединения.Вы также можете использовать эту функцию в настройках клиента, чтобы ограничить интерфейс, который следует использовать для исходящего соединения.
Наиболее распространенное использование bind - это связывание номера порта с сервером и использование подстановочного адреса (INADDR_ANY),что позволяет использовать любой интерфейс для входящих соединений.
Проблема, с которой обычно сталкиваются при связывании, - это попытка связать порт, который уже используется.Подводный камень в том, что активный сокет может не существовать, но привязка к порту по-прежнему запрещена (bind возвращает EADDRINUSE), что вызвано состоянием сокета TCP TIME_WAIT.Это состояние удерживает сокет в течение двух-четырех минут после его закрытия.
После выхода из состояния TIME_WAIT сокет удаляется, и адрес может быть восстановлен без проблем.
Ожидание завершения TIME_WAIT может раздражатьособенно если вы разрабатываете сервер сокетов и вам нужно остановить сервер, чтобы внести изменения, а затем перезапустить его.К счастью, есть способ обойти состояние TIME_WAIT.Вы можете применить опцию сокета SO_REUSEADDR к сокету, чтобы порт можно было сразу же повторно использовать.Перед привязкой адреса я вызываю setsockopt с опцией SO_REUSEADDR.Чтобы разрешить повторное использование адресов, я установил для целочисленного аргумента (вкл.) Значение 1 (в противном случае его можно установить равным 0, чтобы отключить повторное использование адресов).

Предотвращение ошибки «Адрес в использовании» с помощью параметра сокета SO_REUSEADDR

int sock, ret, on;
struct sockaddr_in servaddr;

/* Create a new stream (TCP) socket */
sock = socket( AF_INET, SOCK_STREAM, 0 ):

/* Enable address reuse */
on = 1;
ret = setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );

/* Allow connections to port 8080 from any available interface */
memset( &servaddr, 0, sizeof(servaddr) );
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
servaddr.sin_port = htons( 45000 );

/* Bind to the address (interface/port) */
ret = bind( sock, (struct sockaddr *)&servaddr, sizeof(servaddr) );

После того, как вы применили опцию сокета SO_REUSEADDR, функция bind API всегда разрешит немедленное повторное использование адреса.

...