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); } }