TCP/IP drugi dio (izrada jednostavnog Chat-a) Client Otvorite novu aplikaciju (File -> New Application). Pobacajte na formu slijedeće komponente: - TClientSocket (komada 1) - TMemo (komada 1) - TEdit-a (komada 2) - TBitBtn (komada 1) - TButton-a (komada 2) Najbitnija komponenta unutar vaše nove aplikacije je TClientSocket - provjerite vrijednosti njegovih property-a: - Active - trebao bi biti False (kad se postavi u True, client će se pokušati spojiti na server, a kad se postavi u False pokušat će se odspojiti od servera). - Address - treba biti prazan. To je zapravo IP adresa servera i nju ćemo namjestiti u runtime-u (za vrijeme izvođenja programa). - ClientType - treba biti postavljen na ctNonBlocking, tj. na neblokiranu vrstu veze sa serverom. - Host - ako ne znate IP adresu servera, ali mu znate ime, onda umjesto u Address upišite ime servera u ovaj property. - Name - ovo znate što je - postavite ga na ClientSocket, tj. maknite onu nervirajuću brojku 1 na kraju. - Port - TCP adresa servera. Postavite ju na 55 (možete staviti bilo koji broj koji zadovoljava slijedeća dva uvjeta: 1. Mora biti jednak na clientu i na serveru 2. Ne smije biti "zauzet" (ne smije ga koristiti neki poznati protokol kao npr. http, ftp, itd.)). - Service - ako radite aplikaciju koja koristi neki poznati protokol (npr. http), onda ako u ovaj property upišete njegovo ime (http) isto vam je kao da ste u property Port upisali službeni port http servisa tj. 80. Komponente rasporedite po formi kako vam najbolje odgovara, no prije toga imenujte ih slijedećim imenima: TMemo - Memo1 - njegova uloga je prikazivanje Chat poruka koje će se razmjenjivati. Prvi TEdit - IPEdit - on će nam služiti za upis IP adrese servera za vrijeme izvođenja programa. Drugi TEdit - MessageEdit - u njega će korisnik upisivati poruke. TBitBtn - SendBtn - pogodili ste, služit će nam za slanje poruka serveru (možete mu i postaviti property ModalResult na mrOk da bi reagirao na pritisak tipke Enter). Prvi TButton - ConnectionButton - već pogađate za što ovaj služi. Drugi TButton - DisconnectionButton - također. Dobro, sad kad smo sve pripremili, idemo programirat: Prvo ide spajanje na server: procedure TForm1.ConnectButtonClick(Sender: TObject); begin ClientSocket.Address:=IPEdit.Text; // Postavlja IP adresu servera na ClientSocket ClientSocket.Active:=true; // Započinje spajanje na server end; Gornji kod se pokreće pritiskom na ConnectionButton. Budući da je ovo primjer (i to vrlo jednostavan), ja ovdje ne namjeravam obrađivati moguće pogreške (npr. korisnik zaboravi upisati IP adresu servera). Zatim, odspajanje sa servera: procedure TForm1.DisconnectButtonClick(Sender: TObject); begin ClientSocket.Active:=false; // Započinje odspajanje od servera end; Pritisak na gumb Send (SendBtn): procedure TForm1.SendBtnClick(Sender: TObject); begin ClientSocket.Socket.SendText(MessageEdit.Text); // slanje poruke MessageEdit.Text:=''; // pražnjenje MessageEdit-a end; Analizirajmo pobliže prvi redak koda u gornjoj proceduri: ClientSocket.Socket.SendText(MessageEdit.Text); ClinetSocket - TClientSocket - znate što je to ako ste pročitali prethodni članak, a ako niste, onda ga pročitajte ovdje. Socket - kao što vidite, pozivam property Socket koji je tipa TClientWinSocket i upravo je on taj koji prima i šalje poruke. SendText(MessageEdit.Text) - poziv procedure SendText i prosljeđivanje teksta poruke. Ova procedura će poslati određeni string serveru. Vrlo jednostavno. Ostalo nam je još samo primanje poruka. Kad server pošalje poruku client-u okida se OnRead event client-a: procedure TForm1.ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket); begin Memo1.Lines.Add(Socket.ReceiveText); // prima poruku i upisuje u Memo1 end; Prvo da vam objasnim zašto se event OnRead zove baš OnRead - to je vrlo jednostavno - zapamtite ga po tome što se tada od client-a traži da nešto pročita sa servera. Ako ste to shvatili, onda bi mogli i odgovoriti na pitanje kada se okida OnWrite event client-a ? Parametar Socket, koji sadrži gornja procedura, i pomoću kojeg sam primio tekst poruke pozivanjem funkcije ReciveText, je ustvari property Socket ClientSocket-a. Kao što sam već rekao, pomoću njega primate i šaljete poruke. Tako ste umjesto Socket.ReciveText mogli slobodno staviti ClientSocket.Socket.ReciveText i sve bi normalno radilo. I to bi bilo to što se tiče Clienta. Kompilirajte projekt i počnimo s izradom Servera. Server Otvorite novu aplikaciju. Na formu pobacajte slijedeće: - TServerSocket (komada 1) - TMemo (komada 2) - TEdit-a (komada 1) - TBitBtn (komada 1) - TButton-a (komada 2) Kao i kod Clienta, promijenite slijedeće property-e navedenim komponentama: TServerSocket Active = False - stavlja server u stanje aktivnosti, odnosno neaktivnosti. Name = ServerSocket. Port = 55 - TCP adresa na kojoj će server očekivati poruke. ServerType = stNonBlocking. Prvi TMemo - kao i kod clienta, on će nam služiti za prikazivanje poruka. Name = Memo1. Drugi TMemo - služit će za praćenje statusa pojedinih client-a. Name = LogMemo. TEdit - mjesto za upisivanje poruka. Name = MessageEdit. TBitBtn - gumb za slanje poruke. Name = SendBtn. ModalResult = mrOk. Prvi TButton - startanje servera. Name = StartButton. Drugi TButton - prekidanje rada servera. Name = EndButton. Razmjestite komponente po formi i krećemo na posao. Što nam prvo treba ? Startanje servera, naravno: procedure TForm1.StartButtonClick(Sender: TObject); begin ServerSocket.Active:=true; Memo1.Lines.Add('Server started!'); end; Zatim gašenje servera, tj. stavljanje u neaktivno stanje: procedure TForm1.EndButtonClick(Sender: TObject); begin ServerSocket.Active:=false; Memo1.Lines.Add('Server ended!'); end; Konektiranjem client-a okida se OnClientConnect event servera. U njega stavite slijedeći kod: procedure TForm1.ServerSocketClientConnect(Sender: TObject; Socket: TCustomWinSocket); var i:integer; begin for i:=0 to ServerSocket.Socket.ActiveConnections-1 do ServerSocket.Socket.Connections[i].SendText ('New connection made by: '+Socket.RemoteHost+' --- '+Socket.RemoteAddress); LogMemo.Lines.Add('New connection made by: '+Socket.RemoteHost+' --- '+Socket.RemoteAddress); end; Prva linija, koja gore zauzima tri retka, šalje poruku svim client-ima spojenima na server te ih tako obavještava da se novi client sa svojim imenom (ime računala) i svojom IP adresom spojio na server. Idemo malo detaljnije analizirati tu liniju: Kao što ste primijetili, radi se o petlji koja ide od 0 do broja spojenih client-a -1. U toj petlji se preko tri property-a poziva funkcija SendText koja šalje dani tekst pojedinom client-u. Koja su to tri property-a i koje su njihove uloge: ServerSocket - TServerSocket Socket - TServerWinScoket - "pravi" server. TServerSocket je samo interface za njegovo upravljanje. Connections[i] - TServerClientWinSocket - TServerWinSocket stvara novu instancu TServerClientWinSocket-a za svakog pojedinog client-a, te ih stavlja u niz koji počinje sa 0. Rekao sam da je TServerWinSocket "pravi" server i predstavlja krajnju točku (endpoint) unutar komunikacije, međutim to baš i nije sasvim točno jer TServerWinSocket zapravo naređuje pojedinom TServerClientWinSocket-u da primi ili pošalje poruku, dok oni to čine samostalno. U gornjoj proceduri varijabla Socket zapravo pretstavlja pojedini TServerClientWinSocket koji je zadužen za client-a koji se upravo spojio na server. Diskonektiranjem clienta-a okida se OnClientDisconnect event servera, pa i za njega trebamo štogod napisati, zar ne: procedure TForm1.ServerSocketClientDisconnect(Sender: TObject; Socket: TCustomWinSocket); var i:integer; begin for i:=0 to ServerSocket.Socket.ActiveConnections-1 do ServerSocket.Socket.Connections[i].SendText ('Disconnection made by: '+Socket.RemoteHost+' --- '+Socket.RemoteAddress); LogMemo.Lines.Add('Disconnection made by: '+Socket.RemoteHost+' --- '+Socket.RemoteAddress); end; OnClientDisconnect se okida trenutak prije odspajanja client-a. Gornja procedura u prvoj liniji šalje poruku svim client-ima da se pojedini client odspojio, a u drugoj liniji registrira odspajanje u LogMemo. Najvažniji zadatak chat servera je prosljeđivanje poruka jednog korisnika svima ostalima. Kako ? Dodajte slijedeći kod u OnClientRead event TServerSocket-a: procedure TForm1.ServerSocketClientRead(Sender: TObject; Socket: TCustomWinSocket); var i:integer; PrivremeniString:string; begin PrivremeniString:=Socket.ReceiveText; for i:=0 to ServerSocket.Socket.ActiveConnections-1 do ServerSocket.Socket.Connections[i].SendText(Socket.RemoteHost+': '+ PrivremeniString); Memo1.Lines.Add(Socket.RemoteHost+': '+PrivremeniString); end; Prva linija gornje procedure prima tekst od client-a koji je poslao poruku uz pomoć odgovarajućeg TServerClientWinSocket-a, te ga pohranjuje u varijablu PrivremeniString. Druga linija prosljeđuje i vraća zaprimljenu poruku svim client-ima. Treća linija poruku upisuje u Memo1, zajedno sa imenom pošiljatelja. Još moramo obraditi pritisak na Send gumb (SendBtn): procedure TForm1.SendBtnClick(Sender: TObject); var i:integer; begin for i:=0 to ServerSocket.Socket.ActiveConnections-1 do ServerSocket.Socket.Connections[i].SendText(ServerSocket.Socket.LocalHost+': '+ MessageEdit.Text); Memo1.Lines.Add(ServerSocket.Socket.LocalHost+': '+MessageEdit.Text); MessageEdit.Text:=''; end; Prva linija šalje poruku svim client-ima, druga ju upisuje u Memo1, a treća ju briše iz MessageEdit-a. To bi bilo najosnovnije, međutim što će se dogoditi ako server prekine s radom ? Client-i to neće znati, pa ih je korisno obavijestiti da je server offline. Prema tome, dodajte slijedeći kod u OnClose event Forme: procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); var i: integer; begin if ServerSocket.Socket.Connected then for i:=0 to ServerSocket.Socket.ActiveConnections-1 do ServerSocket.Socket.Connections[i].SendText('Server is ended!'); ServerSocket.Active:=false; end; Prva linija šalje poruku svim client-ima da se server isključuje, pod uvjetom da je netko uopće spojen na server. Zatim ga gasi ! THE END!!!! Ako nemate pristup mreži, možete isprobati da li vam aplikacije rade tako da kad se spajate sa client-om na server koji se nalazi na istom računalu, pod IP adresu servera navedete 127.0.0.1 koja pretstavlja IP adresu trenutnog računala. U ovom članku nisam navodio što koja funkcija radi - to vam sve piše u Delphi help-u, ali ako zapnete, slobodno mi pišite na mail. Također, ako downloadate izvorni kod, naići ćete na koju dodatnu funkciju ili gumb koji su popraćeni komentarima u kodu, tako da ne bi smjelo biti problema. Pazite na firewall, ako ga imate prilikom probe. Bilo bi ga poželjno isključiti.