25 Maj 2001 Politechnika Wrocławska Dokumentacja programu sieciowego - komunikacja z portem szeregowym Autor: Grzegorz Siwek
Spis treści: Wstęp:... Ogólna budowa i działanie, założenia projektowe, 3 SERWER... 4 Rozwiązanie techniczne... 4 Konfiguracja i Uruchamianie serwera... 5 Opis klas serwera... 6 KLIENT... 10 Przykłady i zastosowanie... 10 Opis klas klienta... 11 KOMUNIKACJA ZA POMOCA TELNET... 12 Podsumowanie i licencja... 14 2
Wstęp Program stworzony został na platformę LINUX lub kompatybilną, testowany na dystrybucji Debian 2.2. Program składa się z serwera, który pozwala na komunikacje z portem szeregowym, danego komputera na którym jest włączony, przez gniazdo sieciowe do którego jest podłączony klient protokołem TCP\IP. Można przedstawić to w następujący sposób (rys 1). Rys. 1 Działanie oprogramowania Do komunikacji z klientem została stworzona klasa, która ułatwi rozmowę z serwerem oraz może być wykorzystywana w innych programach, które będą korzystały z serwera. Do komunikacji może tez posłużyć zwykły telnet (rozdział ostatni). Założenia projektowe: - przesyłanie danych jako typ string - komunikacja przez TCP/IP - możliwość pracy dla wielu użytkowników - wybór danego serial - prostota konfiguracji serial - prostota obsłudze połączenia - udostępnianie serwera tylko danym hostom - niezawodność 3
SERWER Standardowym modelem zastosowania sieci komputerowych jest model komunikacji klient-serwer. Serwer jest procesem czekającym na to, by skontaktował się z nim proces, zwany klientem, dla którego serwer ma coś wykonać. Rozwiązanie techniczne Serwer został wykonany jako serwer współbieżny opierając się na funkcji select, która zasypia gdy żaden klient nie wywoła żądania, ale gdy któryś wyśle żądanie wtedy serwer się uaktywnia, również się uaktywni gdy z portu szeregowego nadejdą dane, wtedy serwer obierze dane i wyśle je do danego użytkownika. Ogólny schemat działania serwera jest pokazany na rysunku 2. Rys 2. Budowa blokowa serwera. 4
Konfiguracja i Uruchamianie serwera 1.Instalacja polecenie make umożliwia skompilowanie źródeł umieszczonych w katalogu scr 2. Konfiguracja pliku conf.txt UWAGA!!! Wymagana jest tylko jedna spacja pomiędzy wartością a nazwą zmiennej np. ALL(spacja)127.0.0.1 Przykładowa konfiguracja: # IP komputerów które mogą być połączone z serwerem ALL 192.1.1.0 ALL 127.0.0.1 # Max. ilość połączeń MAX_CONNECT 2 # nazwa pliku logów LOGFILE logfile.log # ========================== USTAWIENIA PORTU COM # Ustawienia do romika NAME romik # nazwa konfiguracji COM 1 # nr portu szeregowego BAUD 19200 PARITY NONE XONXOFF DISABLE STOPBITS 1 DATABITS 8 COM_CLOSE ~!quit # ciąg znaków pozwalających na zakończenie połączenia z portem szeregowym COM_CLOSE_TIME 1 # czas po jakim ciąg zakończenia będzie brany pod uwagę, liczony w #sekundach od ostatniego wysłania danych 3. Uruchomienie./comserv [port] domyślny 7000 5
Opis klas serwera Podstawowe funkcje dla klasy server_app która dziedziczy z class_server UWAGA!!! Ta klasa korzysta z pliku konfiguracyjnego. STRUKTURY: st_user *user Struktura danych przechowująca informacje o użytkowniku i portu z którego on korzysta składa się z następujących części: struct st_user { int dp; przechowuje wartość deskryptora pliku int com; przechowuje połączenie do deskryptora com int nr; przechowuje nr połączenia z pliku conf.txt char buff[1024]; bufor w którym dane zostały zgromadzone unsigned long int ip; przechowuje IP użytkownika połączonego bool flaga_telnet; NIE UZYWANA char ccs[20]; przechowuje string który mówi służy do zakończeniu zamknięcia coma int cct; przechowuje odstęp czasu po jakim sprawdza czy string wysłany służy do zakończenia int krok_koncowy; pomocnicza mówi o kroku zakończenia połączenia coma; } time_t timeval przechowuje czas ZMIENNE: private int ilecom przechowuje ilość dostępnych konfiguracji private int ipol przechowuje ilość połączeń użytkowników private char text_void[256] zmienna pomocnicza do odczytów argumentów z pliku konfiguracyjnego private int int_void; zmienna pomocnicza do odczytów argumentów z pliku konfiguracyjnego private char text_dane[1024] zmienna pomocnicza do odbioru danych 6
PROCEDURY: private int logfile() Zapisuje do pliku czas połączenia się danego użytkownika do sewera private int logfile_com(char *); Zapisuje do pliku czas połączenia się danego użytkownika do sewera public int init() I nicjacja portu serwera uruchomienie procedury słuchającej public int protokol_fun(char) z klasy podstawowej obsługa klienta public int accept_fun() z klasy podstawowej odpowiada za to czy dany klient może być podłączony do serwera public int zerwalo_fun() z klasy podstawowej odpowiada za to gdy dany klient utraci połączenie serwera public int accept_ip(char *) z klasy podstawowej odpowiada za to czy dany klient ma ip pozwalające na podłączenie go do serwera public char *replaceall(char *,char *,char *) funkcja zamieniająca dany znak w stringu na inny znak public int delaytimestring(int,char ) funkcja umożliwia sprawdzenie czy ciąg kolejnych znaków jest sygnałem do zakończenia transmisji prze port public int clearstuser(int ) czyści strukturę danych danego użytkownika public void ignorsygnal() pozwala na zignorowanie sygnałów związanych z nagłym przerwaniem potoku public int InitOpenCom(int nr); funkcja pozwalająca na otwarcie portu o numerze nr zdefiniowanego w pliku konfiguracyjnym public char *getfilearg(char *,int); pobiera dane z pliku konfiguracyjnego dla danego połączenia portu public char *getfileargall(char *,char *,int); pobiera dane z pliku konfiguracyjnego public int initconfigurefile(); sprawdza wstępnie czy plik konfiguracyjny jest dobrze zrobiony private int searchindex_dp(int gniazdo) zwraca index pod którym jest struktura danego użytkownika który aktualnie jest związany z deskryptorem 7
private int searchindex_com(int gniazdo) zwraca index pod którym jest struktura danego użytkownika który aktualnie jest związany z portem szeregowym private bool isopencom(int n) sprawdza czy dany port szeregowy jest w użyciu /////////////////////////////////////////////////////////////////////////// Podstawowe funkcje dla klasy class_server STRUKTURY: ZMIENNE: struct sockaddr_in adres_serwera przechowuje dane dotyczące serwera struct sockaddr_in adres_klienta przechowuje tymczasowe dane dotyczące klienta fd_set odczytdp,testdp przechowuje dane dotyczące połączonych klientów struct timeval timeout przechowuje czas struct in_addr ip; przechowuje tymczasowo IP private int port porzechowuje nr portu na którym jest uruchomiony serwer private int gniazdo_serweradp,gniazdo_klientadp zmienne pomocnicze ustalają który z deskryktorow jest otwarty private int serwer_dlugosc,klient_dlugosc zmienne przechowują długość struktury klienta i serwera public int rezultat; pomocnicza przechowuje tymczasowe dane public char dane[1024]; pomocnicza przechowuje tymczasowe dane public int dp; przechowuje aktywny deskrytor pliku FUNKJE: public void setport(int) ustawia port na który serwer ma słuchać public int init() inicjuje wszystkie zmienne 8
VIRTUAL: public int start() rozpoczyna nasłuch public void settimeoutselect(int,int) na ile czasu ma być nasłuch public void deluserserver(int) usuwa użytkownika public int addselectdp(int) dodaje jego deskryptor do bazy public int delselectdp(int) usuwa jego deskryptor z bazy protected void put(int,char *) wysyła dane do danego deskrytora int virtual readline(int,char *,int,int) czyta cała linie nie używana int virtual readn(int,char *,int) czyta nadchodzący znak int virtual protokol_fun(char) co ma robić z danymi odsyła je z powrotem int virtual accept_fun() akceptuje połączenie z danego hosta int virtual zerwalo_fun() gdy zerwie się połączenie int virtual buffor_fun() nie wykorzystana int virtual accept_ip(char *) co ma zrobić z danym IP 9
KLIENT Do połączenie się z serwerem służy nam klient. Klientem może być zwykły telnet lub jakiś inny program. Jednak aby korzystać z tego serwera i się nie napocić! stworzona została klasa klienta pozwalająca w prosty sposób łączyć się serwerem. Przykłady zastosowania: #include "class_klient.h" int main() { //deklaracja klacy klienta class_klient ck; //gdzie i na jaki port chcemy się połączyć //dp<0 błąd dp>0 deskryptor pliku na który można wysyłać i z którego //odbierać dane int dp=ck.init(7000,"localhost"); //otwarcie portu szeregowego nazwanego os9 od tej pory wszystko co wysyłamy //jest wysyłane na port szeregowy, a dane odebrane pochodzą z portu //szeregowego //p<0 bład p==0 zajęty już p>0 OK int p=ck.connect_com("os9"); //wysyłanie do klienta ciągu znaków //lg<0 błąd lg>0 ciąg wysłany int lg=ck.send_com("\r"); //pobieranie od klienta ciągu znaków czekanie na nie przez 10s; //i zapisanie w zmiennej buff char buff[200]; memset(buff,0,sizeof(buff)); // cz=0 czas upłyną cz<0 niepowodzenie cz - zwraca ilość wysłanych znaków int cz=ck.recv_buff(buff,10); //co odebraliśmy wyświetlamy printf("odebrane znaki = %s;\n",buff); //zamkniecie portu szeregowego po czasie ustalonym w conf.txt wysyła znaki //które umożliwiają rozłączenie się z portem szeregowym //dc<0 błąd dc>0 ilość znaków wysłanych int dc=ck.disconnect_com(); //zamkniecie połączenie z serwerem serwera ck.close(); //koniec programu return 1; }; 10
Opis klas klienta Klient opiera się na klasie class_klient STRUKTURY: struct termios pocz_ust,nowe_ust; przechowująca dane o terminalu nie używana ZMIENNE: private int sockfd deskrytor aktywny pliku private char Buff[MAX_BUF] dane do obcierania są tu przechowywane private FILE *input Wejście z terminala nie wyżuwane private char ccs[50] przechowuje string potrzebny do zamknięcia portu szeregowego zdefiniowanego w pliku konfiguracyjnym sewera private int cct przechowuje odstęp czasu potrzebny do wysłania stringa potrzebnego do zamknięcia portu szeregowego zdefiniowanego w pliku konfiguracyjnym sewera FUNKCJE: public int init(int port,char *serwer) inicjalizacja klienta port nr portu server nazwa serwera public int close() zamkniecie połączenia public int connect_com() otwarcie portu szeregowego defautl public int connect_com(char *) otwarcie portu szeregowego określonego nazwa public int disconnect_com() zamkniecie portu szeregowego public int recv_buff(char *,int time) odczytuje wszystko co nadeszło w zadanym czasie jeśli time=0 nie czeka na więcej zwraca ilość danych public int recv_line(char *,int time) odczytuje wszystko aż do końca linii \n w zadanym czasie jeśli time=0 nie czeka na więcej zwraca ilość danych public int send_com(char *) wysyła dane do serwera public char *replaceall(char *in,char *strold,char *strnew) funkcja zamieniająca dany ciąg znaków strold w stringu in na inny ciąg znaków strnew 11
int init_term() nie używana do terminala int tunnel_termtocom(); nie używana do terminala private int readline(int,char *,int,int) potrzebna do funkcji int recv_line(char *,int time) private int send_com(int,char *) wysyła dane na zdefiniowane wejście int KOMUNIKACJA ZA POMOCA TELNET Telnet program uruchamiany z terminala dlatego wyjaśnię jak działa skrócie takie coś! Za każdym razem, gdy program i użytkownik oddziałują na siebie wzajemnie za pomocą terminala, dzieje się znacznie więcej, niż może wyglądać na pierwszy rzut oka. Na przykład jeśli program zapisuje ciąg znaków do urządzenia terminala, ten ciąg znaków jest najpierw przetwarzany przez część jądra, którą nazywamy sterownikiem terminala (ang. Terminal driver). Zależnie od wartości pewnych znaczników stanu w systemie, napis może być po prostu przekazany dalej bez zmian wysuniecie wiersza (line-feed) lub znak nowego wiersza (newline) na sekwencję składającą się z dwóch znaków powrotu karetki \r i znak nowego wiersza \n (carriage-return,newline). W normalnych warunkach sterownik terminala pozwala użytkownikowi edytować pomyłki w linii wejściowej za pomocą znaków kasuj (erase) i usuń (kill). Dopiero gdy użytkownik jest zadowolony z zawartości linii i naciśnie klawisz [return], sterownik terminala przekazuje ją do programu. Aby zmienić ustawienie terminala: $ stty co_zmienic na_co_zmienic przykłady $ stty -a pokazuje aktualne ustawienia $ stty canon min 1 time 0 Ustawienie terminala czytał znak po znaku a nie jak naciśniemy [enter] 12
Co możemy zmienić? Możemy zmienić bardzo dużo parametrów niżej przedstawiam tylko wybrane: kill - powoduje usunięcie wszystkich znaków aż do początku linii intr znak przerwania zostanie wysłany sygnał SIGINT domyślne ustawienie [Ctrl+C] quit - znak przerwania zostanie wysłany sygnał SIGQUIT eof - do oznaczenia końca strumienia wejściowego z terminala, domyślny [Ctrl+D] nl znal końca linii domyślnie ustawiony na \r\n eol dodatkowy ogranicznik linii, działający jak nl. Domyślna wartość ASCII NULL stop - domyślna wartość [Ctrl+Q] lub [Ctrl+S] susp - znak przerwania zostanie wysłany sygnał SIGTSTP domyślne ustawienie [Ctrl+Z] 13
Podsumowanie Działający program oddaje nieodpłatne wam użytkownikom, na cele dydaktyczne i aby wykorzystywać w celach dydaktycznych - a jeśli coś nie działa! to nie miejcie do mnie pretensji - u mnie działało. Program spisuje się całkiem nieźle jednak chciałbym ostrzec przed: UWAGA!!! Jeśli z program używa więcej niż jeden użytkownik i jeden z użytkowników przesyła bardzo dużą ilość danych. Zaleca się w takim przypadku uruchomienie jeszcze jednego programu na innym porcie obsługującego tylko danego użytkownika. UWAGA!!! RADA!!! Uważać na konfigurowanie pliku konfiguracyjnego Trzymać się wskazówek a jeśli są jakieś pytania to przesłać na adres gsiwek@rab.ict.pwr.wroc.pl Chciałbym jeszcze podziękować za pomóc techniczną panu dr. Markowi Wnukowi. Miłego użytkowania 14