Xem mẫu
- Multithreaded Winsock
Vb6 cho ta Winsock Control để giúp một program VB6 nói chuyện với một
program khác trên mạng TCP/IP.
Ta có thể dùng Winsock Control trong một program để làm Winsock Server hay
Winsock Client. Sự khác biệt nầy rất nhỏ, mặc dầu ta phải lưu ý để phân biệt sự
khác nhau của hai trường hợp. Giả sử ta dùng Winsock Control làm Server trong
một VB6 program để chạy trên một computer và dùng Winsock Control làm
Client trong một VB6 program để chạy trên một computer khác trên mạng
TCP/IP. Ðể cho hai programs nói chuyện (communicate) trước hết ta cần phải
connect (nối) chúng lại với nhau.
Ta cho Winsock Server Listen (lắng nghe) qua một LocalPort (một cổng có
mang một con số, thí dụ như 9123). Kế đó ta cho Winsock Client Connect (móc
nối) qua LocalPort đó ở địa chỉ TCP của Computer nơi ta chạy Winsock Server
program. Sở dỉ ta cần phải nói rõ LocalPort số mấy là vì Server Computer có thể
Listen qua nhiều LocalPorts cùng một lúc để nhiều Clients có thể Connect đến
cùng một Computer TCP address. (Nếu bạn còn mới đối với TCP/IP hãy đọc bài
Căn bản TCP/IP )
Class ServerWinsock và Class ClientWinsock
Trong .NET, Winsock được thay thế bằng TcpListener và TcpClient của
System.Net.Sockets. Để dùng chúng ta chỉ cần Project | Add Reference..
cái System.dll và thêm câu:
Imports System.Net.Sockets ' for TcpClient and TcpServer
ở đầu phần code.
Khi instantiate một TcpListener object, ta cho nó một PortNo để nó lắng nghe
qua cổng đó như sau:
Dim oListener As TcpListener ' Variable for TcpListener
' Instantiate a TcpListener on given PortNo
oListener = New TcpListener(PortNo)
oListener.Start() ' Start the TcpListener
Về phía Client, ta gọi method Connect của TcpClient với tên của
destination/server computer (hay TCP address của computer ấy) và cái cổng trên
destination/server computer. Ta code như sau:
Dim Client As TcpClient ' Variable for the Client TCP socket
- ' Instantiate TCPClient object
Client = New TcpClient()
' Attempt to connect to destination (server) computer on given port
number
Client.Connect(DestinationComputer, TCPIPPortNo)
Bên TcpListener sẽ dùng một Socket để Accept (nhận) cái Request (thỉnh cầu)
của TcpClient:
' Accept request from the TcpClient
Dim oSocket As Socket
oSocket = oListener.AcceptSocket
Khi TcpListener AcceptSocket rồi thì hai bên TcpClient và TcpListener có thể gởi
thông điệp qua lại cho đến khi một bên terminates (stop).
Dưới đây là hình minh họa sự móc nối và gởi thông điệp từ Client (máy SAIGON)
qua Server (máy SADEC). Từ Server ta cũng có thể gởi thông điệp qua Client
cùng một cách như vậy.
Một khi connection đã đứt đoạn, không dễ cho ta nối lại. Trên nguyên tắc, hai
bên phải đóng socket rồi tìm cách lắng nghe/móc nối trở lại.
.NET cho ta một giải pháp đơn giản và thanh tao, đó là dùng thread, một dạng
process nhẹ ký. Ở cùng một cổng, mỗi khi nhận được Request-to-connect từ một
TcpClient, ta instantiate một Socket chạy trong một thread riêng để phục vụ
TcpClient ấy. Khi TcpClient disconnects thì ta cũng đóng socket nầy.
Bên phía TcpClient, mỗi lần cần gởi một thông điệp ta instantiate một TcpClient
mới, và sau khi gởi xong ta disconnect nó ngay.
Cách dùng thread rất đơn giản. Muốn một Sub chạy riêng trong một thread ta
- chỉ cần instantiate một thread với AddressOf của Sub ấy, rồi khởi động thread
ấy như sau:
' create a thread to handle this Client Request
Dim oThread As Thread
oThread = New Thread(AddressOf ProcessRequest)
oThread.Start() ' Run Sub ProcessRequest
Để dùng Thread ta chỉ cần thêm câu:
Imports System.Threading ' for Thread
ở đầu phần code.
Trong dự án nầy, TcpListener được gói trong class ServerWinsock và
TcpClient được gói trong class ClientWinsock. Chính bên trong class
ServerWinsock ta dùng multithread để phục vụ nhiều TcpClient qua cùng một
cổng TCPPortNo duy nhất.
Class ClientWinsock chỉ gởi thông điệp và class ServerWinsock chỉ nhận thông
điệp. Khi ServerWinsock nhận một thông điệp nó sẽ Raise một Event để
program chủ của nó xử lý thông điệp. Thông điệp được gởi đi lại dưới dạng một
array of bytes. Do đó muốn gởi một Text String ta phải cho biết Encode của Text
string lúc bấy giờ là UTF8, Unicode hay ASCII, và đổi nó ra array of bytes như
sau:
Dim Buffer() As Byte ' used for outgoing message
' Convert UFT8 message to an array of bytes before sending
Buffer = System.Text.Encoding.UTF8.GetBytes(mMessage.ToCharArray)
' Send out the buffer
Client.GetStream().Write(Buffer, 0, Buffer.Length)
Về phía đầu ServerWinsock, khi nhận được array of bytes thì phải đổi ra Text
string trở lại như sau:
' Convert the array of bytes (i.e. the buffer) to UTF8 text string
RecvMessage = System.Text.Encoding.UTF8.GetString(Buffer)
' Raise an event to return the message to the program that owns this
ServerWinsock
RaiseEvent OnMessage(RecvMessage)
Trong thí dụ nầy ta dùng UTF8 để gởi Unicode. Nếu dữ kiện chỉ là ASCII thì có
thể dùng encoding ASCII cho hiệu lực hơn vì mỗi ASCII character chỉ cần một
byte:
Buffer = System.Text.Encoding.ASCII.GetBytes(mMessage.ToCharArray) '
Chuẩn bị Buffer để gởi đi
RecvMessage = System.Text.Encoding.ASCII.GetString(Buffer) ' Đởi lại
thành ASCII text string khi nhận
Thật ra để gởi Unicode ta cũng có thể dùng encoding Unicode, tức là UTF16
LittleEndian. (Nếu bạn còn mới đối với Unicode encoding hãy đọc bài Dùng
- Unicode chữ Việt trong .NET).
Dưới đây là mã nguồn của hai classes ClientWinsock và ServerWinsock:
Imports System.Threading ' for threads
Imports System.Net.Sockets ' for TcpClient and TcpServer
' This module contains two classes: ClientWinsock and ServerWinsock
Public Class ClientWinsock
' This object is created to connect to a TCPServer and to send a
single Unicode message
Private ClientThread As Thread ' used to run the main Sub
StartClient of Client
Private TCPIPPortNo As Integer ' TCPIP port number on destination
computer
Private DestinationComputer As String ' name or IP address of
destination computer
Private mMessage As String ' Unicode message to be sent
Public Sub New( ByVal Destination As String, ByVal Message As
String)
' Split the given Message into Destination computer and TCPIP port
number
Dim pos As Integer
' Locate the character ";" in the Message string
pos = Destination.IndexOf(";")
If pos > 0 Then
' the part before ";" is the name or IP address of destination
computer
DestinationComputer = Destination.Substring(0, pos)
TCPIPPortNo = CInt(Destination.Substring(pos + 1)) ' convert
string to integer
Else
' character ";" does not exist, that means only TCPIP Port
number is given for Localhost
DestinationComputer = "Localhost" ' Destination computer is
Localhost
TCPIPPortNo = CInt(Destination) ' convert string to integer
End If
mMessage = Message ' assign outgoing message to local string
variable
'Create a Thread object for Sub StartClient
ClientThread = New Thread(AddressOf StartClient)
'Starting the thread invokes the ThreadStart delegate
' i.e. run Sub StartClient in its own thread
ClientThread.Start()
End Sub
Protected Sub StartClient()
' This is the main code in ClientWinsock. It's run in its own
thread
Dim Client As TcpClient ' Variable for the Client TCP socket
Dim Buffer() As Byte ' used for outgoing message
Try
' Instantiate TCPClient object
- Client = New TcpClient()
' Attempt to connect to destination (server) computer on given
port number
Client.Connect(DestinationComputer, TCPIPPortNo)
' Convert UFT8 message to an array of bytes before sending
Buffer =
System.Text.Encoding.UTF8.GetBytes(mMessage.ToCharArray)
' Send out the buffer
Client.GetStream().Write(Buffer, 0, Buffer.Length)
Client.Close() ' Close the TcpClient
Catch e As Exception
' Write to Console the message that cannot be sent
Console.WriteLine("Can 't send:" & mMessage)
Finally
ClientThread.Abort() ' Abort thread
End Try
End Sub
End Class
Public Class ServerWinsock
' This object is created to serve many TCPClients and to receive
Unicode messages
' A thread is created to serve each TCPClient
Const MaxThread As Integer = 500 ' Maximum number of threads that
ServerWinsock can handle
Private oListener As TcpListener ' Variable for TcpListener
Private bStopListener As Boolean ' Flag indicating that user wants
to dispose this ServerWinsock
Private ActiveThreads As Integer ' Number of active threads, i.e.
threads that are serving TCPClients
' Event that returns the incoming message
Public Event OnMessage( ByVal IncomingMessage As String)
Public Sub New( ByVal PortNo As Integer)
' Instantiate a TcpListener on given PortNo
oListener = New TcpListener(PortNo)
oListener.Start() ' Start the TcpListener
' Create a thread for Sub AcceptConnection
Dim ServerThread As Thread
ServerThread = New Thread(AddressOf AcceptConnection)
ServerThread.Start() ' Run Sub AcceptConnection like a light
weight child process
End Sub
Protected Sub ProcessRequest()
Dim Buffer(5000) As Byte ' used to receive incoming message from
TcpClient
Dim bytes As Integer ' Actual number of bytes read
Dim RecvMessage As String ' UTF8 text string of the buffer (array
of bytes)
' Use oThread to reference the thread of this Sub
Dim oThread As Thread
oThread = Thread.CurrentThread()
' Accept request from the TcpClient
Dim oSocket As Socket
oSocket = oListener.AcceptSocket
- ' Keep looping until user wants to stop
While Not bStopListener
If oSocket.Available > 0 Then ' A message has arrived from
TcpClient
' read the incoming message into a buffer
bytes = oSocket.Receive(Buffer, Buffer.Length, 0)
SyncLock oThread ' Lock oThread
' Convert the array of bytes (i.e. the buffer) to UTF8 text
string
RecvMessage = System.Text.Encoding.UTF8.GetString(Buffer)
' Raise an event to return the message to the program that
owns this ServerWinsock
RaiseEvent OnMessage(RecvMessage)
End SyncLock ' unlock oThread
Exit While
End If
' get out of while loop if TcpClient has disconnected
If Not oSocket.Connected Then Exit While
End While
oSocket.Close() ' Close the TcpServer socket
SyncLock oThread ' Lock oThread
ActiveThreads -= 1 ' Decrement number of Active Threads
End SyncLock ' unlock oThread
End Sub
Private Sub AcceptConnection()
'This is the main Sub of ServerWinsock
' Keep looping until user wants to stop
Do While Not bStopListener
Thread.Sleep(100) ' Sleep 100 msec.
If oListener.Pending() Then ' received a request for
connection from a TCPClient
If ActiveThreads
- Để thử ServerWinsock và ClientWinsock, một TestBed Form được thiết kế để lăn-
xê một form cho mỗi computer mà ta muốn đặt một ServerWinsock trên ấy. Ta
phải nói rõ computer tên gì (hay cho TCP address của nó cũng được) và
TCPPortNo, tức là con số của cổng mà nó lắng nghe.
Khi khởi động, chương trình thử cho hiển thị hai TCPIPTestForms để bạn có thể
thử ngay bằng cách click nút Send
Bây giờ nếu bạn click nút Add Station trên TestBed form, con số TCP Port sẽ
được thêm vào listbox Active Servers và một TCPIPTestForm mới sẽ được hiển
thị. Click nút Send trên form mới nầy, bạn sẽ thấy message được gởi đến hai
TCPIPTestForms có sẵn như sau:
- Bạn có thể tải về mã nguồn của chương trình nầy kể cả hai classes
ClientWinsock và ServerWinsock.
Trong Zip file có chứa 2 projects: TCPQueue và clsWinsock. Nếu có gặp trở ngại
về việc reference thì load clsWinsock và compile trước để dùng clsWinsock.dll
trong bin folder làm reference cho project TCPQueue. Sau khi load TCPQueue,
bạn hãy remove clsWinsock từ References của nó rồi dùng Menu Command
Project |Add Reference để refernce library clsWinsock.dll vừa mới compile.
Nếu bao giờ IDE than phiền về Licence bạn chỉ cần delete file licences.licx
trong Project Folder.
nguon tai.lieu . vn