Chat w Linuksie z obsługą TCP/IP i UNIX Domain Sockets

    -- Sebastian Pawlak, 2002.


Przedstawiony program demonstruje użycie socketów TCP/IP i UNIX Domain Sockets, wątków, sygnałów, semaforów binarnych (mutex) i biblioteki Slang w systemie operacyjnym Linux. Rozmowa za pomocą programu możliwa jest pomiędzy dwiema osobami w sieci. Osoba, która uruchomi program jako pierwsza staje się serwerem. Program, który po uruchomieniu wykryje obecność na danym porcie, pod wskazanym adresem serwera, przechodzi w tryb klienta.


Kod źródłowy pliku "czat.c":

/* Prosty czat p2p na gniazdach TCP i UNIX Domain Sockets.
 * autor: Sebastian Pawlak, 2002.
 */

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <slang/slang.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
#include <pthread.h>

enum {
    MAX_ROZMIAR_NICKA = 16,
    MAX_BUFOR = 256,
    PORT = 6666
};
#define UNIX_DS_NAME "CzatSock"

int sockfd;
int typPolaczenia;
char nick[MAX_ROZMIAR_NICKA + 1],
     nickAdwersarza[MAX_ROZMIAR_NICKA + 1];

void polaczTCP(char *ip);
void polaczUnix(char *ip);
static void obslugaSygnalow (int sig);
void initSlang(void);
void koniecSlang(void);
void *przesylanieOdbiorDanych(void *arg);
void *ekranKlawiatura(void *arg);


int main(int argc, char *argv[])
{
    pthread_t przesOdczDanychThread, ekrKlawThread;
    int zezwol = 1;

    /* analiza parametrow wywolania */	
    if ((argc == 3 && strcmp(argv[1], "-u")) ||
	(argc == 4 && strcmp(argv[1], "-t")) ||
         argc < 3 || argc > 4)  {
        if (argc != 1)
            fprintf(stderr, "Bledne parametry!\n\n");

        printf("  uzycie: ./czat -u|\"-t adresIP\" nick\n"
               "   gdzie: -u - UNIX domain sockets,\n"
               "          -t - TCP,\n"
               "          adresIP - adres IP adwersarza,\n"
               "          nick - twoj identyfikator.\n"
               "przyklad: ./czat -t 127.0.0.1 Bazyli\n"
               "          ./czat -u Bazyli\n");
        exit(1);
    }

    strncpy(nick, argv[argc - 1], MAX_ROZMIAR_NICKA);

    /* obsluga sygnalow */
    SLang_set_abort_signal(obslugaSygnalow); /* SIGINT */
    SLsignal(SIGQUIT, obslugaSygnalow);
    SLsignal(SIGPIPE, obslugaSygnalow);
    SLsignal(SIGTERM, obslugaSygnalow);
    SLsignal(SIGTSTP, obslugaSygnalow);

    /* ustanawianie polaczenia */    
    if (argv[1][1] == 't')
        polaczTCP(argv[2]);
    else
        polaczUnix(argv[2]);

    /* inicjalizacja terminala */ 
    initSlang();

    /* start watkow */    
    if (pthread_create(&przesOdczDanychThread, NULL,
                       &przesylanieOdbiorDanych, NULL) == -1) {
        perror("pthread1");
        exit(1);
    }
    if (pthread_create(&ekrKlawThread, NULL,
                       &ekranKlawiatura, NULL) == -1) {
        perror("pthread2");
        exit(1);
    }
   
    while(1)
        sleep(1);

    return 0;
}


/* Ustanawia polaczenie za pomoca TCP.
 */
void polaczTCP(char *ip)
{
    struct sockaddr_in adres;
    int zezwol = 1;

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }
   
    bzero(&adres, sizeof(adres));
    adres.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &adres.sin_addr);
    adres.sin_port = htons(PORT);

    if (connect(sockfd, (struct sockaddr *)&adres, sizeof(adres)) == -1) {
        /* Nie udalo sie polaczyc z innym klientem wiec stawiamy
         * serwer zeby inny klient mogl podlaczyc sie do nas.
         */
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            perror("socket(2)");
            exit(1);
        }

        bzero(&adres, sizeof(adres));
        adres.sin_family = AF_INET;
        adres.sin_addr.s_addr = htonl(INADDR_ANY);
        adres.sin_port = htons(PORT);

        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
                       &zezwol, sizeof(zezwol)) == -1) {
            perror("setsockopt");
            exit(1);
        }
	
        if (bind(sockfd, (struct sockaddr *)&adres, sizeof(adres)) == -1) {
            perror("bind");
            exit(1);
        }

        if (listen(sockfd, 1) == -1) {
            perror("listen");
            exit(1);
        }

        sockfd = accept(sockfd, NULL, NULL);

        /* wymiana nickow */
        send(sockfd, nick, strlen(nick) + 1, 0);
        recv(sockfd, nickAdwersarza, MAX_ROZMIAR_NICKA + 1, 0);
    } else {
        /* wymiana nickow */
        recv(sockfd, nickAdwersarza, MAX_ROZMIAR_NICKA + 1, 0);
        send(sockfd, nick, strlen(nick) + 1, 0);
    }
}


/* Ustanawia polaczenie za pomoca Unix Domain Sockets.
 */
void polaczUnix(char *ip)
{
    struct sockaddr_un adres = { AF_UNIX, UNIX_DS_NAME };

    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    if (connect(sockfd, (struct sockaddr *)&adres, sizeof(adres)) == -1) {
        /* Nie udalo sie polaczyc z innym klientem wiec stawiamy
         * serwer zeby inny klient mogl podlaczyc sie do nas.
         */

        if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
            perror("socket(2)");
            exit(1);
        }

        if (bind(sockfd, (struct sockaddr *)&adres, sizeof(adres)) == -1) {
            perror("bind(2)");
            exit(1);
        }

        if (listen(sockfd, 1) == -1) {
            perror("listen");
            exit(1);
        }

        if ((sockfd = accept(sockfd, 0, 0))  == -1) {
            perror("accept");
            unlink(adres.sun_path);
            exit(1);
        }

        /* wymiana nickow */
        send(sockfd, nick, strlen(nick) + 1, 0);
        recv(sockfd, nickAdwersarza, MAX_ROZMIAR_NICKA + 1, 0);
    } else {
        /* wymiana nickow */
        recv(sockfd, nickAdwersarza, MAX_ROZMIAR_NICKA + 1, 0);
        send(sockfd, nick, strlen(nick) + 1, 0);
    }

    typPolaczenia = 1;
}

int w;
/* Obsluga sygnalow przekazywanych do programu.
 */
static void obslugaSygnalow (int sig)
{
    if (w == 1)
        return;
    w = 1;
    close(sockfd);
    if (typPolaczenia)
        unlink(UNIX_DS_NAME);
    koniecSlang();

    switch (sig) {
        case SIGPIPE:
            printf("Adwersarz rozlaczyl sie.\n");
            break;
        case SIGINT:
            printf("Program zakonczony przez uzytkownika.\n");
            break;
        case SIGTERM: case SIGQUIT: 
            printf("Przerwano program.\n");
            break;
        default:
            printf("Koniec.\n");
            break;
    }

    exit(0);
}


int okno1wysokosc, okno2wysokosc;   /* okna edycji */
int okno1pozycja, okno2pozycja;
int wysokosc, szerokosc;            /* caly ekran */

/* Inicjalizacja terminala.
 */
void initSlang(void)
{
    SLtt_get_terminfo();
    if (SLang_init_tty(3, 0, 0) == -1) { /* wyjscie ctrl+c */
        perror("Slang_init_tty");
        exit(1);
    }

    SLsmg_init_smg();
    SLtt_set_color(1, NULL, "green", "black");
    SLtt_set_color(2, NULL, "black", "green");

    wysokosc = SLtt_Screen_Rows;
    szerokosc = SLtt_Screen_Cols;

    /* Kreslenie grafiki */    
    SLsmg_set_color(2);
    SLsmg_gotorc(0, 0);
    SLsmg_erase_eol();
    SLsmg_printf(" Czat ");
    SLsmg_write_char(SLSMG_VLINE_CHAR);
    SLsmg_gotorc(0, szerokosc - 16);
    SLsmg_printf("wyjscie: ctrl+c");
    SLsmg_set_color(1);

    okno1pozycja = 1;
    okno1wysokosc = (wysokosc - 1) / 2;
    okno2pozycja = 1 + okno1wysokosc;
    okno2wysokosc = wysokosc - 1 - okno1wysokosc;

    /* gorne okno edycji */
    SLsmg_draw_box(okno1pozycja, 0, okno1wysokosc, szerokosc);
    SLsmg_gotorc(okno1pozycja, 1);
    SLsmg_write_char(SLSMG_RTEE_CHAR);
    SLsmg_write_nchars(nickAdwersarza, MAX_ROZMIAR_NICKA + 1);
    SLsmg_write_char(SLSMG_LTEE_CHAR);

    /* dolne okno edycji */
    SLsmg_draw_box(okno2pozycja, 0, okno2wysokosc, szerokosc);
    SLsmg_gotorc(okno2pozycja, 1);
    SLsmg_write_char(SLSMG_RTEE_CHAR);
    SLsmg_write_nchars(nick, MAX_ROZMIAR_NICKA + 1);
    SLsmg_write_char(SLSMG_LTEE_CHAR);
    SLsmg_refresh();
}


/* Dezaktywacja terminala.
 */
void koniecSlang(void)
{
    SLsmg_reset_smg();
    SLang_reset_tty();
}

/* Przesuwa blok tekstu o wiersz do gory.
 * y - nr wiersza, od ktorego zaczyna sie gorna linia okna,
 * wysok - wysokosc okna lacznie z gorna i dolna linia.
 */
void przesun(int y, int wysok)
{
    int i, l;
    SLsmg_Char_Type buf[256];

    for (i = 0; i < wysok - 3; i++) {
        SLsmg_gotorc(i + y + 2, 1);
        l = SLsmg_read_raw(buf, (szerokosc - 2 > 256) ?
                                256 : szerokosc - 2);
        SLsmg_gotorc(i + y + 1, 1);
        SLsmg_write_raw(buf, l);
    }

    /* czysc ostatni wiersz okna */
    SLsmg_fill_region(y + wysok - 2, 1, 1, szerokosc - 2, ' ');
}


pthread_mutex_t mojMutex = PTHREAD_MUTEX_INITIALIZER;
char buforOdbiorczy[MAX_BUFOR] = { '\0' },
     buforNadawczy[MAX_BUFOR] = { '\0' };
int odebrano, doWyslania;

/* Funkcja obslugi watka, ktorego zadaniem jest
 * wysylanie i odbieranie danych miedzy rozmowcami.
 */
void *przesylanieOdbiorDanych(void *arg)
{
    pthread_detach(pthread_self());

    while (1) {
        pthread_mutex_lock(&mojMutex);
        if (recv(sockfd, buforOdbiorczy, 256, MSG_DONTWAIT) > 0)
            odebrano = 1;

        if (doWyslania) {
            send(sockfd, buforNadawczy,
                 strlen(buforNadawczy) + 1, MSG_DONTWAIT);
            doWyslania = 0;
            bzero(buforNadawczy, sizeof(buforNadawczy[0]) * MAX_BUFOR);
        }
        pthread_mutex_unlock(&mojMutex);

        usleep(10000);
    }
}


/* Funkcja obslugi watka, ktorego zadaniem jest
 * wyswietlanie danych, ktore nadeszly oraz
 * pobieranie znakow z klawiatury.
 */
void *ekranKlawiatura(void *arg)
{
    char z[2] = { '\0' };
    int y1 = 0, y2 = 0;
    int cX = 11, cY = 1 + okno2pozycja;
    time_t czasKal = { 0 }, czasKalStary = { 0 };
    struct tm *czas;

    pthread_detach(pthread_self());

    while (1) {
        if (!doWyslania &&
            difftime(czasKal = time(NULL), czasKalStary) >= 1) {
            czas = localtime(&czasKal);
            strftime(buforNadawczy, 10, "%H:%M:%S>", czas);
            buforNadawczy[strlen(buforNadawczy)] = ' ';
            SLsmg_gotorc(1 + okno2pozycja + y2, 1);
            SLsmg_printf(buforNadawczy);
            SLsmg_gotorc(cY, cX);
            SLsmg_refresh();
            czasKalStary = czasKal;
        }

	if (odebrano) {
            pthread_mutex_lock(&mojMutex);
            odebrano = 0;
            if (y1 > okno1wysokosc - 3) {
                przesun(okno1pozycja, okno1wysokosc);
                y1--;
            }
            SLsmg_gotorc(1 + okno1pozycja + y1, 1);
            SLsmg_write_nchars(buforOdbiorczy, szerokosc - 2);
            y1++;
            pthread_mutex_unlock(&mojMutex);
            SLsmg_gotorc(cY, cX);
            SLsmg_refresh();
        }

        if (!doWyslania && SLang_input_pending(1) && buforNadawczy[0] != '\0' &&
            (z[0] = SLang_getkey()) != 9) { /* z[0] != '\t' */
            SLang_flush_input();
            if (z[0] == 13) { /* enter */
                pthread_mutex_lock(&mojMutex);
                doWyslania = 1;
                pthread_mutex_unlock(&mojMutex);
                y2++;
                if (y2 > okno2wysokosc - 3) {
                    przesun(okno2pozycja, okno2wysokosc);
                    y2--;
                }
                cX = 11;
                cY = 1 + okno2pozycja + y2;

                continue;
            }
	    
            if (z[0] == 127 && cX > 11) { /* backspace */
                buforNadawczy[strlen(buforNadawczy) - 1] = '\0';
                SLsmg_gotorc(cY, cX - 1);
                SLsmg_write_char(' ');
                SLsmg_gotorc(cY, cX - 1);
            } else if ((cX < szerokosc - 1) && /* inny znak */
                       (strlen(buforNadawczy) < MAX_BUFOR - 1)) {
                strcat(buforNadawczy, z);
                SLsmg_write_char(z[0]);
            }
            cX = SLsmg_get_column();
            cY = SLsmg_get_row();
            SLsmg_refresh();
        }	       
        usleep(10000);
    }
}
w3cw3c
automatyka przemysłowa