Professional Documents
Culture Documents
http://www.codeguru.com/csharp/csharp/cs_network/sockets/article.php/c7695
Objective
Theobjectiveof this articleis to demonstrate asocket-based client/server application that will allow two-wayasynchronous communication between aserver and multiple client applications. Because this exampleuses asynchronous methods, the serverapplication does not usethreads to communicate to multiple clients (although internallytheasynchronous communication mechanism uses threads at the OSlevel).
SocketClass
TheSocket class (System.Net.Sockets.Socket) provides aset ofsynchronous and asynchronous methods for synchronous orasynchronous communication. As per the.NET namingconvention, allthe asynchronous methodnames are created byprefixingthe words "Begin"or"End"to thename ofthe synchronousmethods. Themethods prefixed with "Begin"and"End"represent a pair ofasynchronous methods correspondingto a single synchronous method, asshown in the followingtable.
ExampleApplication
The exampleshown in this article has two classes,oneimplementing the Socket Server and the other implementingtheSocket Client.
SocketServerImplementation
TheSocket Server application is implemented in theSocketServer class (file name SocketServer.cs). This class has amain Socket object (m_mainSocket) andan arrayof worker Socket objects (m_workerSocket) as members. Themain Socket object does the listeningfor the clients. Oncea clientis connected, themain Socket transfers theresponsibilityto process the transactions related to that particular client toaworker Socket. Then, themain Socket goes back andcontinueslisteningforother clients. BeginAccept() andBeginReceive() arethe two important methods in theSocket class used by the Socket Serverapplication. TheBeginAccept()method has the followingsignature:
publicIAsyncResultBeginAccept( AsyncCallbackcallback, objectstate ); //(1)Function tocallwhena client isconnected //(2)Stateobjecttopreserve socket // info //
Essentially, after callingtheListen()method ofthemain Socket object,youcallthis asynchronous method and specifyacallbackfunction (1), whichyou designated to do the furtherprocessingrelatedto the client connection.Thestateobject (2) can benullin this particular instance. Because this is an asynchronous method, it will return immediatelyand theserver main thread is freeto process other events. Behind thescenes,aseparate thread willstart listeningon that particular socket forclient connections. When aclient requestsa connection, the callback functionyou specifiedwillbeinvoked. Inside the callback function (in the example, the function is named "OnClientConnect()"),you willdo further processingrelated to the clientconnection.
publicvoidOnClientConnect(IAsyncResultasyn) { try { //Herewecomplete/endtheBeginAccept()asynchronouscall //bycallingEndAcc ept()-whichreturnsthereferenceto //anewSocketobject m_workerSocket[m_clientCount]=m_mainSocket.EndAccept(asyn); //LettheworkerSocketdothefurtherprocessingforthe //justconnectedclient WaitForData(m_wor kerSocket[m_clientCount]); //Nowincrementtheclientcount ++m_clientCount; //Displaythisclientconnectionasasta tusmessageontheGUI Stringstr=String.Format("Client#{0}connected", m_clientCount); textBoxMsg.Text=str; //SincethemainSocketisnowfree,itcangobackandwait //forotherclientswhoareattemptingtoconnect m_mainSocket.BeginAccept(newAsyncCallback (OnClientConnect ),null); } catch(ObjectDisposedException) { System.Diagnostics.Debugger.Log(0,"1", "\nOnClientConnection: Sockethasbeenclose d\n"); } catch(SocketExceptionse) { MessageBox.Show(se.Message); } }
The first thingyou do inside the"OnClientConnect()" function is to calltheEndAccept() method on the m_mainSocket memberobject, which will return areferenceto another socket object. You set this object referenceto oneof themembers ofthe arrayof Socket object referencesyou have (m_workerSocket) andalso increment theclient counter. Now, because you haveareferenceto anew socket object that now can do thefurthertransaction with the client, themain Socket (m_mainSocket) is free; hence,you will callits BeginAccept() method again to start waitingforconnection requestsfromother clients.
On theworker socket,you useasimilar strategyto receivethe datafrom the client.In placeof calling BeginAccept()and EndAccept(), hereyoucallBeginReceive() andEndReceive(). This, in anutshell, is theSocket Server implementation. Whileyouaresendingout datato the clients, theserver simplyuses thespecificworkersocket objects to send datato eachclient.
SocketClientImplementation
(Full SizeImage) TheSocket Client application is implemented in theSocketClient class (filename SocketClient.cs). Compared to theserver whereyou haveamain Socket and an arrayof worker Sockets, hereyouonlyhaveasingle Socket object (m_clientSocket). Thetwo important methods in Socket class used bythe Socket Client application arethe Connect() andBeginReceive() methods. Connect() is asynchronous method and is called to connect to aserver that is listeningfor clientconnections. Because this callwillsucceed/fail immediately, depending on whetherthereis an activeserver listeningor not at the specified IPand Port number, asynchronous method is okayfor this purpose. Onceaconnection is established,you callthe BeginReceive() asynchronousfunction to wait for anysocket write activitybythe server. Here, ifyou callasynchronousmethod, the main thread on the clientapplication willblock andyouwillnot be able to send anydata to the serverwhile the client iswaitingfor data from theserver. When thereis anywriteactivityon the socket from theserverend, the internal thread started byBeginReceive() will invokethe callback function ("OnDataReceived()" in this case), which willtake careof the furtherprocessingof thedata written bythe server. When sendingthe datato theserver,you justcallthe Send() method on them_clientSocket object, which will synchronouslywritethe data tothe socket. That is all thereis forasynchronous socketcommunication usingmultiple clients.
Limitations/PossibleImprovements
y y
Up to 10 simultaneous clients aresupported.Youcan easilymodifyand support unlimited number of clients byusing aHashTableinstead of an array. Forsimplicity, when theserver sends out amessage, itis broadcast toallthe connectedclients. This could easilybemodified to send messages to specific clients byusingthe Socket object pertainingto that particular client. When a client is disconnected, proper action is not taken; the client count is not decremented. Thecorrectwaywould beto reuse or releaseresources forother client connections.
Acknowledgement
Even though thecontentofthis article is independentlydeveloped, theexampleprogram used is influenced bythe articleon Socket Programmingin C# byAshish Dhar.
Updateaddedon03/01/2005
Foramore comprehensive example covering topics suchas threadsynchronization, pleasesee Part IIofthisarticle.
About theAuthor
JayanNairis a Senior SoftwareEngineerwith 11+years of experienceworkingwith cutting edgesoftwaretechnologies. Currentlyheis developingthe next generation software applications forthe telecommnunications testingindustry. Jayan's passions: Object Oriented softwaredesignand developingreusable software components. His motto:"if thesoftware you write is not reusable,you arenot writing software, buthardwareinstead". Jayan finished his Masters degreein Computer Sciencefrom VirginiaTech,Blacksburg,VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. Heis also a Sun Certified Programmer for theJavaPlatform (SCPJ). You can contact him at jnair1998@hotmail.com.
Downloads
yasync_client_server_exe.zip yasync_client_server_src.zip
Requested FeaturesAdded
This exampleincludes modifications to support the following features: 1. 2. 3. 4. 5. 6. Howto support an unlimited number of clients Howto find which clientsent aparticular message Howto replyor send messages to specificclients Howto find when aparticular client is disconnected Howto get the list of allconnectedclients at anygiven time Arevariables safein AsyncCallback methods?What about thread synchronization?[Updated on 02/01/05]
Other Enhancements
1. On theserver andclient code, the receivebuffer sizeis increased to 1024 instead of a single byte for moreefficiency. 2. Cleanup codeis added aftera client is disconnected. Screen shot of Socket Server:
Inside theWaitForData() function,you willmakethe actual asynchronouscallto receive the data from theclient as shown below:
publicvoidWaitForData(System.Net.Sockets.Socketsoc, intclientNumber) { try { if(pfnWorkerCallBack==null) { //Specifythecallbackfunctionthatistobeinvokedwhen //thereisanywriteactivitybythe connectedclient pfnWorkerCallBack=newAsyncCallback(OnDataReceived); } SocketPackettheSocPkt=newSocketPacket(soc,clientNumber); //Startreceivinganydatawrittenbytheconnectedclient //asynchronously soc.BeginReceive(theSocPkt.dataBuffer,0, theSocPkt.dataBuffer.Length, SocketFlags.None,
In theabovecode, the user-defined class SocketPacketis themostcriticalitem. Asyou can see, an object of this class isthe last parameter passed to the asynchronousfunction call BeginReceive(). This object cancontain anyinformation thatyou find useful;itcan beused later, whenyouactuallyreceive thedatafrom theclient. You send (1) theworker socket object and (2)theindexnumberof the client packaged insidethis object. You willretrieve them back whenyouactuallyreceivethe datafromaparticular client. Given below is the definition ofthe SocketPacketclass.
publicclassSocketPacket { //ConstructorthattakesaSocketandaclientnumber publicSocketPacket(System.Net.Sockets.Socketsocket, intclientNumber) { m_currentSocket=socket; m_clientNumber =clientNumber; } publicSystem.Net.Sockets.Socketm_currentSocket; publicintm_clientNumber; //Buffertostorethedatasentbytheclient publicbyte[]dataBuffer= newbyte[1024]; }
In theabovecode, the SocketPacketclass containsthe referenceto a socket,adata buffer of size1024 bytes, andaclient number. This client numberwillbe availablewhenyou actually start receivingdata fromaparticular client. Byusingthis client number,you can identify which client actuallysentthe data. To demonstrate this in the example code, the server will echo back to theclient (after convertingto uppercase)the received message, usingthe correct socket object.
HowtoReplyorSend MessagestoSpecificClients
You might havefiguredoutthis already. This is verysimple to implement. Because the SocketPacket objectcontains the referenceto a particular worker socket,you just use that object to replyto the client. Additonally,you alsocould send anymessageto anyparticular client byusingthe worker socket object stored in theArrayList.
HowtoFindwhenaParticularClientis Disconnected
This is a bit harder to address. Theremaybeotherelegant ways to do this, but hereis a simple way. When a client is disconnected, therewillbea final callto the OnDataReceived() function.If nothingin particular is done, this callwillthrowaSocketException. Whatyou can do hereis to look inside this exception and seewhetherthis was triggeredbythe"disconnection"of a client. Forthis,you willlook at the error codeinsidethe exception object and seewhether it corresponds to 10054.Ifso,you willdo the required action correspondingto the client
disconnection. Hereagain, theSocketPacket object will giveyou the indexnumberof the client that was disconnected.
catch(SocketExceptionse) { if(se.ErrorCode==10054) { stringmsg="Client"+socketData.m_clientNumber+ "Disconnected"+ "\n"; richTextBoxReceivedMsg.AppendText(msg); //Removethereferencetothe workersocketoftheclosed //clientsothatthisobjectwillgetgarbagecollected m_workerSocketList[socketData.m_clientNumber -1]=null; UpdateClientList(); } else { MessageBox.Show(se.Message); } }
//ErrorcodeforConnectionreset //bypeer
In theabovepicture, the item labeled (1)is themain GUIthread that startswhenyou start the Server application. Thethread labeled (2)starts wheneveranyclient tries to connect to the socket. Thethread labeled (3)spawns when thereis anywrite activitybyanyoneof the connectedclients. In theexample code, theasynchronous functionsOnClientConnect() andOnDataReceived() arecalled bythreads other than the main GUIthread. Anyother functions called insidethese two functions are also invoked bythreads other than the main GUIthread. Threading issues to consider 1. Sharedvariables Anyshared variables thatyou modifyinsidethe shared codementioned abovemustbe protected bysynchronization structures.In this example, the shared variablesyou modifywithin the sharedcode arem_clientCountandm_workerSocketList. You can useverysimplestrategies to protect thesevariables. Them_clientCount variable is an integer variable and hencecan beincremented byusingthe static method within the Interlockedclass as shown below:
//Nowincrementtheclientcountforthisclient //inathreadsafemanner Interlocked.Increment(ref m_clientCount);
Similarly,youcan protect them_workerSocketListmember variable from modification bymultiple threads at the sametime, bycreatingaSynchronized ArrayListas shown below:
privateSystem.Collections.ArrayListm_workerSocketList=
ArrayList.Synchronized(newSystem.Collections.ArrayList());
2. Modifying theGUI Themain GUIthread actuallyowns theGUIcontrols. Hence, in production code, itis not recommended or advisableto access or modifyanyof theGUIcontrols bythreads otherthan themain thread. Whenyou need to updatethe GUI,you should makethe main thread do itforyouas shown in the followingcode:
//Thismethodcouldbecalledbyeitherthemainthreador //anyoftheworkerthreads privatevoidAppendToRichEditControl(stringmsg) { //Checktoseeifthis methodiscalledfromathread //otherthantheonecreatedthecontrol if(InvokeRequired) { //WecannotupdatetheGUIonthisthread. //AllGUIcontrolsaretobeupdatedbythemain(GUI) //thread. //Hence,wewillusetheinvokemethodonthecontrol //thatwillbecalledwhentheMainthreadisf ree //DoUIupdateonUIthread object[]pList={m sg}; richTextBoxReceivedMsg.BeginInvoke(new UpdateRichEditCallback(OnUpdateRichEdit),pList); } else { //Thisisthemainthreadwhichcreatedthiscontrol, //henceupdateitdirectly OnUpdateRichEdit(msg); } } //ThisUpdateRichEditwillberunbackontheUIthread //(usingSystem.EventHandlersignaturesowedon't //needtodefineanewdelegatetypehere) privatevoidOnUpdateRichEdit(stringmsg) { richTextBoxReceivedMsg.AppendText(msg); }
Acknowledgement
PartIIof this articlewasdeveloped to address thequestions and comments I received from readers after publishing PartIof this article. Theexampleprograms used in PartIwhich are further enhanced and extended forPartII,areinfluenced bythearticleon SocketProgrammingin C# byAshish Dhar.
FinalComments
Networkprogrammingisaveryinterestingtopic.TheC# languageprovidesyou with all the tools necessaryto quicklydevelop networked applications. Compared to C++, Java and C# havea richer set of programmingAPIs, which will eliminatemostof the complexities previouslyassociated with networkprogramming.This exampleincorporates all the features
that the readers requested. Even then, use this exampleonlyas a learningtool. Asyou learn more about socket programming,youwillrealizethe necessityto add thread synchronization constructs as wellseetheopportunities for furtheroptimization,to solvethe problem atyour hand. Good luck withyourlearningand thanks for reading.
About theAuthor
JayanNairis a Senior SoftwareEngineerwith 11+years of experienceworkingwith cutting edgesoftwaretechnologies. Currentlyheis developingthe next generation software applications forthe telecommnunications testingindustry. Jayan's passions: Object Oriented softwaredesignand developingreusable software components. His motto: "if thesoftware you write is not reusable,you arenot writing software, buthardwareinstead". Jayan finished his Masters degreein Computer Sciencefrom VirginiaTech,Blacksburg,VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET anddistributed applications. Heis also a Sun Certified Programmer for theJavaPlatform (SCPJ). You can contact him at jnair1998@hotmail.com.
Downloads
yasync_client_server_II_exe.zip yasync_client_server_II_src.zip