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





