|
I socket sono uno strumento che permette di inviare e ricevere dati tra due applicazioni che corrono su macchine collegate da una rete, la quale,
nel caso più frequente, coincide con Internet. Le classi che espongono i metodi necessari sono contenute nel namespace System.Net.Sockets,
di cui la classe Socket costituisce il membro più eminente. In questo capitolo e nel successivo, tuttavia, non useremo direttamente
tale classe, poiché è poco pratica da gestire e fa massiccio uso di tecniche di programmazione un po' complesse, quali il
multithreading, che non ho ancora spiegato. Introdurrò, invece, al suo posto, due classi più semplici che fanno da wrapper
ad alcune funzioni basilari del socket: TcpListener e TcpClient.
Server
Il server, nel nostro caso, è il computer sul quale risiede l'applicazione principale deputata alla gestione delle connessioni e dei servizi
dei client esterni. Più in generale è una componente informatica che fornisce servizi ad altre componenti attraverso una rete.
Per implementare un'applicazione Server da codice dobbiamo far sì che essa possa accettare connessioni da parte di altri. Per far questo è
necessario usare la classe TcpListener, che si mette in ascolto su di una porta, e riferisce quando ci sono richieste di connessioni in attesa
su di essa. I suoi membri di spicco sono:
- AcceptTcpClient() : accetta una connessione in attesa e restituisce un oggetto TcpClient collegato al client che ha inviato la richiesta.
Usando tale oggetto sarà possibile inviare o ricevere dati, poiché il Listener, di per sé, non fa altro che attendere
e accettare connessioni, ma l'azione vera viene intrapresa da oggetti TcpClient;
- Pending() : restituisce True se si cono connessioni in attesa;
- Server : restituisce l'oggetto socket che TcpListener sfrutta. Come avevo detto nel paragrafo introduttivo, queste due classi fanno
uso internamente di Socket, ma espongono metodi di più semplice gestione;
- Start() : inizia l'operazione di listening su una porta data;
- Stop() : interrompe il listening;
Il costruttore di TcpListener che useremo richiede come unico parametro la porta su cui mettersi in ascolto.
Client
La classe fondamentalmente usata per un client è TcpClient. I suoi membri più significativi sono:
- Available: restituisce il numero di bytes ricevuti e pronti per la lettura
- Close(): chiude la connessione
- Connect(IP, P): tenta una connessione verso il server identificato da IP sulla porta P. IP può essere sia un indirizzo IP che DNS
- Connected: restituisce True se è connesso, altrimento False
- GetStream(): funzione importantissima che restituisce un oggetto di tipo Sockets.NetworkStream su cui e da cui si scrivono e leggono
tutti i dati scambiati tra client e server
- ReceiveBufferSize: imposta la grandezza del buffer di bytes ricevuti
- SendBufferSize: imposta la grandezza del buffer di bytes inviati
Il client tenta la connessione al server e, se accettato, può dialogare con esso scambiando messaggi. I dati vengono inviati e
ricevuti attraverso uno stream di rete bidirezionale, che è possibile ottenere richiamando GetStream(). Quando il client scrive su
questo stream, il server riceve i dati e li può leggere, e viceversa.
Un semplice scambio di messaggi
Per iniziare scriveremo un semplice programma per scambiare messaggi (chiamarlo "chat" sarebbe a dir poco inopportuno). Per semplicità
d'uso, la stessa applicazione potrà fare sia da server che da client, così un utente potrà sia collegarsi ad un
altro che attendere connessioni (ma non fare le due cose contemporaneamente). L'interfaccia che ho preparato è questa:

Ci sono anche due timer, tmrConnections e tmrData. Ecco il codice:
Imports System.Net.Sockets
Imports System.Text.UTF8Encoding
Public Class Form1
Private Listener As TcpListener
Private Client As TcpClient
Private NetStream As NetworkStream
'Questa procedura serve per attivare o disattivare i
'controlli a seconda che si sia connessi oppure no. Serve
'per impedire che si tenti di inviare un messaggio quando
'non si è connessi, ad esempio
Private Sub EnableControls(ByVal Connected As Boolean)
btnConnect.Enabled = Not Connected
btnListen.Enabled = Not Connected
txtIP.Enabled = Not Connected
btnSend.Enabled = Connected
txtMessage.Enabled = Connected
btnDisconnect.Enabled = Connected
If Connected Then
tmrData.Start()
Else
tmrData.Stop()
End If
End Sub
Private Sub btnListen_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnListen.Click
'Inizializza il listener e inizia l'ascolto sulla porta
'5000. Inoltre, attiva il timer per controllare se ci
'sono connessioni in arrivo. Il timer scatta ogni 100ms
Listener = New TcpListener(5000)
Listener.Start()
tmrConnections.Start()
btnListen.Enabled = False
btnConnect.Enabled = False
txtLog.AppendText("Server - in ascolto..." & Environment.NewLine)
End Sub
Private Sub tmrConnections_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrConnections.Tick
'Se ci sono connessioni...
If Listener.Pending() Then
'Ferma un attimo il timer
tmrConnections.Stop()
'Chiede all'utente se confermare la connessione
If MessageBox.Show("Rilevato un tentativo di connessione. Accettare?", Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) = Windows.Forms.DialogResult.Yes Then
'Ottiene l'oggetto TcpClient collegato al client
Client = Listener.AcceptTcpClient()
'Ferma il listener
Listener.Stop()
'Ottiene il network stream
NetStream = Client.GetStream()
'E attiva/disattiva i controlli per quando si è connessi
EnableControls(True)
Else
Listener.Stop()
Listener.Start()
tmrConnections.Start()
End If
End If
End Sub
Private Sub btnConnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConnect.Click
Dim IP As Net.IPAddress
'Prima esegue un controllo sull'indirizzo IP per
'controllare che sia valido
If Not Net.IPAddress.TryParse(txtIP.Text, IP) Then
MessageBox.Show("IP non valido!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Exit Sub
End If
'Quindi inizializza un client e tenta la connessione
'al dato IP sulla porta 5000
Client = New TcpClient()
txtLog.AppendText("Client - tentativo di connessione..." & vbCrLf)
Try
Application.DoEvents()
Client.Connect(IP, 5000)
Catch Ex As Exception
End Try
'Se la connessione ha avuto successo, ottiene il network
'stream e agisce sui controlli come nel codice precedente
If Client.Connected Then
txtLog.AppendText("Tentativo di connessione riuscito!" & vbCrLf)
NetStream = Client.GetStream()
EnableControls(True)
Else
txtLog.AppendText("Tentativo di connessione fallito..." & vbCrLf)
End If
End Sub
Private Sub tmrData_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrData.Tick
'Se ci sono dati disponibili
If Client.Available > 0 Then
'Li legge dallo stream
Dim Buffer(Client.Available - 1) As Byte
NetStream.Read(Buffer, 0, Buffer.Length)
'Li trasforma in una stringa
Dim Msg As String = UTF8.GetString(Buffer)
'Se il messaggio inizia con questa stringa
'particolare signifia che l'altro utente ha chiuso
'la connessione, quindi disconnette anche questo
If Msg.StartsWith("\close\") Then
btnDisconnect_Click(Me, EventArgs.Empty)
Exit Sub
End If
'Altrimenti lo accoda alla textbox grande
txtLog.AppendText("Ricevuto: ")
txtLog.AppendText(Msg)
txtLog.AppendText(Environment.NewLine)
End If
End Sub
Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSend.Click
If Not String.IsNullOrEmpty(txtMessage.Text) Then
Dim Buffer() As Byte = UTF8.GetBytes(txtMessage.Text)
txtLog.AppendText("Inviato: " & txtMessage.Text & Environment.NewLine)
NetStream.Write(Buffer, 0, Buffer.Length)
txtMessage.Text = ""
End If
End Sub
Private Sub btnDisconnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDisconnect.Click
tmrData.Stop()
Dim Buffer() As Byte = UTF8.GetBytes("\close\")
NetStream.Write(Buffer, 0, Buffer.Length)
Client.Client.Close()
Client.Close()
Client = Nothing
If Listener IsNot Nothing Then
Listener.Server.Close()
Listener = Nothing
End If
EnableControls(False)
txtLog.AppendText("Disconnesso" & Environment.NewLine)
End Sub
End Class
Come avete visto dal codice non c'è nulla di particolarmente complicato da capire. Tuttavia, questo programma è molto semplice
e permette di gestire solo una connessione (in arrivo o in uscita). Il Listener, anche se riavviabile, continuerà a dare Pending
= True almeno fino a che tutti i client relativi alla connessione siano stati correttamente chiusi, e questo non si verifica se non alla
fine del programma, ossia quando uno dei due si disconnette. Per farla breve, è impossibile creare un'applicazione di scambio
messaggi multiutente con questi oggetti e queste modalità. |
|