Contents
In the first sample, we explored basic SocketPro features. We haven’t completed the sample yet because it lacks a set of key features. We’ll finish the rest of ignored key features in the first tutorial. You will learn a few how-tos:
· How to secure data communication between a client and a server using secure socket layer (SSL/TLS)
· How to authenticate a client
· How to reuse high performance libraries
· How to use anonymous delegates or Lambda expression to process return results on client side
· How to use the interface IAsyncResultsHandler to process return results on client side
· How to limit the number of socket connections for a client
· How to use chat service for notifying various messages among connected clients
At the end, you will see a few FAQs and their answers with comments. In addition to this text tutorial, you can find its video tutorials here and here.
2. How to secure data communication between a client and a server using secure socket layer (SSL)
Here is the article named as Secure your data communication between two networked machines at this site http://www.udaparts.com/document/articles/security.htm. Reading through the short article will help you understand the code.
SocketPro uses either SSL/TLS or Blowfish to encrypt data so that only two partners can know data exchanged. This tutorial demonstrates how to use MSTLSv1 to encrypt all of data movements between a client and a SocketPro server through MS secure channel (SSPI).
At client side, you just use one line code to complete the job as shown in the below.
m_ClientSocket.GetUSocket().EncryptionMethod = (short)(chkUseSSL.Checked ? USOCKETLib.tagEncryptionMethod.MSTLSv1 : USOCKETLib.tagEncryptionMethod.NoEncryption);
On client side, you can use the property ISocketBase::PeerCertificate to get the interface IUCert to a server certificate. With help of the interface, you can authenticate a remote server before sending credentials (user id and password) to avoid man-in-middle attack. See the commented code inside the client sample.
At server side, you also need one line to complete the work like the below.
UseSSL("C:\\cyetest\\socketpro4\\bin\\udacert.pfx", "mypassword", "udaparts", tagEncryptionMethod.MSTLSv1);
3. How to authenticate a client
The above code ensures data communication in the encryption format. We should also make sure people who have valid user ids and passwords are allowed to access a SocketPro server. This process is called as authentication. At this time, SocketPro supports three methods to authenticate a client according to given user id and password. For details, see the above article. This tutorial sample uses amMixed method, which is the combination of amOwn and amIntegrated. You can authenticate a client by overwriting the virtual function CSocketProServer::OnIsPermitted. Here is the code:
protected override bool OnIsPermitted(int hSocket, int lSvsID)
{
string strUID = GetUserID(hSocket);
//password is available ONLY IF authentication method to either amOwn or amMixed
string strPassword = GetPassword(hSocket);
Console.WriteLine("For service = {0}, User ID = {1}, Password = {2}", lSvsID, strUID, strPassword);
tagAuthenticationMethod am = Config.AuthenticationMethod;
if(am == tagAuthenticationMethod.amOwn || am == tagAuthenticationMethod.amMixed)
{
//do my own authentication
return IsAllowed(strUID, strPassword);
}
return true;
}
Note that once the function is called, password is immediately cleaned from the SocketPro server for the better security and you can not use GetPassword to retrieve a password any more. SocketPro always cleans password as soon as possible on both client and server sides.
4. How to reuse libraries at server side
You can load previously created libraries into a SocketPro server. For how to create these libraries in C/C++, you may see the attached samples Udemo and Winfile. Here is the code to reuse these libraries. These libraries are usually written from C++ for the best performance.
private void ReuseLibraries()
{
//those libraries are distributed in the directory ..\bin
IntPtr hInst = CBaseService.AddALibrary("uodbsvr.dll", 0);
if(hInst == (IntPtr)0)
{
Console.WriteLine("library uodbsvr.dll not available.");
}
hInst = CBaseService.AddALibrary("ufilesvr.dll", 0);
if(hInst == (IntPtr)0)
{
Console.WriteLine("library ufilesvr.dll not available.");
}
hInst = CBaseService.AddALibrary("c:\\socketpro4\\bin\\udemo.dll", 0);
if(hInst == (IntPtr)0)
{
Console.WriteLine("library udemo.dll not available.");
}
}
5. How to limit the number of socket connections for a client
Sometimes, we like to restrict the number of socket connections from a client. It reduces DOS attacks from a hacker. To do so, simply put one line code before starting a SocketPro listening socket as show in the below. By default, the max number of connections is 32 per client.
//limit the max number of connections to 2 for each of client machines
Config.MaxConnectionsPerClient = 2;
6. How to use chat service for notifying various messages among connected clients
A typical distributed application contains a net communication between a server and a client only. There is no communication among two connected clients at all. However, this architecture does not meet application requirements which require better cooperation among clients under many cases. We need some ways to enable communications among clients whenever an interested thing happens. This type of communication is similar to Internet chat among a group of people. Therefore, SocketPro provides this type of chat service, which is a built-in service for all of connected socket connections.
To set up a chat group at server side, see the below code for starting one chat group.
bool ok = PushManager.AddAChatGroup(1, "Group for SOne"); //A unique non-zero number
You can join or leave a group at either client side or server side. To join or leave a group at client side, call the IUChat::Enter or Exit. To join or leave a group at server side, you can call CClientPeer::Enter or Exit. See the below code from the function OnSocketConnected for joining a chat group.
//join the group 1 -- SOne Group
int[] groups = {1};
m_ClientSocket.Push.Enter(groups);
With SocketPro, you can send out a message at either client or server side. Take this tutorial sample. At client side, you call IUChat::XSpeak using the following code.
//send a text message to groups 1, 5 SOneSvs Group
int[] groups = {1, 5};
GetAttachedClientSocket().Push.Broadcast("EchoData called", groups);
At server side, similarly call the following code.
//inform all of joined clients that idSleep is called
int []groups = {1, 2};
bool b = Push.Broadcast("Sleep called", groups);
Now we need a way to track various notification events at client side. To set up tracking notification events, you simply set a delegate to OnBaseRequestProcessed as shown in the below.
public void OnBaseRequestProcessed ( System.Int16 sRequestID )
{
switch(sRequestID)
{
case (short)USOCKETLib.tagChatRequestID.idEnter: //old version depreciated
case (short)USOCKETLib.tagChatRequestID.idXEnter:
{
string strMsg;
int nGroup = 0;
int nPort = 0;
int nSvsID = 0;
string strUID = null;
string strIPAddr = m_ClientSocket.GetUSocket().GetInfo(0, out nGroup, out strUID, out nSvsID, out nPort);
strMsg = strUID;
strMsg += "@";
strMsg += strIPAddr;
strMsg += ":";
strMsg += nPort;
strMsg += " has just joined the group";
txtMsg.Text = strMsg;
}
break;
case (short)USOCKETLib.tagChatRequestID.idExit:
{
string strMsg;
int nGroup = 0;
int nPort = 0;
int nSvsID = 0;
string strUID = null;
string strIPAddr = m_ClientSocket.GetUSocket().GetInfo(0, out nGroup, out strUID, out nSvsID, out nPort);
strMsg = strUID;
strMsg += "@";
strMsg += strIPAddr;
strMsg += ":";
strMsg += nPort;
strMsg += " has just exited from the group";
txtMsg.Text = strMsg;
}
break;
case (short)USOCKETLib.tagChatRequestID.idSpeak: //old version depreciated
case (short)USOCKETLib.tagChatRequestID.idXSpeak:
{
int nGroup = 0;
int nPort = 0;
int nSvsID = 0;
string strUID = null;
string strMsg = (string)m_ClientSocket.GetUSocket().Message;
string strIPAddr = m_ClientSocket.GetUSocket().GetInfo(0, out nGroup, out strUID, out nSvsID, out nPort);
strMsg += " from ";
strMsg += strUID;
strMsg += "@";
strMsg += strIPAddr;
strMsg += ":";
strMsg += nPort;
txtMsg.Text = strMsg;
}
break;
default:
break;
}
}
With this tutorial sample, you can see different notifications whenever a client is connected or disconnected, or whenever a client calls either method Echo and Sleep. Note some of messages originate from a client, and some from its server.
On server side, you can also track coming chat requests by overriding the virtual function OnChatRequestComing. You can put debug break points to see what parameters are and figure them out.
You can also send a message privately to a client identified by user id or its IP address and port number from either a client or a server.
Sometimes, you may like to start an independent service just for chat service only. Note that this is optional. To do so, you create an instance using the below code.
private CNotificationService m_chat = new CNotificationService();
Afterwards, you need to add the service into SocketPro by the below call as you have learnt before.
ok = m_chat.AddMe(USOCKETLib.tagServiceID.sidChat);
7. Processing return results using the interface IAsyncResultsHandler on client side
The tool uidparser.exe is used to help beginners quickly get started with SocketPro. While you are getting used to SocketPro asynchronous programming style, we hope you to get rid of the tool after you understand the features of SocketPro.
Now, let’s delete the file Tone.cs from project first. Afterwards, make class frmSampleOne implemented with the interface IAsyncResultsHandler, but use the below code to declare the variable m_MySvsHandler:
private CAsyncServiceHandler m_MySvsHandler;
Further, we use the below code to instantiate handler and client socket as well as set events.
m_ClientSocket = new CClientSocket();
m_MySvsHandler = new CAsyncServiceHandler(TOneConst.sidCTOne, m_ClientSocket, this);
m_ClientSocket.m_OnSocketClosed += OnClosed;
m_ClientSocket.m_OnSocketConnected += OnConnected;
m_ClientSocket.m_OnBaseRequestProcessed += OnBaseRequestProcessed;
As you can see, we directly attach m_MySvsHandler to a client socket (m_ClientSocket). Also, we set an interface (this) to use the methods of the interface IAsyncResultsHandler to process return results within this window form instead.
Here is the code for processing return results in window form:
public void Process(CAsyncResult AsyncResult)
{
object objOut = null;
int nData = 0;
switch (AsyncResult.RequestId)
{
case TOneConst.idQueryCountCTOne:
AsyncResult.UQueue.Load(ref nData);
txtCount.Text = nData.ToString();
break;
case TOneConst.idQueryGlobalCountCTOne:
AsyncResult.UQueue.Load(ref nData);
txtQueryGlobalCount.Text = nData.ToString();
break;
case TOneConst.idQueryGlobalFastCountCTOne:
AsyncResult.UQueue.Load(ref nData);
txtQueryGlobalFastCount.Text = nData.ToString();
break;
case TOneConst.idEchoCTOne:
AsyncResult.UQueue.Load(ref objOut);
break;
case TOneConst.idSleepCTOne:
btnSleep.Enabled = true;
break;
default:
break;
}
}
After comparing the above code with the code in tutorial one, they are very similar to each other. SocketPro adapter now provides this approach for you. This is one of many choices as you can also use the other ways to process return results. If you use this approach, the virtual function OnResultReturn of the class CAsyncServiceHandler will not be called again.
8. Processing return results by anonymous delegate or Lambda expression on client side
At this writing time, many development languages supports either anonymous delegate, Lambda expression or both. It is found that we can use them easily in SocketPro so that you can make a asynchronous request and its return results within the same function scope so that it makes code more readable and easier to debug. Without use of this technique, asynchrony computation will make code scattered at different places, which reduces code readability and increases the difficulty of debugging. Once you are used to this style, we are expecting you will like it. Anonymous delegate and Lambda expression make asynchrony computation considerably more joyful and smarter.
Let me give you an example here.
btnSleep.Enabled = false;
int nSleep = Int32.Parse(txtSleep.Text);
m_MySvsHandler.SendRequest(TOneConst.idSleepCTOne, nSleep, delegate(CAsyncResult ar)
{
btnSleep.Enabled = true;
}
);
It is also pointed out that we omit the method CClientSocket::WaitAll. The code is considerably better in code simplicity and readability. If you use this way, both interface IAsyncResultsHandler and CAsyncServiceHandler::OnResultReturn will not be called again.
SocketPro totally provides you three ways, CAsyncServiceHandler.OnResultReturn, IAsyncResultsHandler and anonymous delegate as well as Lambda expression, to process return results at your will. In general, if you develop a window form application, you may like to use anonymous delegate, Lambda expression and IAsyncResultsHandler. If you develop a middle tier, console or web asp.net application, you may like to use CAsyncServiceHandler.OnResultReturn as shown in the tutorial one in general.
Videos tutorial 2-1 and tutorial 2-2
10. FAQs
a. Inside the above function OnIsPermitted, what about authentication methods amTrusted and amIntegrated?
First of all, SocketPro defines the method amTrusted, but it does not support it at this time.
In regards to the authentication method amIntegrated, SocketPro does authentication inside for you automatically. Password is immediately cleaned after SSPI authentication. The above virtual function is an event only. The failure or success of authentication does not depend on your code at all. Also, you can’t retrieve a password because the password is already cleaned before this function is called.
b. Looking at your tutorial sample code, you use the following code to ask for a service. Could you elaborate a bit more about the code comment?
//when switch from sidStartup to another, false is a must.
m_ClientSocket.Commit(false);
This is a good question though. It is related how to batch a set of requests at client side.
When you call the method Commit with true, internally object USocket will insert two requests idStartBatching and idCommitBatching automatically to inform a SocketPro server when a returned result should be started to batch and when the batching should be ended. This is fine with all of services except the service sidStartup. This particular service is an initial service that only supports one request idSwitchTo. All of other requests or strange ones will cause immediately aborting the established socket connection. When you set true here, you will certainly close the socket connection. If you commit a batch with input false, object USocket will not insert the two requests. This is a design of SocketPro and not a problem. This design will enforce that a client must call IUSocket::SwicthTo first after a socket connection is established without exception. Once a client calls the method, SocketPro will automatically retrieve a user id and password for validation. If the validation is fine, the client can do anything else. Otherwise, the socket will be closed immediately for the sake of security.
c. Dotnet remoting and DCOM also have callbacks support. Your SocketPro also has these events and notifications. What difference among them?
At the very beginning, maybe you don’t know the difference and think they are very similar on the surface. However, they function very different internally. SocketPro differs from the other two in three aspects.
SocketPro is much faster than other three. At server side, dotNet remoting and DCOM must wait a returned result from a client, but SocketPro does not, and just posts a message into local buffers and returns immediately. When a server supports tens of thousands of clients, SocketPro will be very fast in comparison to others because DCOM and dotNet remoting have to wait all of callback returned results one by one. You can implement callback from WCF with similar implementation of SocketPro. However, WCF is slower than SocketPro because SocketPro is created with push design for callack. In many cases, you don’t have to do calbacks at all while you are still able to achieve callback effects.
SocketPro is more reliable than the other two. Considering one or two clients suddenly power off, SocketPro will still function correctly, but WCF, dotNet remoting and DCOM will either throw an exception or hang.
SocketPro does not have firewall and NAT devices problem at all. DotNet remoting may have a problem with these network devices.
d. Can I use SocketPro to write an application with security features that MS Internet Information Server has? For example, can I do anonymous access, authenticated access, impersonation, IP address and domain name restrictions?
Yes! You can do all of the above ones easily inside the virtual function OnIsPermitted of CSocketProServer, when you set authentication method to either amOwn or amMixed