Sei sulla pagina 1di 18

The New Brew-CQ

Synchronous Sockets and Threading

Server Topology:

The Brew-CQ server is an application written in the new .NET compilers from Microsoft. The
language of choice is Visual Basic. The purpose of this application is to demonstrate how a chat
server (such as MSN Messenger and Brew-CQ) works.There are a couple of things that the server
must do. Firstly, it should monitor all client connections, accepting and closing, maintaining a list of
all logged on client. It must maintain an up to date list of logged on client on each of the logged on
client applications. Hence, when a client first logs on to the server his name must be added to the
list, then the up to date list must be distributed to all clients. Conversely, when a client logs off the
server the list must be updated and then distributed to all clients still logged on to the server. The
second thing that the server provides is a message relay from client to client. This is different from
some chat programs in that they have server/client applications, where we are using a straight
server. So, we accept one clients message and send it to the appropriate other client. In the next
sections I discuss some of the specific programming used to accomplish this application.

Listen to the Music:

The main differnce from a server and a client is that the server listens for client trying to
connect to it and then either accept or reject the connection request. The server uses a listening
synchronous socket which prompts us to write a thread to constantly keep the server listening while
allowing the applilcation to providing other services. The listen instruction once executed sits and
waits for an attempt to connect. Once an connection is initiated by a client the thread continues
execution. Within the listening thread a lot of things have to take place. Firstly, a new socket
instance must be created and accept the connection. Then the list of clients has to be updated and
then distributed to the all of the clients. The connecting client first must send the username for the
client the list can then be updated. A small protocol had to implemented to dicern between a
message or the list of names being sent to a client. Simply we tack on "names@" to the beginning
of the namelist and "username@" at the beginnig of the message. Once the namelist has been
created we can now send the list. The last thing to do is to create a thread for the new client to
receive messages from that client. A thread is executed for each client connected. it was found we
can run multiple instances of the thread procedure and they run independantly of the others. We
suspend the execution of the listen thread for 200 milliseconds to allow the creation of the new
thread and get its values before the listen thread changes these values. Finally, we loop back and
start to listen for the next client that wishes to connect.

Communication Breakdown:

The server constantly polls all connections to see if they are still alive. If the connection
exists and is currently OK the connection thread does nothing. However, if the connection does not
respond the server kills the thread associated with that client. It also updates the connection list
and ditributes the new list to all still connected client. Sounds great, But! This method wish in
previous versions of Visual Studio could be accomplished. In .Net however, the Poll method return
the status of the socket and no combination of its returned results can tell you if the remote socket
diconnects. The Poll method would continually drop the connection because the thread that is
polling the connection would pick up the selectread as true and the bytesavailable as zero as soon
as the receiving thread had read all of the message, this is the closest combination to tell if the
connection had been dropped. The other method to monitor the connection is with the Connected
property, which is true when connected and false when disconnected. Wow, problem solved! Well,
not quite. The Connected property is updated after a socket IO event, which means you always lose
your last message before the property is updated. Too bad the Poll method is not an IO event.
Solution, error trapping and sending disconnect messages. Each client thread and the listening
thread have on error commands to trap errors that occur.

Are You Receiving Me:

Each client when connected has a thread created for it that is constantly ready to receive
messages. The thread is created within the listening thread and is aborted in the connection thread.
This thread monitors a specific client connection and waits until a message has been sent. The
server receives the message and reads the first bit of the message to find the destination client.
Once it has determined what the destination it looks up the socket instance of that client and sends
the message off to the appropriate client. It then continues to listen for another message.

Form Layout:

The layout of the form is simple. There are two list boxes, one for the names of the
connected clients, the other the corresponding list of ip addresses of each client. One of these list
boxes are redundant, but it was a debugging tool more than anything. The only other component on
the page is the exit button which aborts all running threads and terminates the application. The list
boxes are called conlist and namelist, the exit button is called exitbutton.

'
'
' This program written for ENG3553 Networking
'
' by Bruce Misner
' 2004
'
' Demonstrates synchronous sockets and threading
' This is the Server Application
'
'
Imports System
Imports System.Object
Imports System.Net
Imports System.Net.Sockets
Imports System.Net.SocketAddress
Imports System.Threading
Imports System.IO
Imports System.Text

Public Class Form1


Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

'My Global Variables


'
' by Brew-C
'

Dim listenthread As Thread


Dim conthread As Thread
Dim clithread(50) As Thread
Dim ipaddress As ipaddress
Dim newcon As Integer
Dim tester As Boolean

Dim ssock(50) As Socket

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles MyBase.Load

conlist.Items.Clear()
listenthread = New Thread(New ThreadStart(AddressOf
Me.listenproc))
listenthread.IsBackground = True
listenthread.Start()
tester = False

End Sub

Public Sub cliproc()


Dim clientno As Integer

clientno = newcon

'
' Declare variables
'

Dim i As Integer
Dim j As Integer
Dim data As String = Nothing
Dim newdata As String = Nothing
Dim bytesrec As Integer
Dim destination As String
Dim fromwho As String
Dim towho As String
Dim bytes = New Byte(1024) {}
Dim lendata As Integer
Dim curlen As Integer

'
' call to error handler
'
On Error GoTo clihandler

'
' Set up the from who header portion
'
fromwho = namelist.Items.Item(clientno) + "@"

'
' wait for data arriving from this client
'
recvnext:
Do While ssock(clientno).Available = 0

Loop

'
' read the data
'

bytesrec = ssock(clientno).Receive(bytes)

'
' convert to string
'
data = Encoding.ASCII.GetString(bytes, 0, bytesrec)

'
' pick out the length of data sent from client
'
lendata = Val(Microsoft.VisualBasic.Left(data, InStr(data, "@")
- 1))

'
' set byte counter
'
curlen = bytesrec

'
' keep receiving the whole message
'
Do While lendata > curlen
bytesrec = ssock(clientno).Receive(bytes)
data = data + Encoding.ASCII.GetString(bytes, 0, bytesrec)
curlen = curlen + bytesrec

Loop

'
' convert message to string
'
data = Microsoft.VisualBasic.Right(data, Len(data) - InStr(data,
"@"))

'
' find who the message is destined to
'
If InStr(data, "@") > 0 Then towho = Microsoft.VisualBasic.Left
(data, InStr(data, "@"))

'
' check if the message is to be sent
'
If InStr(data, "@") > 0 And towho <> "bye@" Then
destination = Microsoft.VisualBasic.Left(data, InStr(data,
"@") - 1)
newdata = Nothing
'
' assemble message with new header
'
newdata = fromwho + Microsoft.VisualBasic.Right(data, Len
(data) - Len(destination) - 1)

'
' convert to byte array
'
bytes = Encoding.ASCII.GetBytes(newdata)

'
' find destination
'
For i = 0 To 49
If (namelist.Items.Item(i) = destination) Then
ssock(i).Send(bytes)
newdata = ""
bytesrec = 0
End If
Next i

'
' see if client is diconnecting
'
ElseIf InStr(data, "@") > 0 And towho = "bye@" Then

'
' handle disconnection
'
conlist.Items.Item(clientno) = "No Connection"
namelist.Items.Item(clientno) = ""
ssock(clientno).Shutdown(SocketShutdown.Both)
ssock(clientno).Close()

'
' update connected client list
'
data = "names@"

For j = 0 To 49
If namelist.Items.Item(j) <> "" Then
data += namelist.Items.Item(j) + "@"
End If
Next j

'
' convert to byte array
'
bytes = Encoding.ASCII.GetBytes(data)

'
' send to all connected clients
'
For j = 0 To 49
If namelist.Items.Item(j) <> "" Then
ssock(j).Send(bytes)
End If
Next j

'
' kill that clients thread
'
clithread(clientno).Abort()

End If

GoTo recvnext

clihandler:
MessageBox.Show(Err.Description + " " + Str$(i))

End Sub
'
'
' Thread code for the synchronous listener
'
'

Public Sub listenproc()

Const portNumber As Integer = 13


Dim address As String

'
' Initialize listbox entries
'

conlist.Items.Clear()
namelist.Items.Clear()

Dim x As Integer
For x = 0 To 49
conlist.Items.Add("No Connection")
namelist.Items.Add("")
Next x

'
' server name
'
address = "newdig1"

'
' Create listener instance
'

Dim tcplist As New Socket(AddressFamily.InterNetwork,


SocketType.Stream, ProtocolType.Tcp)
Dim endpoint As New IPEndPoint(Dns.Resolve(address).AddressList
(0), 13)

'
' This makes it syncronous
'

tcplist.Blocking = True
tcplist.Bind(endpoint)

'
' Waits for client to initiate connection
'

tcplist.Listen(10)

keeplistening:

'
' Find free listbox entry
'
newcon = 0

Do While (conlist.Items.Item(newcon) <> "No Connection")


newcon = newcon + 1
Loop

'
' Accept client connection
'

ssock(newcon) = tcplist.Accept()
Dim bytes = New Byte(1024) {}
Dim data As String = Nothing
Dim bytesrec As Integer

conlist.Items.Item(newcon) = ssock
(newcon).RemoteEndPoint.Serialize().Item(4).ToString() + "." _
+ ssock(newcon).RemoteEndPoint.Serialize().Item(5).ToString
() + "." _
+ ssock(newcon).RemoteEndPoint.Serialize().Item(6).ToString
() + "." _
+ ssock(newcon).RemoteEndPoint.Serialize().Item(7).ToString
()

bytesrec = ssock(newcon).Receive(bytes)
data = Encoding.ASCII.GetString(bytes, 0, bytesrec)

namelist.Items.Item(newcon) = data

Dim i As Integer

data = "names@"

'
' Create up to date client list
' to be sent to all connected clients
'

For i = 0 To 49

If namelist.Items.Item(i) <> "" Then


data += namelist.Items.Item(i) + "@"

End If
Next i

bytes = Encoding.ASCII.GetBytes(data)

'
' Send name list to all Clients
'

For i = 0 To 49
If namelist.Items.Item(i) <> "" Then
ssock(i).Send(bytes)
End If
Next i
'
'
' Create Thread for connecting client
'
clithread(newcon) = New Thread(New ThreadStart(AddressOf
Me.cliproc))
clithread(newcon).IsBackground = True
clithread(newcon).Start()

'
' Pause for Thread to be Created
'
listenthread.Sleep(200)

GoTo keeplistening

End Sub

Private Sub exitbutton_Click(ByVal sender As System.Object, ByVal e


As System.EventArgs) Handles exitbutton.Click

'
' Closes all running threads and connected sockets
'
' by Brew-C
'

listenthread.Abort()

Dim i As Integer

For i = 0 To 49
If conlist.Items.Item(i) <> "No Connection" Then clithread
(i).Abort()
If conlist.Items.Item(i) <> "No Connection" Then
ssock(i).Send(Encoding.ASCII.GetBytes("bye@"))
ssock(i).Shutdown(SocketShutdown.Both)
ssock(i).Close()
End If
Next

'
' End the Program
'
End

End Sub
Private Sub namelist_DoubleClick(ByVal sender As Object, ByVal e As
System.EventArgs) Handles namelist.DoubleClick

'
'
' This routine allows the server to forcefully disconnect
' any client currently logged in
'
'

Dim i As Integer

Dim bytes = New Byte(1024) {}


Dim data As String = Nothing

'
' find which name was double clicked
'

i = namelist.SelectedIndex
conlist.Items.Item(i) = "No Connection"

'
' get rid of the client
'

clithread(i).Abort()

namelist.Items.Item(i) = ""
ssock(i).Send(Encoding.ASCII.GetBytes("bye@"))
ssock(i).Shutdown(SocketShutdown.Both)
ssock(i).Close()

data = "names@"

'
' Create up to date client list
' to be sent to all connected clients
'

For i = 0 To 49

If namelist.Items.Item(i) <> "" Then


data += namelist.Items.Item(i) + "@"

End If
Next i

bytes = Encoding.ASCII.GetBytes(data)

'
' Send name list to all Clients
'

For i = 0 To 49
If namelist.Items.Item(i) <> "" Then
ssock(i).Send(bytes)
End If
Next i

End Sub

Private Sub Form1_Closing(ByVal sender As Object, ByVal e As


System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
'
'
' This routine covers other exits from the program besides
' a click of the exit button
'
'
' Abort all threads and close all sockets, sending bye first
'
listenthread.Abort()

Dim i As Integer

For i = 0 To 49
If conlist.Items.Item(i) <> "No Connection" Then clithread
(i).Abort()
If conlist.Items.Item(i) <> "No Connection" Then
ssock(i).Send(Encoding.ASCII.GetBytes("bye@"))
ssock(i).Shutdown(SocketShutdown.Both)
ssock(i).Close()
End If
Next i

End Sub
End Class

Client Application

The client application is your interface to chat. There are several things the client app. has
to do. Firstly, the client has to connect to the server and establish a connection. It should also be
able to disconnect from the server as well. The client must be able to list all connected clients on
the server so you can select who you wish to send a message to. The client must have a place to
type your message you wish to send and a way to invoke the message send off. The client must
also have a place to display received messages.

Form Layout

The client form has more to display so is a little more complicated. From top down we start
with the username textbox and the connect button. First when you start the client app. you must
provide a username for yourself and click the connect button to "loggin" to the server. Once the
connection has been established the server returns a list of all other clients that are logged on.
From the list of connected people you can select whom you wish to send a message to. You must
also have a message to send to that user, hence, a textbox that is for the message you wish to
send. Once the message is ready to send a click of the send button beneath the textbox with your
message will send the message to the other user via the server. The other textbox is for received
messages, as well as, a clear button. When the textbox is too full you can click the clear button
and will start the textbox anew.

The Code

'
' This is the Client Applilcation
'
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Imports System.Object

Public Class Form1


Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

'
' My Global Variables
'
'
Dim csock As Socket

Dim recthread As thread


Dim endpoint As IPEndPoint

Dim conthread As Thread

#End Region

'
' Form Load Event Code
'
'
'
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
endpoint = New IPEndPoint(Dns.Resolve("newdig1").AddressList(0),
13)
End Sub
'
'
' Receive thread code
'
'
Public Sub recproc()
'
' declare local variables
'
Dim bytes = New Byte(1024) {}
Dim data As String
Dim recbytes As Integer
Dim cr As String
Dim crbyte(2) As Byte

crbyte(0) = 13
crbyte(1) = 10
'
' error trap
'
On Error GoTo handler
'
' carriage return linefeed define
'
cr = Encoding.ASCII.GetString(crbyte)
'
'
' wait for data available
'
'
recvnext:

Do While csock.Available = 0
Loop
'
' receive data and convert to string
'
recbytes = csock.Receive(bytes)
data = Encoding.ASCII.GetString(bytes)
'
'
' are we receiving an updated namelist
'
'
If Microsoft.VisualBasic.Left(data, InStr(data, "@")) =
"names@" Then
Dim tpoint As Integer
tpoint = 7
theirnames.Items.Clear()
Dim npoint As Integer
npoint = 1
'
'
' pull out individual names and populate the listbox
'
'
Do While tpoint < recbytes
npoint = InStr(tpoint, data, "@")
If npoint = 0 Then npoint = recbytes
theirnames.Items.Add(Mid$(data, tpoint, npoint -
tpoint))
tpoint = npoint + 1
Loop

bytes = New Byte(1024) {}


GoTo recvnext
'
'
' is the server disconnecting
'
'
ElseIf Microsoft.VisualBasic.Left(data, InStr(data, "@")) =
"bye@" Then
'
'
' close socket and reset form to disconnected conditions
'
'
csock.Shutdown(SocketShutdown.Both)
csock.Close()
theirnames.Items.Clear()
cbut.Text = "Connect"
MessageBox.Show("Server has left")
recthread.Abort()
'
'
' receive message
'
'
Else
recbox.AppendText("Received this from: " +
Microsoft.VisualBasic.Left(data, InStr(data, "@") - 1) _
+ cr)
recbox.AppendText(Microsoft.VisualBasic.Right(data, Len
(data) - InStr(data, "@")))
recbox.AppendText(cr + cr)

End If
'
'
' redeclare bytes to expunge old message
'
'
bytes = New Byte(1024) {}
GoTo recvnext
'
'
' error handler
'
'
handler:
'
'
' show what you got then terminate receiving thread
'
'
MessageBox.Show(data)
recthread.Abort()
End Sub
'
'
' Connect Button Click event
'
'
Private Sub cbut_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles cbut.Click

Dim bytes = New Byte(1024) {}


Dim data As String
Dim recbytes As Integer
'
'
' check if you have entered a username
'
'
If yourname.Text = "" Then
MessageBox.Show("You must specify your username")
Exit Sub
End If
'
'
' check if connected
'
'
If (cbut.Text = "Connect") And (yourname.Text <> "") Then
'
'
' if not connected
'
'
On Error GoTo handler
'
' create socket instance and connect
'
'
csock = New Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp)
csock.Connect(endpoint)
'
'
' send your username to server
'
'
bytes = Encoding.ASCII.GetBytes(yourname.Text)
csock.Send(bytes)
bytes = Nothing
cbut.Text = "Disconnect"
'
'
' start receiving thread
'
'
recthread = New Thread(New ThreadStart(AddressOf recproc))
recthread.IsBackground = True
recthread.Start()

ElseIf cbut.Text = "Disconnect" Then


'
' if currently connected
'
csock.Send(Encoding.ASCII.GetBytes("6@bye@"))
csock.Shutdown(SocketShutdown.Both)
csock.Close()
theirnames.Items.Clear()
recthread.Abort()
cbut.Text = "Connect"
End If

GoTo skiphandler

handler:

MessageBox.Show("Can not connect")

skiphandler:

End Sub
'
'
' Run this code when the Send Button is Clicked
'
'
'
Private Sub sendbut_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles sendbut.Click
'
'
' declare local variables
'
'
Dim data As String
Dim lendata As String
Dim lendataint As Integer
'
'
' setup on error trap
'
'
On Error GoTo sendhandler
'
'
' check to see if a destination user has been selected
'
'
If theirnames.SelectedIndex = -1 Then
'
' if no selection
'
'
MessageBox.Show("No destination user specified")
Exit Sub
End If
'
'
' define local variables
'
'
Dim bytes = New Byte(1024) {}
'
'
' build string to send
'
' string starts with length of data then destination user then message
text
' all with @ signs in between
'
'
data = theirnames.Items.Item(theirnames.SelectedIndex) + "@" +
sendbox.Text
lendata = Str$(Len(data)) + "@"
lendataint = Len(lendata) + Len(data)
data = Str$(lendataint) + "@" + data
'
' convert to binary and send message
'
bytes = Encoding.ASCII.GetBytes(data)
csock.Send(bytes)
sendbox.Text = ""

GoTo skipsendhandler

sendhandler:
'
'
' If the client can not send message to server run this code
'
'
MessageBox.Show("Server Problems. Your message was lost")
csock.Close()
theirnames.Items.Clear()

recthread.Abort()
cbut.Text = "Connect"

skipsendhandler:

End Sub
'
'
' This code clears the received textbox
'
'
Private Sub clearbut_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles clearbut.Click

recbox.Text = ""

End Sub

End Class

Summary

I would like to clean up a few odds and ends. First when it comes to threads there is
another document you should see. However, what is not mentioned there is the fact you can create
a collection of threads by defining them as an array. You can run the same thread code multiple
times at once and they will run independantly of each other. The Poll method is an unreliable means
of checking for existing connections, too many other events satisfy conditions that signal a
diconnect event.

Despite the above mentioned problem with the Poll method this prgram is quite stable and
seems to run well. More error trapping could be worked on to make it a more than useable chat
program.

Potrebbero piacerti anche