Saturday, January 2, 2010
OstroSoft Winsock
Download project source code
Minimum requirements: Visual Basic 5, oswinsck.dll*
* If you don't have OstroSoft Winsock Component, see installation instructions
1. In Visual Basic create a new Standard EXE project
2. Add a Reference to oswinsck
3. Add controls to the form: txtSource (TextBox), cmdView (CommandButton), txtURL (TextBox), txtProxy (TextBox), chkProxy (Checkbox), Label1 (Label)
4. Enter the following code:
Option Explicit
Dim sServer As String
Dim sPage As String
Dim WithEvents oWinsock As OSWINSCK.Winsock
Private Sub cmdView_Click()
Dim nPort As Long
txtSource.Text = ""
nPort = 80
sServer = Trim(txtURL.Text)
If InStr(sServer, "://") > 0 Then_
sServer = Mid(sServer, InStr(sServer, "://") + 3)
If InStr(sServer, "/") > 1 Then
sPage = Mid(sServer, InStr(sServer, "/"))
sServer = Left(sServer, InStr(sServer, "/") - 1)
If InStr(sPage, "#") > 1 Then _
sPage = Left(sPage, InStr(sPage, "#") - 1)
Else
sPage = "/"
End If
If InStr(sServer, ":") > 1 Then
nPort = Mid(sServer, InStr(sServer, ":") + 1)
sServer = Left(sServer, InStr(sServer, ":") - 1)
End If
Set oWinsock = Nothing
Set oWinsock = CreateObject("OSWINSCK.Winsock")
If chkProxy.Value = 0 Then
oWinsock.Connect sServer, nPort
Else
sPage = "http://" & sServer & sPage
oWinsock.Connect txtProxy.Text, nPort
End If
End Sub
Private Sub oWinsock_OnClose()
oWinsock.CloseWinsock
Set oWinsock = Nothing
End Sub
Private Sub oWinsock_OnConnect()
oWinsock.SendData "GET " & sPage & " HTTP/1.0" & vbCrLf & vbCrLf
End Sub
Private Sub oWinsock_OnDataArrival(ByVal bytesTotal As Long)
Dim sBuffer As String
oWinsock.GetData sBuffer
txtSource.Text = txtSource.Text & sBuffer
End Sub
Private Sub oWinsock_OnError(ByVal Number As Integer, _
Description As String, ByVal Scode As Long, _
ByVal Source As String, ByVal HelpFile As String, _
ByVal HelpContext As Long, CancelDisplay As Boolean)
Debug.Print "Error " & Number & ": " & Description
End Sub
Winsock API
Though Microsoft has done a great job wrapping Winsock API, developers are now facing a new challenge; thread-safety and asynchronous calls. Still the developer can get out without handling these issues, soon to realize that the application hangs or behaves unexpectedly due to the server asynchronously replying back, or that the UI will hang till the server responds. The goal of this project is to have a reliable socket library that will serve as a base for all TCP connections; covering most of the common mistakes where others left.
Ways of receiving data
You can receive data in three ways. First, you can block the application thread until a packet has been received, by calling the Receive method of a Socket object. Obviously, this is not a quite good solution for real-world scenarios. The second option is you can go into a loop (busy loop), polling the Socket object to determine if a packet has been received. This is an OK solution except that it will slow down your main application thread every time you tell the socket to poll and wait for a period of time. The third and last option is to receive data asynchronously by telling the socket to BeginReceive data into byte array, and once data has been received, call a special procedure that is set up to receive the data. Of course, by now you can tell what option I�ve chosen.
Working example
In my project, I�ve developed a component where you can just drag and drop into your application and whoo-ya, it's working. Let�s look into how and why it is done in this way.
Connection
The first thing you need to do in socket application is to connect to a specific port. There are three overloads of this function for ease of use:
Collapse
Public Sub Connect(ByVal hostNameOrAddress As String, ByVal port As Int32)
Public Sub Connect(ByVal serverAddress as IPAddress, ByVal port as Int32)
Public Sub Connect(ByVal endPoint As IPEndPoint)
The main function is as follows (error handling in the code snippet is omitted):
Collapse
Public Sub Connect(ByVal endPoint As IPEndPoint)
_mySocket = New Socket(AddressFamily.InterNetwork, _
SocketType.Stream, ProtocolType.Tcp)
_mySocket.Connect(endPoint)
If IsConnected() = True Then
RaiseEvent Connected(True)
End If
Dim bytes(_packetSize - 1) As Byte
try
_mySocket.BeginReceive(bytes, 0, bytes.Length, _
SocketFlags.None, AddressOf ReceiveCallBack, bytes)
Catch ex As Exception
Throw New Exception("Error receiving data", ex)
If IsConnected() = False Then
RaiseEvent Connected(False)
End If
End Try
End Sub
After establishing a connection, we raise a connection event which I will illustrate soon; once raised, we declare an array of bytes to receive the data. Now, the first trick is to use the BeginInReceive. This tells the socket to wait for a received data on another thread -a new one- from the thread pool, ha? Rings a bill? Yes. It is asynchronous, now I have my application running and not hanging to receive a packet. The function receives the bytes array which will be filled, its length, socket flag, an address of the callback function, and a state object. The callback function will be called when either an entire packet is received or the buffer is full. Again, if there is any error, a connection event is raised stating that the connection is lost and there were an error in receiving the data.
What about ReceiveCallback
ReceiveCallback function is the handler which will handle the packet when received. This is the core of the asynchronous operation. Take a look at the code and then I will explain.
Collapse
Private Sub ReceiveCallBack(ByVal ar As IAsyncResult)
Dim bytes() As Byte = ar.AsyncState
Dim numBytes As Int32 = _mySocket.EndReceive(ar)
If numBytes > 0 Then
ReDim Preserve bytes(numBytes - 1)
Dim received As String = _ascii.GetString(bytes)
'--Now we need to raise the received event.
' args() is used to pass an argument from this thread
' to the synchronized container's ui thread.
Dim args(0) As Object
Dim d As New RaiseReceiveEvent(AddressOf OnReceive)
args(0) = received
'--Invoke the private delegate from the thread.
_syncObject.Invoke(d, args)
End If
If IsConnected() = False Then
Dim args() As Object = {False}
Dim d As New RaiseConnectedEvent(AddressOf OnConnected)
_syncObject.Invoke(d, args)
Else
'--Yes, then resize bytes to packet size
ReDim bytes(PacketSize - 1)
'--Call BeginReceive again, catching any error
Try
_mySocket.BeginReceive(bytes, 0, bytes.Length, _
SocketFlags.None, AddressOf ReceiveCallBack, bytes)
Catch ex As Exception
'--Raise the exception event
Dim args() As Object = {ex}
Dim d As New RaiseExceptionEvent(AddressOf OnExcpetion)
_syncObject.Invoke(d, args)
'--If not connected, raise the connected event
If IsConnected() = False Then
args(0) = False
Dim dl As New RaiseConnectedEvent(AddressOf OnConnected)
_syncObject.Invoke(dl, args)
End If
End Try
End If
End Sub
Now that you�ve gone through the code, what do you think? Don�t worry, I will explain. First, we have a local variable to hold what I received from the server. Then I immediately issue EndReceive which will free any resources used by the BeginRecieve to create a new thread, and so forth. I also resize the array of bytes with the actual size. We, then, declare a local delegate object, and instead of modifying the main application (the user interface, for example) inside this component, it raises an event (Received) via OnReceive method. And since the event is raised on the main thread, it�s completely thread-safe. This is done by calling Invoke method from the private object _syncObject. But what is _syncObject? It is of type ISynchronizeInvoke. Ha? Well _syncObject is used to switch the context from the socket thread to the main thread (in this case, the UI component). This is achieved by having the SynchronizingObject property available to the main control. Here is the code for this property:
Collapse
Public Property SynchronizingObject() As _
System.ComponentModel.ISynchronizeInvoke
Get
If _syncObject Is Nothing And Me.DesignMode Then
Dim designer As IDesignerHost = Me.GetService(GetType(IDesignerHost))
If Not (designer Is Nothing) Then
_syncObject = designer.RootComponent
End If
End If
Return _syncObject
End Get
Set(ByVal Value As System.ComponentModel.ISynchronizeInvoke)
If Not Me.DesignMode Then
If Not (_syncObject Is Nothing) And Not (_syncObject Is Value) Then
Throw New Exception("Property ca not be set at run-time")
Else
_syncObject = Value
End If
End If
End Set
End Property
Events
There are three events and so three delegates:
* Connections: fires when a connection is opened or the connection is closed.
* Exception: fires whenever an exception happens.
* Received: fires whenever a packet is received.
password field, address bar
The screen keyboard can write all the fields (password field, address bar, Notepad etc.). You can use Ctrl, Shift, Alt, Altgr, Esc, (Alt + f4) or any other keyboard events. You can also use the screen keyboard for opening Notepad, WordPad, Winamp, Outlook, Internet Explorer, Task Manager, CDROM tray, calculator, run window, search, media player and your Hotmail inbox. The program shows your MSN messenger contact list along with the names of all the songs you have listened to, simultaneously. The screen keyboard looks like Microsoft screen keyboard, but it has some extra properties.
Background
My project heavily uses Win32 API (Application Programming Interface) .The screen keyboard contains low level language properties. I have seen similar samples written in C++, but they cannot write every where. I wanted the screen keyboard to be written in VB.NET because I could not find any code here in CodeProject or any other site that fulfilled my requirements. Almost all other similar programs were written in C++, and none of them fully met my requirements. The API text viewer contains all the Windows API functions and constraints. The API text viewer also displays all your requirements when you use it.
Windows API references
* File, Memory, Process, Threading, Time, Console and Comm control(kernel32.dll)
* Windows handling, and Windows UI control(user32.dll)
* Graphics and Imaging(gdi32.dll)
* Audio, Video, and Joystick control (winmm.dll)
* Registry, Event Log, Authentication, and Services(advapi32.dll)
* Printing (winspoll.dll)
* Asian characters support(imm32.dll)
* Executing processes(shell32.dll)
* Winsock, Windows berkley socket support(wsock32.dll)
* WNet* Instrumentation (mpr.dll)
* Common Dialog control(comdlg32.dll)
* Windows Network support(netapi32.dll)
* Windows Compression(lz32.dll)
* Common Controls(comctl32.dll)
* Versioning support (version.dll)
* Object linking and embedding(ole32.dll)
Screen keyboard
API functions for screen keyboard
Collapse
Public Declare Function SetActiveWindow Lib "user32" Alias _
"SetActiveWindow"(ByVal hwnd As Long) As Long
Declare Function keybd_event Lib "user32" Alias "keybd_event" _
(ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, _
ByVal dwExtraInfo As Long) As Long
Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" _
(ByVal lpstrCommand As String, ByVal lpstrReturnString As String, _
ByVal uReturnLength As Integer, ByVal hwndCallback As Integer) _
As Integer
API constraints for screen keyboard
Example
Collapse
Const KEYEVENTF_KEYUP = &H2 ' Release key
Const VK_RETURN = &HD 'enter
Const VK_SHIFT = &H10 'Shift
Const VK_CONTROL = &H11 'Ctrl
Const VK_CAPITAL = &H14 'capslock
Const VK_ESCAPE = &H1B 'escape
etc.
Screen keyboard has 122 constraints. &HD, H10, &H1B etc. are the key�s hexadecimal codes.
Screen keyboard functions
Collapse
Public Function whichbuttonpushed(ByVal _
sender As String) As String
The sender string gets the button's name when you click the button.
Example
Collapse
Select Case _sender
Case "btna"
keybd_event(VK_A, 0, 0, 0) 'send a key
keybd_event(VK_A, 0, KEYEVENTF_KEYUP, 0)'release a key
shiftrelease() 'release shift
altrelease() 'release alt
leftaltrelease() 'release altgr
Case "btnb"
keybd_event(VK_B, 0, 0, 0) 'send b key
keybd_event(VK_B, 0, KEYEVENTF_KEYUP, 0)'release b key
shiftrelease() 'release shift
altrelease() 'release alt
leftaltrelease() 'release altgr
etc.
end select
The following function gets the numlockbutton names and controls the numlock key state:
Collapse
Public Function numlockfunc(ByVal numlock_key As String, _
ByVal open_close As Boolean) As String
Example
Collapse
If open_close = False Then 'numlock opened
Select Case numlock_key
Case "btnnumlockdiv"
keybd_event(VK_DIVIDE, 0, 0, 0)
keybd_event(VK_DIVIDE, 0, KEYEVENTF_KEYUP, 0)
etc.
End Select
Else 'numlock closed
Select Case numlock_key
Case "btnnumlockdiv"
keybd_event(VK_DIVIDE, 0, 0, 0)
keybd_event(VK_DIVIDE, 0, KEYEVENTF_KEYUP, 0)
etc.
End Select
End If
Screen keyboard set focus function
The keyboard sets the focus to the active window when the button is clicked. The active window introducer number is (8975651603260375040). I found this number using the GetActiveWindow function:
Collapse
Public Declare Function GetActiveWindow Lib "user32" _
Alias "GetActiveWindow" () As Long
The following code snippet is very important for the screen keyboard because the function SetActiveWindow sets the focus to the active window.
Example
Collapse
SetActiveWindow(8975651603260375040)
The SetActiveWindow function is used after the button is clicked.
Messenger API
.NET contains Interop.MessengerAPI. I have included Interop.MessengerAPI.dll as reference and used the singin method. In addition to this MessengerAPI contains the openinbox property. Openinbox enables us to go to the Hotmail inbox directly if any one signs in on the computer.
Get the Winamp process
Collapse
Dim processes() As process
Dim process As New process
Dim processstring As String
Sub Timer1_Tick
Dim mycoll As New Collection
processes = process.GetProcesses
For Each process In processes
mycoll.Add(process, process.Id.ToString)
If process.ProcessName = "winamp" Then
processstring = process.MainWindowTitle
'gets song�s name(MainWindowTitle)
Exit For
End If
Next
Me.Text = "MY SCREEN KEYBOARD" & Space(55) & _
DateTime.Now & Space(20) & processstring
End sub
Capture the screen
Collapse
Call keybd_event(System.Windows.Forms.Keys.Snapshot, 0, 0, 0)
System.Threading.Thread.Sleep(200)
' To have time to catch the clipboard
Static i As Integer
Dim data As IDataObject
data = Nothing
data = Clipboard.GetDataObject()
Dim bmap As Bitmap
Dim p As New PictureBox
If data.GetDataPresent(GetType(System.Drawing.Bitmap)) Then
bmap = CType(data.GetData(GetType(System.Drawing.Bitmap)),
Bitmap)
p.Image = bmap
i += 1
p.Image.Save(
"C:\Documents and Settings\All Users\Desktop\Screenshot " _
& i.ToString & " .jpg", Imaging.ImageFormat.Jpeg)
End If
If Not File.Exists(
"C:\Documents and Settings\All Users\Desktop\Screenshot " _
& i.ToString & " .jpg") Then
screenshot()
Else
End If
License
SMPP knowledge
I gained some SMPP knowledge from one of my former jobs. After quitting the job, I wrote this library in my leisure time. Basically, what I did was to design the inheritance of the packets in a logic way, so far I think, and encode and decode the packets based on the SMPP specifications. Hope you guys like it.
SMPPLIB is a packet library for sending and receiving SMS messages (2-way SMS) based on SMPP specification v3.4 (partial support). The library is dependent on MFC (but what it depends on is CString and Winsock initialization only, I may strip off the dependence in future release).
The SMPPLIB is a building block for developing Short Message Service (SMS) value added services. It provides a simple solution to solve the interface problem with Short Message Service Center (SMSC).
Basically, there are plenty of classes for most packets defined in the SMPP specification, together with three ESME entities, which are EsmeTransmitter, EsmeReceiver and EsmeTransceiver. Usually, in the SMPP protocol, it works in the way that an ESME sends a command packet to the SMSC, then the SMSC replies with a reply packet. The same holds when the SMSC sends a packet to the ESME to indicate something, then the ESME sends back a reply packet to the SMSC.
Using the API to send a SMS message is easy.
Collapse
EsmeTransmitter trans;
CSmppAddress scr(2, 0, "85291234567");
trans.init("212.5.192.111", 5055);
trans.bind("t_test", "testpwd", "", scr);
trans.submitMessage("Hiya, this is my message", "85291111888", 2, 0);
Usage Instruction
The DLL library is built with SMPPLIB_EXPORTING defined, so when using it, this variable should not be defined. If the symbol is not defined, the classes in the SMPPLIB are imported using __declspec(dllimport).
To use the DLL, just put the Smpplib.dll and Smpplib.lib into a searchable directory of your application, and set your project options to link with Smpplib.lib. You are now ready for using the classes of SMPPLIB.
The SMPPCOM is the project for the COM component, it is dependent on the smpplib.dll built for the SMPPLIB project, so you may have to manually put them together. The SMPPCOM is an ATL project that supports MFC.
TestServer is only a project for me to test the sending/receiving of packets, so that I can verify the fields inside a packet after the complete encode/decode process. In TestServer, packets are processed in processPacket routine, while in SmppLibTest, packets are processed in the registered callback processPacket.
SMPPLIB Classes introduction
CSmppDate
It is the representation in the packets for specifying dates. It encapsulates a SYSTEMTIME as public member and a null indicator as protected member.
Collapse
void setDate(CString sd);
void setDate(SYSTEMTIME sdt);
CString toString();
CSmppDate& operator=(CSmppDate &dt);
int getLength(); void setNull();
bool isNull();
CSmppAddress
It is the representation of ESME address with type of number and numbering plan indicator.
Collapse
CSmppAddress(uint32 adrton, uint32 adrnpi, CString addr);
CSmppAddress& operator=(CSmppAddress& addr);
int getLength();
void setAddrNpi(uint32 adrnpi);
void setAddrTon(uint32 adrton);
void setAddr(CString addr);
CPacketBase
It is the base class of all SMPP packets. It deals with encoding the packet headers and loading the headers. In passing parameter, usually packets are passed by casting into CPacketBase, so for getting back the SMPP packet, they have to be cast to the respective type base on the command_id.
All packets in the library are inherited from CPacketBase, so they all support the following operations:
*
Collapse
uint32 getCommandId();
It gets the command ID from the packets as defined by the SMPP specification.
*
Collapse
uint32 getCommandStatus();
It gets the command status from the packet. Usually for SMSC response packets, a command status of 0 means no error, otherwise it indicates error condition. Detailed error code can be checked from the SMPP 3.4 Specification.
*
Collapse
uint32 getSequenceNumber();
It gets the sequence number for the packet. Sequence number is a monotonic increasing number for request packets and response packets. A response packet should set the same sequence number as the corresponding request packet.
*
Collapse
void setCommandId(uint32 cmid);
It sets the command ID for a packet. In most cases, user shouldn't set it by his own. Correct command ID is assigned as a packet is created. Beware, you shouldn't set command ID, it's been set based on the packet you created.
*
Collapse
void setCommandStatus(uint32 cmst);
It sets the command status of a packet. Command status is defined in the SMPP 3.4 specification.
*
Collapse
void setSequenceNumber(uint32 seqn);
It sets the sequence number of a packet.
SMPP packets supported in the API library
Every response packet in the SMPPLIB can be passing a command packet in the constructor, to set the sequence number of the response packet as the sequence number of the command packet.
CAlertNotification
Alert Notification Packet.
Collapse
CSmppAddress getSource();
CSmppAddress getEsme();
void setSource(CSmppAddress &src);
void setEsme(CSmppAddress &esme);
CGenericNack
Generic Negative Acknowledge Packet.
CBindPacketBase
Base class for Bind packets.
Collapse
CString getSystemId();
CString getPassword();
CString getSystemType();
CSmppAddress& getSourceRange();
void setSystemId(CString sid);
void setPassword(CString pwd);
void setSystemType(CString stype);
void setSourceRange(CSmppAddress &addr);
void setInterfaceVersion(uint32 iver);
CBindRespBase
Base class for Bind Response packets.
Collapse
void setSystemId(CString sid);
CString getSystemId();
CBindTransmitter
Bind Transmitter Packet. Inherited from CBindPacketBase.
CBindTransmitterResp
Bind Transmitter Response Packet. Inherited from CBindRespBase.
CBindReceiver
Bind Receiver Packet. Inherited from CBindPacketBase.
CBindReceiverResp
Bind Receiver Response Packet. Inherited from CBindRespBase.
CBindTransceiver
Bind Transceiver Packet. Inherited from CBindPacketBase.
CBindTransceiverResp
Bind Transceiver Response Packet. Inherited from CBindRespBase.
CUnbind
Unbind Packet.
CUnbindResp
Unbind Response Packet.
CMessagePacketBase
Base class for CSubmitSM and CDeliverSM packets.
Collapse
CString getServiceType();
CSmppAddress getSource();
CSmppAddress getDestination();
uint32 getEsmClass();
uint32 getProtocolId();
uint32 getPriorityFlag();
CSmppDate getScheduledDelivery();
CSmppDate getValidityPeriod();
uint32 getRegisteredDelivery();
uint32 getReplaceIfPresent();
uint32 getSmDefaultMsgId();
uint32 getDataCoding();
uint32 getSmLength();
void getMessage(PBYTE &msg, uint32 &nsz);
//setter
void setServiceType(CString stype);
void setSource(CSmppAddress &src);
void setDestination(CSmppAddress &dest);
void setEsmClass(uint32 esm);
void setProtocolId(uint32 pid);
void setPriorityFlag(uint32 pflag);
void setRegisteredDelivery(uint32 reg);
void setReplaceIfPresent(uint32 rip);
void setDataCoding(uint32 enc);
void setSmLength(uint32 smlen);
void setMessage(PBYTE msg, uint32 nsz);
CMessageRespBase
Base class for CSubmitSMResp and CDeliverSMResp.
Collapse
CString getMessageId();
void setMessageId(CString msgid);
CSubmitSM
Submit Short Message Packet. Inherited from CMessagePacketBase.
CSubmitSMResp
Submit Short Message Response Packet. Inherited from CMessageRespBase.
CDeliverSM
Deliver Short Message Packet. Inherited from CMessagePacketBase.
CDeliverSMResp
Deliver Short Message Response. Inherited from CMessageRespBase.
CEnquireLink
Enquire Link Packet.
CEnquireLinkResp
Enquire Link Response.
CQuerySM
Query Short Message Packet.
Collapse
CString getMessageId();
void setMessageId(CString msgid);
CQuerySMResp
Query Short Message Response Packet.
Collapse
CString getMessageId();
void setMessageId(CString msgid);
CSmppDate getFinalDate();
void setFinalDate(CSmppDate &fldate);
uint32 getMessageState();
void setMessageState(uint32 msgst);
uint32 getErrorCode();
void setErrorCode(uint32 errcode);
CDataPacketBase
Base class for CDataSM.
Collapse
CString getServiceType();
CSmppAddress getSource();
CSmppAddress getDestination();
uint32 getEsmClass();
uint32 getRegisteredDelivery();
uint32 getDataCoding();
void setServiceType(CString stype);
void setSource(CSmppAddress &src);
void setDestination(CSmppAddress &dest);
void setEsmClass(uint32 esm);
void setRegisteredDelivery(uint32 reg);
void setDataCoding(uint32 enc);
CDataSM
Data Short Message Packet. Inherited from CDataPacketBase.
CDataSMResp
Data Short Message Response Packet.
ESME entities supported:
CEsmeTransmitter
Transmitter to send SMS.
CEsmeReceiver
Receiver to receive SMS.
CEsmeTransceiver
Transceiver to send and receive SMS.
Besides bind, unbind, submitMessage and enquireLink operations of the ESME entities, packets can be sent directly through:
Collapse
bool sendPacket(CPacketBase &pak);
The ESME entity classes are designed to operate in asynchronous mode. A callback routine should be prepared in the order packets are received from SMSC. This is the basic place where a client application defines its logic. To register the call back routine to the ESME entities, call the registerProcessPacket with the callback function pointer.
The prototype of the callback function should be in the form:
Collapse
void __stdcall processPacket(CPacketBase *pak, LPVOID param)
The library is dependent on MFC. Before any call, the user application must initialize the Winsock library by issuing WSAStartup().
Sample Code
Collapse
#include "stdafx.h"
#include "SMPPAPI.h"
#include "winsock2.h"
#include "EsmeTransceiver.h"
#include "SmppUtil.h"
#include "smpppacket.h"
#include "smpp.h"
void __stdcall processPacket(CPacketBase *pak, LPVOID param)
{
switch (pak->getCommandId())
{
case SMPP_BIND_TRANSMITTER_RESP:
{
CBindTransmitterResp *pPak;
pPak = static_cast
TRACE("Packet is casted to CBindTransmitterResp\n");
TRACE(pPak->getSystemId());
TRACE("\n");
trans.submitMessage("Hiya, this is my message",
"85261110229", 2, 0);
}
break;
case SMPP_BIND_RECEIVER_RESP:
{
CBindReceiverResp *pPak;
pPak = static_cast
TRACE("Packet is casted to CBindReceiverResp\n");
TRACE(pPak->getSystemId());
TRACE("\n");
}
break;
case SMPP_BIND_TRANSCEIVER_RESP:
{
CBindTransceiverResp *pPak;
pPak = static_cast
TRACE("Packet is casted to CBindTransceiverResp\n");
TRACE(pPak->getSystemId());
TRACE("\n");
}
break;
case SMPP_DELIVER_SM:
{
CDeliverSM *pPak;
pPak = static_cast
TRACE("Packet is casted to CDeliverSM\n");
TRACE("\n");
}
break;
case SMPP_SUBMIT_SM_RESP:
{
CSubmitSMResp *pPak;
pPak = static_cast
TRACE("Packet is casted to CSubmitSMResp\n");
TRACE(pPak->getMessageId());
TRACE("\n");
trans.unbind();
}
break;
case SMPP_UNBIND_RESP:
{
CUnbindResp *pPak;
pPak = static_cast
TRACE("Packet is casted to CUnbindResp\n");
TRACE("\n");
}
break;
default:
break;
}
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
CSmppAddress scr(2, 0, "85263243234");
trans.init("212.5.192.111", 5055);
trans.registerProcessPacket(processPacket, NULL);
trans.bind("t_test", "testpwd", "", scr);
getch();
}
}
In the example, a callback function processPacket(CPacketBase *pak, LPVOID param) is prepared to handle the notifications from SMSC. To set the procedure, you've noticed that, in main, the sample called registerProcessPacket(processPacket, NULL). When the SMSC replies the SMPP_BIND_TRANSCEIVER_RESP, it displays the System ID of the remote system, and then submits a message to a mobile subscriber.
Example projects can be found in the example folders, the SmppLibTest and TestServer. To try it, just run the TestServer, then the SmppLibTest.
TestServer is a sample testing server built using the SMPPLIB, while SmppLibTest is a client application.
COM Support
With amazing COM support, several COM classes are created for the SMPPLIB. Now, you can send and receive SMS in any COM automation support language and system, including Visual Basic and ASP websites. After installation, you should have SMPPCOMLib defined in your system.
The following COM classes are created:
Collapse
SmppDateCom
Method
STDMETHOD(isNull)(VARIANT_BOOL *valnull);
STDMETHOD(setNull)(void);
STDMETHOD(toString)(BSTR *strdate);
SmppAddressCom
Property
TON
NPI
Address
SubmitSMCom / DeliverSMCom
Property
ServiceType
Source
Destination
esmClass
dataCoding
protocolID
priorityFlag
scheduledDelivery
validityPeriod
registeredDelivery
replaceIfPresent
smDefaultMsgId
Message
Method
STDMETHOD(compactMessage)(void);
STDMETHOD(flipByteOrder)(void);
STDMETHOD(setMessage)(VARIANT msgdata);
STDMETHOD(getMessage)(VARIANT* pmsgdata);
EsmeTransmitterCom / EsmeReceiverCom / EsmeTransceiverCom
Method
STDMETHOD(bind)(BSTR sysid, BSTR passwd,
BSTR systype, ISmppAddressCom* iaddr, VARIANT_BOOL* pret);
STDMETHOD(unbind)(VARIANT_BOOL* pret);
STDMETHOD(enquireLink)(VARIANT_BOOL* pret);
STDMETHOD(init)(BSTR svrip, LONG port);
STDMETHOD(close)(void);
STDMETHOD(get_Connected)(VARIANT_BOOL* pVal);
In addition to the common methods, EsmeTransmitterCom and EsmeReceiverCom also support a submitting message method.
STDMETHOD(submitMessage)(ISubmitSMCom* isubmit, BSTR* pMsgid, VARIANT_BOOL* pret);
For EsmeTransceiverCom and EsmeReceiver, whenever a MO message is received, an OnDeliverSM event will be fired. This event can be handled in an automation client.
For example, in VB.NET, it is handled by the subroutine declared with the following syntax.
Private Sub receiver_OnDeliverSM(ByVal dsm As SMPPCOMLib.DeliverSMCom) Handles receiver.OnDeliverSM
For details of their usage, please take a look to the SmppComTest project, which is a testing VB project for sending and receiving SMS messages.
In order to receive events, you should create your instance of EsmeReceiverCom or EsmeTransceiverCom with the WithEvent keyword in Visual Basic.
Acknowledgement
As I remember, I've partially used code from Norm Almond, which is the Buffer class and classes for TCP connections. Thanks.
Frequently Asked Questions
Q1: What kind of conformance with SMPP specifications v3.3 and v3.4, this library supports?
A1: This library partially supports SMPP spec v3.3 and v3.4. Here it provides a handful of tedious packet encoding and decoding methods, and put them into individual classes. Users have to just use exposing getter/setter to manipulate the packet contents instead. This library also provides some ESME entities to make connections to SMSC and the skeleton to exchange packets with SMSC.
Q2: How I use it in VB.NET, VC++.NET?
A2: This library was developed in VC6, and recompiled in VC++.NET. They are not managed classes. However, you can still use Interop Service to use COM components. If you don't want to use Interop service, you may consider to buy our commercial .NET version. It supports more than this free C++ version.
Q3: How to compile this library in VC6?
A3: For the smpplib, it comes with a VC6 project file, you just have to open it and compile it. For the SMPPCOM project, I didn't build the VC6 COM project in VS6, so you have to copy create the VC6 COM project yourself. It is quite trivial I think, you have to just create an ATL project and put the source code into the project.
Q4: What is linkListen in CEsmeReceiver?
A4: It listens to a port for SMSC to make connection to your receiver. This is usually for outbound packets send by SMSC to ask you to bind to them, to receive message.
Q5: What is the difference between the .NET commercial version and this free version?
A5: This free version was developed in VC++, while the commercial version is developed in C#. The commercial version offers more support on auto-reconnection, auto-enquirelink, auto deliver_sm and enquire_link responses, optional parameters, etc. It also comes with a featured SMS sending/receiving application with broadcast feature. Personally, I think C# is easier to use than VC++.
Q6: How do I receive delivery acknowledge?
A6: So far as I know, this needs some setup in your SMSC. Besides, in Submit SM packet, there is an attribute called Registered Delivery you can set. For details for the bit pattern you need to set, please consult the SMPP specification.
Q7: How do I start using this library within my VC++ project?
A7: Firstly, you should read thoroughly this document, and understand the sample project. This library operates in event (packet receiving) driven mode. You have to provide the callback handler for 'what is going to be done when a packet is received'.
Q8: Why couldn't my client create the COM components, but in my development platform it works fine?
A8: The COM project is just a wrapper to the smpplib.dll. You should put SmppCom.dll and Smpplib.dll together. If you did file copy to your client computer, you have to use 'regsrv32 SmppCom.dll' to register the COM into your client machine.
Q9: Does it support Optional Parameter?
A9: Currently, optional parameters are not supported in this C++ version, and I am not going to spend time to support them.
Q10: Can I use it to send free SMS?
A10: This packet library is used for connecting to an SMSC. It is not for sending SMS directly. Whether SMS is charged, it is up to the Telecommunication companies, but mostly they will charge. This library is not intended to be used by hobbyists, though you can still study it.
IOCP-enabled real-life echo server
I've been working with Winsock for a while, and even had some experience with IOCP. I have read several books and several articles that examined various related issues - but none of which provided me with a complete basis for a real-life application. I decided to try to combine most of my findings into one article that will walk you through the creation of a scalable and robust, IOCP-enabled real-life echo server!
This article is targeted at people who have both Winsock 2, and multi-threaded programming experience. All the code in this article was successfully compiled and run using Visual C++ 6 SP4 and Windows 2000 Server.
The model presented in this article will only work on Windows 2000 Server, Advanced Server, and Data Center operating systems. It takes advantage of an API function that is not available prior to Windows 2000: BindIoCompletionCallback().
The code presented here, can be ported to earlier version of Windows (Windows NT 3.51+) with slight changes.
Use the advices and code that is given in this article at your own risk. This code has been tested by me, and as far as I know, is bug-free. Please let me know of any bugs/problems you find in this code. Please remember that I take no responsibility for any kind of damage caused to you as a direct or in-direct result of usage or mis-usage of any information or piece of code found in this article.
You may freely use the code presented here in your own applications, but I'd like to know about it when you do! It would be nice of you if you drop me an email when you use my code in your product.
Please do not republish this article or any part of it without my permission.
The source code for the server application was modified a little, to fix a bug that was present in buffer.h. My thanks go to Dave Flores for going into the details in that file and locating the bug!
Now let's get to business.
What In The World Are I/O Completion Ports (IOCP) ?
For a more complete description of what's IOCP, and what it can do for you and your threads, I would recommend you to consult Programming Server-Side Applications for Microsoft Windows 2000 by Jeffrey Richter and Jason D. Clark, chapter 2: "Device I/O and Interthread Communication". I am going to briefly discuss it in the context of Winsock and server application development.
IOCP is maybe the most difficult to understand way to work with sockets. If you have used Winsock before (and I assume that you have), then you probably have used WSAAsyncSelect() or WSAEventSelect(), to request Winsock to inform you of relevant sockets' events. You use WSAAsyncSocket() to request Winsock to post messages to your window procedure upon events. You use WSAEventSelect() to request Winsock to signal an event upon events. If you wanted to take advantage of the threading model that Windows offers you (and you certainly should, in order to scale on machines with several CPUs), you had to spawn and take care of threads on your own.
When you use IOCP, you spawn a pool of threads once - and they are used to handle the network I/O in your application. Technically, in Windows 2000, you don't even have to spawn the pool yourself - you can let Windows take care of the spawning and management of the threads in the pool, and this is exactly what I'll do in this article.
The Main Program:
1. Create and initialize 500 sockets.
2. Create a listener socket.
3. Call AcceptEx() for each socket.
4. Bind the listener socket with the appropriate callback function (ThreadFunction() )
5. Create an event object, call it My_Die_Event.
6. Wait on My_Die_Event.
7. Cleanup (details regarding this will come later).
ThreadFunction()
(This function is called by Windows when an I/O operation has completed. Windows uses one of the threads from the thread-pool it created earlier to execute the function)
1. Check on which client the I/O operation completed.
2. Check which I/O operation completed.
3. Do some processing.
4. Issue another I/O operation (another read, or another write on the socket).
5. Return from the function. It will be called again, when another I/O operation completes.
So what do we have here?
We have threads that are all waiting for an I/O operation to complete. Those threads, as was specified before - are automatically created by Windows, when we first try to bind a socket to a callback function. Note that this automatic creation of threads is only available in Windows 2000 (and probably in future versions of Windows). If you intend to develop for Windows NT 4 or 3.51, you will have to spawn the threads yourself, and associate them, and the sockets, with a completion port. I will not show you how to do this in this article, but the changes are rather minor. Once an I/O operation completes, the operating system posts what's called "an I/O completion packet" to our completion port. Once the packet is sent, Windows resumes a thread from the pool, and has it run ThreadFunction(), while setting the appropriate parameters for the function. All this happens behind the scenes. As far as we are concerned - our callback function is automatically executed by some thread Windows assigns to it, when an I/O operation completes.
Let's take a look at the numbers we have set.
Q: How many threads are there in the pool that Windows creates?
A: This is up to Windows. In versions of Windows prior to 2000, you had to create the pool of threads yourself. The advantage to that, was that you had control of the actual number of threads in the pool. The disadvantage was that it was more complex, and less readable.
Q: Why do we create 500 sockets?
A: We are creating a set of sockets to be used throughout the program. Creating only 500 sockets will limits us to only 500 simultaneous connections - this could be ok, depends on the application. It would be a good idea to make this a configurable value. Creation and destruction of sockets is expensive. By creating a pool of sockets at the beginning of the program, we are enabling sockets-reuse and enhancing performance.
Getting Our Hands Dirty
Before we jump ahead and start writing the IOCP-manipulation code, I'd like to pause the IOCP discussion - and bring up another issue. If you are writing a server application, you have clients. When you work with clients, you have to mess with buffers. I am going to show you my general approach in these cases - this involves the creation of generic buffer class, and also what I call "packet class" (we'll get into it a bit later).
In order to receive data from a client, you call ReadFile(). In that call, you supply a buffer into which you want the data to be retrieved. This buffer must remain valid until the I/O operation completes. So far so good. You can set the buffer size to 1024 bytes, or whatever - and read data in portions of 1024 bytes. Let's take a look at various scenarios - what the buffer may look like when we receive a notification that a read operation was completed:
Scenario #1:
Only 321 bytes were received.
You interpret the data, figure out what the client wants, do some processing, send some data back to the client and call ReadFile() again, to receive more data.
Scenario #2:
Only 321 bytes were received.
You try to interpret the data, but you can't figure out what the client wants - the 321 bytes are not a complete command.
You need to call ReadFile() again to retrieve more data, but this will overwrite the first 321 bytes in the buffer!
Scenario #3:
1024 bytes were received.
You try to interpret the data, but you can't figure out what the client wants - the 1024 bytes are not a complete command. You need to call ReadFile() again to retrieve more data, but this will overwrite the contents of the buffer!
As you can see, we have no problem with scenario #1, but scenarios #2 and #3 are more problematic. Sometimes you will be able to act upon a client's request as it gets in, but you can't rely on that.
The case with the output buffer is a bit different. The buffer that you provide to WriteFile() must remain valid for the duration of the I/O operation. We would, however, like to be able to freely add data to be written out, regardless of a current state of an output I/O operation.
For the output buffer, I created an expandable buffer class. The sending operation logic is pretty simple, as you'll see in the code later. Basically, whenever you try to write data to a client, the program attempts to send the data immediately. If it can't - it stores the data in the expandable buffer, and the data is sent whenever the current I/O operation completes.
The case is a little bit different for the input buffer. It would be too much overhead to use such a buffer class for the input buffer. Instead, the code expands the input buffer automatically when required. The input buffer management is pretty interesting, you can examine the code as I'll show it.
About thread-safety:
The expandable buffer class is thread-safe. I made it thread-safe using a critical section. In some applications, you don't need to make the buffer class thread-safe, because calls to it are always serialized, because of the nature of the application. (If a situation where two threads attempt to write data to the same client simultaneously cannot occur, then it's safe to remove the thread-safety mechanisms). In order to remove the thread-safety mechanisms, you can simply inherit from the class, and override the relevant member functions. (InitializeInUse(), EnterInUse(), LeaveInUse() and DeleteInUse() ).
Look at buffer.h to see the buffer class code.
When sending data to clients, we provide WriteFile() with a buffer to be sent. This buffer must remain valid until the I/O operation is completed. I implement this by holding 2 buffers. The first - is the one that is passed to WriteFile(). The second - accumulates data, and is copied to the first whenever a send is completed.
Before showing you some more code, I would like to discuss another issue. The server application that you are writing, probably needs to receive commands from the clients, and respond with commands. If you are designing your own protocol (and not implementing some standard-protocol server, such as an FTP server or a web server), you have the freedom to decide the actual format of the data transferred between the server and the client.
I like to base everything on what I call a "packet infra-structure". The client posts requests by sending a complete packet, and the server responds by sending a complete packet. You can define a packet in whichever way you want. In this article, I have implemented what I consider the most generic packet type there can be.
A packet, in this article, is a structure that consists of an integer, and binary data. When the client posts a request, it first sends 4 bytes, describing the length of the requests, and then the request itself. This makes it very easy for the server to know when a request has fully arrived from the client (all it needs to do, is check the length of the request, and see that it has received enough data). The server responds in much the same way.
Internally, I created a tagPacket class, that holds two integers and a buffer. The second integer, holds the current size of the buffer. This could always be identical to the other integer, which holds the length of the data in the buffer - depends on the way you implement the application. If you create a new packet instance for each packet received, you can easily do with only one integer, describing the length of the data, and having the buffer always in the same size of the data. If, however, you decide not to allocate and de-allocate a packet for every client request, you may do so by separating the size of the buffer, and the length of the data. Whenever a new packet is received, the size of the buffer is examined. If the buffer is found large enough to contain the new data, the data is copied to the buffer, and its length is stored in the other integer.
Look at general.h to check out the packets manipulation code.
I believe that this code is pretty straight-forward. As you can see I have another function in this file - the function used to log errors. You will probably want this function to do something else, probably log errors to the system's Event Log. In buffer.h you will also find a function to retrieve a packet from the buffer.
As you can see, it is very easy to see whether a packet has arrived or not. Note that this approach may lead to some problems. For example - what if as a result from a bug, or a hacking attempt, the first 4 bytes indicate a value of 2 giga? In such a case, the abuser can keep sending data, and consume a lot of server resources. We will handle such cases later, by limiting a request's size and terminating abusing connections.
It is time to talk about the client's class. The approach I have taken while designing the client class and manipulation mechanisms, was of reuse. I prefer allocating memory once, and then reusing it, instead of allocating memory for each new connection and de-allocating it when the connection terminates. Allocation and de-allocation of memory are expensive operations, and should be avoided - in my opinion - in most cases - even at the expense of extensive resources consumption.
A few explanations regarding client.h and client.cpp.
Whenever we perform an I/O operation, using ReadFile() or WriteFile(), we should pass an overlapped structure as one of the parameters. We are actually passing an extended OVERLAPPED structure (a structure derived from the OVERLAPPED structure). The structure we are passing, contains some context information. The context information consists of the memory address of the client's class' instance that requested the I/O operation, and the type of the operation requested (read or write). This information is required in the callback function.
When we call ReadFile() to receive data, we pass it end_in_buf_pos, and not actual_in_buf. We also start reading data from start_in_buf_pos, and not actual_in_buf. Basically, those manipulations are done to avoid unnecessary expansion of actual_in_buf, and unnecessary calls to memmove(..). Look at CClient::Read(..) to see how it is done.
Whenever a ReadFile(..) operation completes, a timestamp is recorded. Some other section in the class uses this data to ensure that inactive clients (possibly abusers) are disconnected from the server. The function that is responsible for that is CClient::Maintenance(..) and it will be discussed in a short while.
CClient is an abstract class, which means that you must derive your own class from it. In your own class, you have to override three functions - these functions will be explained now.
int CClient::ProcessPacket(tagPacket *p)
Whenever a complete packet is received, this function is called with the new packet's address in p. In the code that I will present, this packet is a member of CClient. I used only one packet per client throughout the life of the application, to avoid constant allocations and de-allocations of tagPacket. This function is responsible for processing data received from the client - you may do there whatever you like - including sending data back to the client, using Write(..). The value that is returned by the function, tells the server application if it needs to do something. I have defined three possible values. CMD_DO_NOTHING - no action is required. CMD_DISCONNECT - client must be disconnected (possibly an abuser that sent a bogus packet). CMD_SHUTDOWN - the server should now shutdown. Those command values are declared in commands.h which will be shown a bit later.
void CClient::CreateInvalidPacket(tagPacket *p)
It could happen that a client is found to be an abuser during CClient::Read(..). For example, when a client attempts to send too much data in one packet. In such a case, instead of providing a true packet, CClient::Read(..) will call CClient::CreateInvalidPacket(..) which is responsible for creating a packet that will be recognized by CClient::ProcessPacket(..) as a special-purpose invalid packet, so it can take appropriate action (probably disconnect the client).
void CClient::Maintenance()
This function should occasionally be called for each client. Its purpose is to ensure that no client is abusing the system. Currently, it performs two different checks, as you can see in its code. It is called from the main thread.
Look at client.h and client.cpp to see the client class code.
Look at client_0.h and client_0.cpp to see the code I wrote for those three functions.
Putting It All Together (or - Enter: IOCP)
Now let's tie the pieces together, with IOCP. Folks, this is what you've been waiting for. First of all, we will create the function that is called when an I/O operation is completed. The function's declaration is actually dictated to us by Windows.
Look at callback.h to see this function's declaration.
Now comes the body of this function. It's really not too complicated. Note that it contains the line extern HANDLE dieEvent. It signals the dieEvent, on which the main thread is waiting, when it's time to shutdown.
Look at callback.cpp to see this function's definition.
This function counts the packets that it receives, and prints some stuff to the screen. Eventually you will probably want to change that.
Now comes the code that starts things up. It says everything we said in the beginning - initiates clients, sockets and waits on dieEvent. One interesting point, is the way maintenance is done. Every CHECK_CYCLE seconds (which I set to 10), it resumes (it waits on dieEvent up to CHECK_CYCLE seconds), and executes the CClient::Maintenance() function on every client in the system. This function makes sure that the client is not abusing the system. One way to abuse the system, is to connect to it and not send any data - thus not allowing AcceptEx(..) to accept the connection. You will probably want to tweak the values there to suit your own needs. You also may want to add other types of maintenance to the CClient::Maintenance() function.
That's about it. It's not a real echo server, in the sense that it's not actually repeating exactly what it receives. The application expects packets and returns the very same packets that it receives - however, it will not respond correctly to clear text. Testing such servers with telnet is pretty much impossible. That's why I've created a small Visual Basic utility, which connects to the server, and allows you to send any number of packets.
Note that there seems to be some kind of bug in the code that retrieves packets and shows their content. I haven't looked for it too much - it only happens when you receive massive amount of data at once.
I hope that this article and sample code will be useful for you. Feel free to email me for questions / remarks / whatever :-)
Internet Transfer Control
Most of you might have worked with Internet Transfer Control which is very handy control when it comes to Internet Programming but there is another control which even more robust and helps programmers creating more flexible applications. Winsock control comes with VB6 and is used to create applications that access the low-level functions of the Transmission Control Protocol/Internet Protocol (TCP/IP).
TCP/IP is a specification that defines a series of protocols used to standardize how computers exchange information with each other. TCP/IP provides communication across interconnected networks that use diverse hardware architectures and various operating systems. The protocols in TCP/IP are arranged in a series of layers known as a protocol stack. Each layer has its own functionality.
Winsock is a standard that is maintained by Microsoft. This standard is basically a set of routines that describe communications to and from the TCP/IP stack. These routines reside in a dynamic link library that runs under Windows. The winsock DLL is interfaced with TCP/IP and from there through the Internet.
In this article, I am going to show how to use the winsock in a client server environment, we will create two separate applications, one of which will be a server and the other will be a client. Both client and server will interact with each other to exchange data. Client will send a request to the server and the server which will be connected to a database will retrieve the information requested by the client from the database and will return the requested information back to the client. You will a database with this article, the database contains the item numbers and their prices. In real life situations, database might be located on a machine different from the one that hosts the client application.
I think it would be better to talk about the ports before we proceed any further. A port is a special memory location that exists when two computers are in communication via TCP/IP. Applications use a port number as an identifier to other computers, both the sending and receiving computers use this port to exchange data.
To make the job of communication easier, some port numbers have been standardized. These standard port numbers have no inherent value other than that users have agreed to use them with certain applications. Table below lists a number of popular and publicly accepted port numbers and their corresponding applications.
Service
Port
HTTP 80
FTP 20,21
GOPHER 70
SMTP 25
POP3 110
TELNET 23
FINGER 79
LOCAL LOOPS/CALLBACKS 0
Using the Winsock Control
Winsock is above the TCP/IP protocol stack in the ISO/OSI model. TCP/IP is an industry standard communication protocol that defines methods for packaging data into packets for transmission between computing devices on a heterogeneous network. TCP/IP is the standard for data transmission over networks, including the Internet. TCP establishes a connection for data transmission and IP defines the method for sending data packets.
The Microsoft Winsock control makes using the TCP/IP a breeze. Microsoft has wrapped up the Winsock and INetAPI API calls into a nice neat package that you can easily incorporate into your Visual Basic applications.
Winsock Operating Modes
The Transport layer (also known as the Host-to-Host Transport layer) is responsible for providing the Application layer with session and datagram communication services. The core protocols of the Transport layer are TCP and User Datagram Protocol (UDP). The Winsock control supports the following two operating modes:
* sckTCPProtocol
* sckUDPProtocol
Winsock Properties
Winsock enables you to create clients and servers using the same control. This dual functionality enables you to specify through property setting the type of application you will be building. The Winsock control uses a number of the same properties, whether you are creating client or a server, thereby all but eliminating the learning curve needed to create applications. Some of the important properties of the control are as following:
BytesReceived Property
This property returns the number of bytes currently in the receive buffer. This is a read-only property and is unavailable at design time. The value returned is a long integer.
LocalHostName Property
The LocalHostName property returns the name of the local host system. This is read-only property and is unavailable at the design time. The value returned is a string.
LocalIP Property
The LocalIP property returns the local host system IP address in the form of a string, such as 11.0.0.127. This property is read-only and is unavailable at design time.
LocalPort Property
This property returns or sets the local port number. This can be both read from and written to and is available at both design time and runtime. The value returned is a long integer.
Protocol Property
Returns or sets the protocol, either TCP or UDP, used by the Winsock control.
RemoteHost Property
The RemoteHost property returns or sets the remote host. This can be both read from and written to and is available both in design time and runtime. The value returned is a string and can be specified either as an IP address or as a DNS name.
RemotePort Property
This property returns or sets the remote port number.
State Property
This returns the state of the control as expressed by an enumerated list. This is read-only property and is unavailable at design time.
Winsock Methods
Some of the important methods of Winsock control are as following:
Accept Method
It accepts the request for connection from the client system. For this method to be used, the control must be in the listening state.
Close Method
The Close method terminates a TCP connection from either the client or server applications.
GetData Method
GetData is the method that retrieves the current block of data from the buffer and then stores it in a variable of the variant type.
PeekData Method
The PeekData method operates in a fashion similar to the GetData method. However, it does not remove data from the input queue.
Listen Method
This is invoked on the server application to have the server application wait for a TCP request for connection from a client system.
SendData Method
This method dispatches data to the remote computer. It is used for both the client and server systems.
Connect Method
The Connect method requests a connection to a remote computer.
I am not going to discuss events here. You can find the complete details of events on the Microsoft site (http://www.microsoft.com).
In the sample provided with this article, we are going to create two applications, one server and client. This is a real world example, where the clients requests some information from the server and the server retrieves some specific information from the database and sends the retrieved information back to the client. The database used in the sample is also provided with the code. The database name is Prices.mdb. This is a small database comprising of a single table containing two fields. The fields are item number and price. The clients sends the item number to the server and the server retrieves the price against that item number from the database and sends it back to the client. One of the current trends in software development today is the issue of thick clients versus thin clients. A thick client is basically an application that performs the bulk of the processing on the individual client PC, whereas a thin client performs the processing on the server.
Creating the Client
Follow the steps shown below:
1. Start a new EXE project.
2. Add a Winsock control to your application.
3. Add all the controls to the form (See the application for details).
Here is the complete code:
Collapse
Option Explicit
Private Sub cmdClose_Click()
Winsock1.Close
shpGo.Visible = False
shpWait.Visible = False
shpError.Visible = True
End Sub
Private Sub cmdConnect_Click()
Winsock1.RemoteHost = "11.0.0.1" 'Change this to your host ip
Winsock1.RemotePort = 1007
Winsock1.Connect
shpGo.Visible = True
txtItem.SetFocus
End Sub
Private Sub cmdSend_Click()
If Winsock1.State = sckConnected Then
Winsock1.SendData txtItem.Text
shpGo.Visible = True
Label3.Caption = "Sending Data"
Else
shpGo.Visible = False
shpWait.Visible = False
shpError.Visible = True
Label3.Caption = "Not currently connected to host"
End If
End Sub
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
Dim sData As String
Winsock1.GetData sData, vbString
'Label1.Caption = sData
txtPrice.Text = sData
Label3.Caption = "Received Data"
shpGo.Visible = True
shpWait.Visible = False
shpError.Visible = False
End Sub
Private Sub Winsock1_SendComplete()
Label3.Caption = "Completed Data Transmission"
End Sub
Creating the Server
The server portion of the price lookup example is designed to accept the item number sent from the client and look up the associated price in a database. The server than sends the information back to the client. There is file named as �path.txt� in the folder called as �server�. Locate that file and change the database path in the file to the location where the database is located on your machine. The connection to the database is made in the DataArrival event of the Winsock control. The following code segment opens the database and finds the first occurrence of the value in sItemData. When the record is found, the value contained in the price field is sent back to the client.
Collapse
' Get clients request from database
strData = "Item = '" & sItemData & "'"
rs.Open "select * from prices", strConnect, adOpenKeyset,adLockOptimistic
rs.Find strData
strOutData = rs.Fields("Price")
Follow the steps shown below to create the server:
1. Start a new Standard EXE in VB.
2. Add the Winsock control to your application.
3. Add the controls to the form as shown in the accompanying code (See the folder named as �server�).
Here is the complete code:
Collapse
Option Explicit
Dim iSockets As Integer
Dim sServerMsg As String
Dim sRequestID As String
Private Sub Form_Load()
Form1.Show
lblHostID.Caption = Socket(0).LocalHostName
lblAddress.Caption = Socket(0).LocalIP
Socket(0).LocalPort = 1007
sServerMsg = "Listening to port: " & Socket(0).LocalPort
List1.AddItem (sServerMsg)
Socket(0).Listen
End Sub
Private Sub socket_Close(Index As Integer)
sServerMsg = "Connection closed: " & Socket(Index).RemoteHostIP
List1.AddItem (sServerMsg)
Socket(Index).Close
Unload Socket(Index)
iSockets = iSockets - 1
lblConnections.Caption = iSockets
End Sub
Private Sub socket_ConnectionRequest(Index As Integer, ByVal requestID As Long)
sServerMsg = "Connection request id " & requestID & " from " & Socket(Index).RemoteHostIP
If Index = 0 Then
List1.AddItem (sServerMsg)
sRequestID = requestID
iSockets = iSockets + 1
lblConnections.Caption = iSockets
Load Socket(iSockets)
Socket(iSockets).LocalPort = 1007
Socket(iSockets).Accept requestID
End If
End Sub
Private Sub socket_DataArrival(Index As Integer, ByVal bytesTotal As Long)
Dim sItemData As String
Dim strData As String
Dim strOutData As String
Dim strConnect As String
' get data from client
Socket(Index).GetData sItemData, vbString
sServerMsg = "Received: " & sItemData & " from " & Socket(Index).RemoteHostIP & "(" & sRequestID & ")"
List1.AddItem (sServerMsg)
'strConnect = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=G:\Prices.mdb;Persist Security Info=False"
Dim strPath As String
'Change the database path in the text file
Dim fso As New FileSystemObject, txtfile, _
fil1 As File, ts As TextStream
Set fil1 = fso.GetFile("path.txt")
' Read the contents of the file.
Set ts = fil1.OpenAsTextStream(ForReading)
strPath = ts.ReadLine
ts.Close
Set fso = Nothing
strConnect = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Persist Security Info=False;Data Source=" & strPath & _
"; Mode=Read|Write"
Dim rs As New ADODB.Recordset
' Get clients request from database
strData = "Item = '" & sItemData & "'"
rs.Open "select * from prices", strConnect, adOpenKeyset, adLockOptimistic
rs.Find strData
strOutData = rs.Fields("Price")
'send data to client
sServerMsg = "Sending: " & strOutData & " to " & Socket(Index).RemoteHostIP
List1.AddItem (sServerMsg)
Socket(Index).SendData strOutData
End Sub
Running the example
1. Create executable for both the applications.
2. Launch both the applications.
3. Click the Connect button.
4. Enter a value from 0 to 6 (currently the database contains only six records, error handling is not done in this code, you can add the error handling yourself) and click the Lookup button. The associated price will be displayed in the price field.
Winsock Revamped
This version is out of date. Please check out the newer version located here: Winsock Revamped
Contents
Introduction
I started with this control when I found that VB.NET does not support the Winsock control I used to use in VB 6. It's been a while in coming, but I finally got it out and ready. The biggest challenge I had was using the TcpClient object and getting the RemoteIP. Good luck doing that - maybe in the next version of Studio but not in 2003. So I ditched the TcpClient and went with sockets.
Using the control
This control adds to your form like any other invisible control like the timer. Add it to your toolbox and you'll be all set. If you know how to use the VB 6 Winsock control, then you already know how to use this - I've built everything from the VB 6 Winsock control to match, and I think I've gotten the important things. If you already know, then go ahead and skip to the explanation of the control itself. For those who don't know how to use this, keep reading.
Once the control has been added to the form, there are only a few properties in the Properties window:
LocalPort- This property is used for server controls. It sets the port your program will listen on.RemoteIP- This is for client controls. It sets the IP address of the computer you want to connect to.RemotePort- This is for client controls. It sets the port of the computer you want to connect to.
Server Specifics
To start your server listening, you must run the Listen() method of the control (Winsock1.Listen()). Now, when someone tries to connect to you, all the connection requests will come through the event ConnectionRequest. You'll need a handler for this event.
In the connection handler, you'll need a Winsock control you want to accept the connection on. You could use the current listener as your connection - but that requires stopping the listener, and you want to accept other connections at the same time? Right? So, you'll need another Winsock control. Now, you don't need to add them to the form in order for them to work - so you can just instantiate one before this at some point. Here's an example of a connection request handler:
Private Sub Winsock1_ConnectionRequest(_
ByVal sender As Winsock_Control.Winsock, _
ByVal requestID As System.Net.Sockets.Socket) _
Handles Winsock1.ConnectionRequest
'AddHandler Winsock3.DataArrival, AddressOf DataArrival
Winsock3.Accept(requestID)
Winsock3.Send("Welcome!")
End Sub Notice the AddHandler statement I have commented out. At one point, I was using an instantiated control and needed to add a handler for the incoming data. I created a subroutine called DataArrival to handle that. Now, in the demo project, it's just another control on the form. As this is the connection request (server, of course), I've decided to send a welcome message as soon as the connection is accepted. This is done using the Send method. Just place your string there, and it will send it.
Clients
The clients must use the Winsock.Connect() method to connect to a server. It is overloaded, so you can use no arguments, the IP, or the IP and Port when calling it. When using no arguments, it will use the IP and Port already stored in RemoteIP and RemotePort.
Clients and Servers
All the data - for the server and clients - come through the DataArrival method. You get the data that comes in using the GetData method. Here's a short example of getting the data:
Private Sub DataArrival(_
ByVal sender As Winsock_Control.Winsock, _
ByVal BytesTotal As Integer) _
Handles Winsock3.DataArrival
Dim s As String
sender.GetData(s) Notice that the sender is getting, the data and it's passing s as a ByRef variable. The event passes the control to the DataArrival subroutine through the sender control, allowing you to send information right back to it.
Sending and receiving data is done using the Send and GetData methods respectively. You've just seen how the control raises the DataArrival event when data comes in - let's take a look now at how that data is sent out and received by the user.
The Send() method (as of 10/20/2005) is overloaded, and can accept one of three types for sending, a string, a bitmap, or a byte array. Using these three overloads, you should be able to send any kind of data you want - provided you convert it to a byte array for sending - just make sure you know what you are retrieving when it is received. The string and bitmap overloads will both convert their respective data into a byte array and then send it out using the third overload.
The GetData() method (again as of 10/20/2005) has also been overloaded the same way for retrieving data, as the Send method - you get your bitmap, string, and byte array overloads. There is a significant difference between the VB 6 version and this version here, however. In the VB 6 version, if you chose to hold off using the GetData method in the DataArrival routine and the DataArrival routine ran more than once, when you finally use GetData, it retrieves the entire buffer. With this control, it will grab the first data in a FIFO (first in first out) method. This data is stored as a byte array in a collection until you use one of the GetData overloads, which it then pops out of the collection and performs the necessary conversion (if any is needed) before returning it to you. Be sure to use the correct overload for the incoming data.
One thing you should remember when you are done communicating with the server - or client - is that you should close the connection. Do so using the Close() method.
The Control
OK, now for the meat of it - the nitty gritty. You want to know how it's done. OK. The key - sockets, and of course asynchronous function calls (threads too!). I'll just go over a little bit of the code as most of it is pretty self explanatory.
The Listen() method
The Listen method starts up a new thread, running a continuous async call as in the following code:
Dim tmpSock As Socket
If GetState = WinsockStates.Listening Then
tmpSock = _sockList.EndAccept(asyn)
RaiseEvent ConnectionRequest(Me, tmpSock)
_sockList.BeginAccept(New _
AsyncCallback(AddressOf OnClientConnect), _
Nothing)
End If Here, I'm declaring a temporary socket used to accept the new connection, and pass it to the ConnectionRequest event - which of course starts right back again.
The Connect() method - incoming data
Again, I use async calls to begin the connection (see Public Sub Connect()). This takes us over to the OnConnected subroutine. OnConnected actually calls another sub to finalize the connection (why?? I can't remember any more), but the Finalize connection sub starts our reader. Again - this is another async call. Once the data has been pulled into the byte array, it is sent over to the AddToBuffer sub. Let's take a look at this routine:
Private Sub AddToBuffer(ByVal bytes() As Byte, ByVal count As Integer)
Dim curUB As Integer
If Not _buffer Is Nothing Then
curUB = UBound(_buffer) Else curUB = -1
Dim newUB As Integer = curUB + count
ReDim Preserve _buffer(newUB)
Array.Copy(bytes, 0, _buffer, curUB + 1, count)
Dim byterm As Byte = 4
Dim idx As Integer = Array.IndexOf(_buffer, byterm)
Dim byObj() As Byte
Dim byTmp() As Byte
While idx <> -1
'found an EOT (end of transmission)
'marker split it if necessary and
'put it in the buffer to get - call DataArrival
Dim ln As Integer = _buffer.Length - (idx + 1)
ReDim byObj(idx - 1)
ReDim byTmp(ln - 1)
Array.Copy(_buffer, 0, byObj, 0, idx)
Array.Copy(_buffer, idx + 1, byTmp, 0, ln)
ReDim _buffer(UBound(byTmp))
Array.Copy(byTmp, _buffer, byTmp.Length)
Me._bufferCol.Add(byObj)
RaiseEvent DataArrival(Me, byObj.Length)
idx = Array.IndexOf(_buffer, byterm)
End While
If count < class="code-digit">1 And _buffer.Length > 0 Then
_bufferCol.Add(_buffer)
RaiseEvent DataArrival(Me, _buffer.Length)
_buffer = Nothing
End If
End Sub Here, the bytes that were received from the socket and the number of bytes received are passed as arguments. The number received is necessary as the socket will completely fill the byte array - even though it desn't use it all. This will allow us to separate the junk from the data, which is what happens during the first Array.Copy call.
Next, we check for an EOT (end of transmission) character (Dim byterm as Byte = 4). The EOT character is appended to all data you send using this control. It allows the control to separate overlapping data - the demo project shows an example of this when sending a picture twice, the receiving sockets receive it as one block of data so we need to be able to separate that as well. This is what goes on during the While loop, the saving of the first object data to the buffer collection (used for data retrieval during GetData), and truncating that object out of the byte buffer. Finally, the While loop raises the DataArrival event so you can grab the object just sent to the buffer collection.
For backwards compatability, and for other languages that don't send EOTs automatically, we check that the byte count is less than the length of the original byte array - telling us if it's finished receiving or not, but only calling the DataArrival event if there is data that is in the buffer.
Dynamic Connections
Dynamic connections - ah the joys of dynamics. In VB 6, you would have to create a control array and increment the array as you need more. You could only remove a connection once the upper bound connection was closed (at least easily), and to an unused connection from one that was closed while another connection was open - you would have to iterate through the array to find one that was closed, and use that one.
Not anymore! With .NET, we have collections, which are much better than arrays for storing data this way. Let's walk through the process of using Winsock connection collections!
First, we will need a WinsockCollection class:
Public Class WinsockCollection
Inherits CollectionBase
Private col As New Collection
Public Sub Add(ByVal value As Winsock_Control.Winsock)
Add(value, "")
End Sub
Public Sub Add(ByVal value As Winsock_Control.Winsock, _
ByVal Key As String)
Try
list.Add(value)
col.Add(Key)
Catch ex As Exception
MsgBox("Add")
End Try
End Sub
Public Shadows Sub Clear()
MyBase.Clear()
End Sub
Public Function IndexOf(ByVal value As _
Winsock_Control.Winsock) As Integer
For i As Integer = 0 To Count - 1
If Item(i) Is value Then Return i
Next
Return -1
End Function
Public Function GetKey(ByVal value As _
Winsock_Control.Winsock) As String
Return col.Item(IndexOf(value) + 1)
End Function
Public Sub Remove(ByVal value As Winsock_Control.Winsock)
Try
If list.Contains(value) Then
col.Remove(IndexOf(value) + 1)
list.Remove(value)
Else
MsgBox("Value doesn't exist in collection.")
End If
Catch ex As Exception
MsgBox("Winsock/Remove")
End Try
End Sub
Public Sub RemoveIndex(ByVal index As Integer)
Try
If index > Count - 1 Or index < 0 Then
Throw New IndexOutOfRangeException
Else
list.RemoveAt(index)
col.Remove(index + 1)
End If
Catch ex As Exception
MsgBox("Winsock/RemoveIndex")
End Try
End Sub
Default Public ReadOnly Property Item(ByVal index As Integer) _
As Winsock_Control.Winsock
Get
Try
If index > Count - 1 Or index < 0 Then
Throw New IndexOutOfRangeException
End If
Return CType(list.Item(index), Winsock_Control.Winsock)
Catch ex As Exception
MsgBox("Collection/Item")
End Try
End Get
End Property
Default Public ReadOnly Property Item(ByVal Key As String) _
As Winsock_Control.Winsock
Get
Try
Dim idx As Integer = -1
For i As Integer = 1 To col.Count
If col.Item(i) = Key Then
idx = i
End If
Next
If idx = -1 Then Return New Winsock_Control.Winsock
Return CType(list.Item(idx - 1), Winsock_Control.Winsock)
Catch ex As Exception
MsgBox("Key/Item")
End Try
End Get
End Property
End Class The first thing you'll notice here is that I have a collection within the collection. This was necessary to store the key values for my particular application (a chat server) as the Winsock control didn't have a property for a key value.
You'll also notice the message boxes I put in the various Try...Catch blocks just to know quickly, while it is running, where the errors occur. Other than that - this is a pretty standard inherited collection.
Now that we have our collection - it should be declared with the proper scope for your project, for me this was form global.
Our magic starts with the ConnectionRequest of your listener:
Private Sub wskListener_ConnectionRequest(ByVal sender As _
Winsock_Control.Winsock, ByVal requestID As _
System.Net.Sockets.Socket) _
Handles wskListener.ConnectionRequest
‘Builds y.UID.ToString for the Winsock Key
Dim x As New Winsock_Control.Winsock
WskCol.Add(x, y.UID.ToString)
AddHandler CType(WskCol.Item(y.UID.ToString), _
Winsock_Control.Winsock).DataArrival, _
AddressOf wsk_DataArrival
AddHandler CType(WskCol.Item(y.UID.ToString), _
Winsock_Control.Winsock).Disconnected, _
AddressOf wsk_Disconnected
CType(WskCol.Item(y.UID.ToString), _
Winsock_Control.Winsock).Accept(requestID)
CType(WskCol.Item(y.UID.ToString), _
Winsock_Control.Winsock).Send("Welcome!")
End Sub Here, you'll notice y.UID.ToString. y.UID.ToString was a unique identifier for my users, this was how I tied my users to their connection - hence the key for the collection provided easy access to the users' connections.
Notice also the order of operations here. I first create the new Winsock object, then add it to the collection, and finally add the handlers and accept the connection. If I had added the handlers and accepted before adding the Winsock object to the collection, I would have had problems using the object later on.
The DataArrival subroutine does not need anything special done to it - as it already receives a copy of the connection via the sender.
The next magic happens in the Disconnected event:
Private Sub wsk_Disconnected(ByVal sender As Winsock_Control.Winsock)
WskRemoval.Add(sender)
Remove()
End Sub
Private Sub Remove()
If WskRemoval.Count > 0 Then
WskCol.Remove(WskRemoval.Item(1))
WskRemoval.Remove(1)
End If
End Sub First, take notice of WskRemoval.Add(sender) - I'm adding the Winsock control to be removed to another collection. I've found that just removing the Winsock object from the Winsock collection can tend to cause a bottleneck because it's doing it too quickly. So I left that for the Remove method.
The Remove() sub iterates through the WskRemoval collection one by one, removing each Winsock object that has been queued for removal until there are none left to be removed.
The nice thing about using the collection is there is nothing in between when you remove a connection, and you never have to search for a closed connection that you can use - you just create on and go. A great improvement over the VB 6 Winsock arrays!
Finishing Up
I hope everyone who uses this control or the code finds this useful. I know I did - I've already created a chat server that runs with a Java client in a browser. Works great.
Any suggestions/bugs/comments, please send them to codeproject@stdominion.net.
History
- 10-20-2005
- Changed buffer mechanism to allow storing of multiple objects - required if you use multiple sends on bitmaps, otherwise they stack as one bitmap and don't get retrieved properly. Utilizes the EOT (end of transmission) (ASCII 4) to do the separating, although it still checks for a count less than the byte length for backwards compatibility.
- Sending data now appends an EOT at the end of the data.
- 10-19-2005
- Added support for direct
Byte()sending and retrieving. - Added support for bitmap sending and retrieving.
- Added support for direct
- 08-24-2005
- Released.