//SocketInterface.h #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 #ifndef CSocketClienth #define CSocketClienth /* Don't specify a "Windows Sockets Application" when creating your Project with AppWizard for any of these classes - there's no need :-) Windows Sockets allow you to get data between networked computers whether the network is a LAN, WAN or the Internet. Even the simplest examples I've seen elsewhere are too difficult for casual use, and cause people problems when trying to use Sockets in separate Threads. Windows Sockets can get as difficult as you'd like to make them... it took about two months to evolve these classes. I've tried to see through the fog and create the simplest useful classes that I can. Ultimately they send and receive data through CArchives. This is discussed later. CSocketClient lets you create a Client Application. That means that your Application talks and listens to exactly one other Computer which the user (or code) has to specify by IP address (or Computer Name) and TCP Port. This may be a one to one, Peer to Peer arrangement, or your application may be a Client which is talking to a Server which is handling thousands of other Clients at the same time. Each CSocketClient instance makes one Socket connection. You'd normally use Send("Msg"); and Msg=GetReply(); to send and receive Messages. CSocketServer and CConnection allow your Application to talk and listen to many other computers at the same time. This is Server or full Peer to Peer behaviour. CSocketServer listens for conections on a particular TCP Port. It does this in a separate Thread, so your Application can carry on with other work. When a Client requests a Connection, an instance of CConnection is created and added to a list of classes held by CSocketServer. CConnection is derived from CSocketBase and behaves in a similar fashion to CSocketClient except that each instance of CConnection is running in a separate Thread. This means that if one instance of CConnection hangs for some reason, the Server should continue to function. At this point I would like to mention I/O Completion Ports, scalability and resources. This code does not use I/O Completion Ports. If you are developing an application which will ultimately have thousands of connections then you need to use I/O Completion Ports and/or many servers. I/O Completion Ports use a pool of 50 or more Worker Threads... If you have a peak of fewer than 50 simultaneous Connections then this code (using one Thread per Connection) will use fewer System Resources and be far faster to code! By default the Port is 14 which not a Standard Port (There shouldn't be anything else listening on this Port). Ports over 1024 are usually free for new Applications. Standard Ports are listed in your Windows\system32\drivers\etc\SERVICES file. You may get a TRACE:"Warning - Attempt made to seek on a CSocketFile" from these routines: it's just the CArchives closing down. Be aware that if the socket uses an Internet Connection it is subject to the same restrictions as e-mails and Web Pages: Binary data may be altered by Routers, so it should be sent using UU, Base64 or Quoted Printable encoding. If you are sending text with a few strange characters, use Quoted Printable. If you are sending Binary Data use Base64 or an equivalent of your own design (UU encoding is less efficient). I've read that CArchive remembers every object it saves (so that it only saves a reference if it gets the same object twice)... I'm not sure that this is true for CArchives linked to CSocketFiles... But just incase, stop this behaviour (which is obviously wrong for a Socket transmission) by using the following instead of CArchive if you're sending Objects through CArchive: class CSocketArchive : public CArchive { public: CSocketArchive(CFile* pFile, UINT nMode, int nBufSize=4096) : CArchive(pFile, nMode, nBufSize) {} void ClearIndex() { if(IsStoring()) { if(m_nMapCount>1) { m_pStoreMap->RemoveAll(); m_pStoreMap->SetAt(NULL,(void*)(DWORD)0); m_nMapCount=1; } }else while(m_nMapCount-->1) m_pLoadArray->RemoveAt(m_nMapCount); } }; --------------------------------------- CSocketClient ============= This is the standard Client class. It allows you to connect to a Port on a specific Server; Call Send(...) to start a conversation; When you expect a reply use GetReply(): CSocketClient Chat("127.0.0.1", 23); Chat.Send("Hi"); if(Chat.GetReply()=="bye") Chat.Disconnect(); else Chat.Send("bye"); If the Server starts a conversation, the overridable function OnReceive() is called from where you may use GetReply() and Send(...) again. Here's a Chat Server Client example: Create a Dialog Application with a ListBox to print messages in, an EditBox to Type Messages in, and a [Send] Button. The GetReply and Send overrides alter the default behaviour from sending CStrings to ordinary strings. Making the Client interchangable with a Telnet session. #include "SocketInterface.h" class CClientDlg : public CDialog { ... public: class CChatClient : public CSocketClient { CClientDlg* Dlg; void OnReceive() {Dlg->Say(GetReply());} void OnClose () {Dlg->Disconnect();} CString GetReply() { CString S; ArI->ReadString(S); // It'll wait here for as long as the connection is OK. If the connection is broken it returns an empty string. return S; } public: void Send(const CString& Msg) {ArO->WriteString(Msg+"\r\n"); ArO->Flush();} CString Connect(const CString& RemoteIP, BYTE Port, CClientDlg* _Dlg) {Dlg=_Dlg; return CSocketClient::Connect(RemoteIP,Port);} } Chat; void Say(const CString& Msg) {if(::IsWindow(m_hWnd)) ((CListBox*)GetDlgItem(IDC_LIST1))->AddString(Msg);} void Disconnect() { Say("Disconnected"); GetDlgItem(IDC_Send)->EnableWindow(FALSE); } ... }; BOOL CClientDlg::OnInitDialog() { CDialog::OnInitDialog(); CString S(Chat.Connect("127.0.0.1",23, this)); //Note that you can't connect to yourself: You'll need to be running a Server on this PC to get a connection on 127.0.0.1 if(!S.IsEmpty()) Say(S); // Report any Error Messages return TRUE; // return TRUE unless you set the focus to a control } //When a Send Button is clicked, the text from an Edit Box (IDC_EDIT1) is sent. void CClientDlg::OnSend() { CString S; GetDlgItemText(IDC_EDIT1, S); Chat.Send(S); } --------------------------------------- CSocketServer ============= Start by deriving a class from CConnection and overriding the virtual functions to encapsulate the behaviour you want for one Connection. Since Telnet is a standard application on Windows, we can make a Telnet Chat Server as an example. The Server will accept connections from Telnet or from the client described above using CSocketClient. Create a Dialog Application with a ListBox to print messages in, an EditBox to Type Messages in, and a [Broadcast] Button. The Server needs a pointer to the Dialog Box to give textual output to be displayed in a ListBox. Derive a class from CSocketServer called CChatServer which simply holds a pointer to the Dialog and provides a Say(...) function that will add text to a List Box: #include "SocketInterface.h" class CChat; // <- forward references class CServerDlg; class CChatServer : public CSocketServer { CServerDlg* Dlg; public: CChatServer(BYTE Port, CServerDlg* Dlg) : CSocketServer(Port), Dlg(Dlg) {} void Say(const CString& Msg); protected: virtual CConnection* New(); }; void CChatServer::Say(const CString& Msg) {Dlg->Say(Msg);} CConnection* CChatServer::New() {return new CChat(this);} class CServerDlg : public CDialog { CChatServer* Chat; public: CServerDlg(CWnd* pParent=NULL) : CDialog(CServerDlg::IDD, pParent), Chat(0) {} // standard constructor virtual ~CServerDlg(); void Say(const CString& Msg) {if(::IsWindow(m_hWnd)) ((CListBox*)GetDlgItem(IDC_LIST1))->AddString(Msg);} ... }; After the Dialog class is the Handler class, derived from CConnection. This provides all the functionality for one Chat Server Connection. GetReply and Sender alter the default behaviour from sending CStrings to ordinary strings. class CChat : public CConnection { void Say (const CString& Msg) {((CChatServer*)GetServer())->Say(ID+Msg);} void Broadcast(const CString& Msg) {Say(Msg); ((CChatServer*)GetServer())->Broadcast(ID+Msg);} CString ID; CString GetReply() { CString S; try { ArI->ReadString(S); // It'll wait here for as long as the connection is OK. If the connection is broken it returns an empty string. }catch(...) {return '¿'+sConnectionBroken;} return S; } public: CChat(CSocketServer* Me) : CConnection(Me) {} protected: void Sender(const CString& Msg) {ArO->WriteString(Msg+"\r\n"); ArO->Flush();} void OnDisconnect() {Broadcast("Disconnected");} void OnConnect() { ID.Format("%s(%i): ", RemoteIP, m_hSocket); Broadcast("Connected"); Send("You will appear as "+ID); } void OnReceive() { CString S(GetReply()); if(*S=='¿') {Say(S.Mid(1)); return;} if(!S.CompareNoCase("Bye")) Disconnect(); Broadcast(S); } }; Create the server in OnInitDialog, destroy it in the destructor and create a Button labeled [Broadcast] which sends some text in an Edit Box to all connections: BOOL CServerDlg::OnInitDialog() { CDialog::OnInitDialog(); Chat=new CChatServer(23,this); // C:\Windows\System32\drivers\etc\SERVICES file lists Telnet as TCP port 23 return TRUE; // return TRUE unless you set the focus to a control } CServerDlg::~CServerDlg() {try{delete Chat;}catch(...){}} void CServerDlg::OnBroadcast() { CString S; GetDlgItemText(IDC_EDIT1, S); Say("Broadcasted: "+S); Chat->Broadcast("Server: "+S); } The CConnection will make the CSocketServer hold a list of CChats. If the Server didn't need to give any visual feedback (a if running as a Service for example) you put the following in your Application class InitInstance(): #include "Telnet.h" BOOL CMyApp::InitInstance() { CSocketServer(23); // C:\Windows\System32\drivers\etc\SERVICES file lists Telnet as TCP port 23 ... } So you will now have an application which runs a Server listening to TCP port 23 which will create a new instance of CChat for each new Connection. When this application is running, you can open Telnet: [Start Menu][Programs][Accessories][Telnet][File Menu][Remote System...][Host Name]127.0.0.1[Connect] Then type Hi and press return, then bye and press return to watch the session work. Incedentally, 127.0.0.1 is the IP Address used to refer to the local computer regardless of the IP Addresses of any network cards it may have. CArchive and CString: Usually you should be able to just use CStrings to hold data using the << and >> operators which send and use the data length before the string data. You can't use CArchive::ReadString and CArchive::WriteString to transfer data because they scan the data looking for '\n' and decide that's the end of the string; the data length is not sent. You can run into confusion when holding data in CStrings if you use any functions (like fputs) which look for a Null Terminator instead of using the data Length. It is possible to use the CArchive::Write or CArchive::WriteString but you must be careful in the Server because data is sent in a queue and direct accesses like this won't be using the queue... Send appends the message to the Message Queue. The Message Queue doesn't get processed until you return from OnReceive(). In this example: void OnReceive() { Send("Hello"); ArO << MyObject; } Hello will be sent _after_ MyObject! Use Sender("Hello") or ArO << CString("Hello") << MyObject; to send Messages with objects. */ #include // MFC socket extensions #include "Thread.h" //For CLock #ifndef Languageh static const CString sNoSocket2DLL ("Couldn't find a usable WinSock DLL"); static const CString sNoSockets ("Windows sockets initialization failed"); static const CString sNoSocket ("Couldn't Create Communication Socket: "); static const CString sNoSocketConnect ("Communications Socket couldn't connect: "); static const CString sConnectionBroken ("Unexpected disconnection"); static const CString sAborted ("Aborted"); //The standard Socket errors: static const CString sWSAEINTR ("Interrupted function call"); static const CString sWSAEBADF ("Invalid Socket Descriptor"); static const CString sWSAEACCES ("Permission denied"); static const CString sWSAEFAULT ("Bad address"); static const CString sWSAEINVAL ("Invalid argument"); static const CString sWSAEMFILE ("Too many open files"); static const CString sWSAEWOULDBLOCK ("Resource temporarily unavailable"); static const CString sWSAEINPROGRESS ("Operation now in progress"); static const CString sWSAEALREADY ("Operation already in progress"); static const CString sWSAENOTSOCK ("Socket operation on non-socket"); static const CString sWSAEDESTADDRREQ ("Destination address required"); static const CString sWSAEMSGSIZE ("Message too long"); static const CString sWSAEPROTOTYPE ("Protocol wrong type for socket"); static const CString sWSAENOPROTOOPT ("Bad protocol option"); static const CString sWSAEPROTONOSUPPORT("Protocol not supported"); static const CString sWSAESOCKTNOSUPPORT("Socket type not supported"); static const CString sWSAEOPNOTSUPP ("Operation not supported"); static const CString sWSAEPFNOSUPPORT ("Protocol family not supported"); static const CString sWSAEAFNOSUPPORT ("Address family not supported by protocol family"); static const CString sWSAEADDRINUSE ("Address already in use"); static const CString sWSAEADDRNOTAVAIL ("Cannot assign requested address"); static const CString sWSAENETDOWN ("Network is down"); static const CString sWSAENETUNREACH ("Network is unreachable"); static const CString sWSAENETRESET ("Network dropped connection on reset"); static const CString sWSAECONNABORTED ("Software caused connection abort"); static const CString sWSAECONNRESET ("Connection reset by peer"); static const CString sWSAENOBUFS ("No buffer space available"); static const CString sWSAEISCONN ("Socket is already connected"); static const CString sWSAENOTCONN ("Socket is not connected"); static const CString sWSAESHUTDOWN ("Cannot send after socket shutdown"); static const CString sWSAETOOMANYREFS ("WSAETOOMANYREFS"); static const CString sWSAETIMEDOUT ("Connection timed out"); static const CString sWSAECONNREFUSED ("Connection refused"); static const CString sWSAELOOP ("WSAELOOP"); static const CString sWSAENAMETOOLONG ("WSAENAMETOOLONG"); static const CString sWSAEHOSTDOWN ("Host is down"); static const CString sWSAEHOSTUNREACH ("No route to host"); static const CString sWSAENOTEMPTY ("WSAENOTEMPTY"); static const CString sWSAEPROCLIM ("Too many processes"); static const CString sWSAEUSERS ("WSAEUSERS"); static const CString sWSAEDQUOT ("WSAEDQUOT"); static const CString sWSAESTALE ("WSAESTALE"); static const CString sWSAEREMOTE ("WSAEREMOTE"); #endif //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> CSocketBase <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // Holds User and Port information class CSocketBase : public CSocket { protected: static CString UserName; static CString HostName; static CString LocalIP; CString RemoteIP; BYTE Port; CSocketFile* File; CArchive* ArI; CArchive* ArO; public: CSocketBase(const CString& RemoteIP, BYTE Port=14) : RemoteIP(RemoteIP), Port(Port), File(0), ArI(0), ArO(0) {} CSocketBase() : Port(14), File(0), ArI(0), ArO(0) { if(UserName.IsEmpty()) { // Save looking up the data every time DWORD Len=1024; ::GetUserName(UserName.GetBufferSetLength(Len),&Len); UserName.ReleaseBuffer(); try { WORD wVersionRequested=MAKEWORD(2,0); WSADATA wsaData; /* Confirm that the WinSock DLL supports 2.0. Note that if the DLL supports versions greater than 2.0 in addition to 2.0, it will still return 2.0 in wVersion since that is the version we requested. */ if(WSAStartup(wVersionRequested, &wsaData) || wsaData.wVersion!=MAKEWORD(2,0)) {AfxMessageBox(sNoSocket2DLL); return;} if(!gethostname(HostName.GetBufferSetLength(256), 256)) { HostName.ReleaseBuffer(); hostent* lpHostEnt=gethostbyname(HostName); if(lpHostEnt) { LPSTR lpAddr=lpHostEnt->h_addr_list[0]; if(lpAddr) { in_addr inAddr; memmove(&inAddr, lpAddr, 4); LocalIP=inet_ntoa(inAddr); } } } }catch(...) {} WSACleanup(); } } CString GetUserName() const {return UserName;} CString GetHostName() const {return HostName;} CString GetLocalIP () const {return LocalIP ;} CString GetRemoteIP() const {return RemoteIP;} void SetUserName(const CString& User) {UserName=User;} void SetHostName(const CString& Host) {HostName=Host;} void SetLocalIP (const CString& IP) {LocalIP =IP;} void SetRemoteIP(const CString& IP) {RemoteIP=IP;} void SetPort(BYTE TCPPort=14) {Port=TCPPort;} // You will have to stop and re-start Listening(...) to listen to the new port. //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Data Transfer Functions <<<<<<<<<<<<<<<<<< virtual CString GetReply() { // Override, for example, to use ArI->ReadString(S); instead. CString S; try { *ArI >> S; // It'll wait here for as long as the connection is OK. If the connection is broken it returns an empty string. }catch(...) {return '¿'+sConnectionBroken;} return S; } void Disconnect() { try{delete ArI;} catch(...){} ArI=0; try{delete ArO;} catch(...){} ArO=0; try{delete File;} catch(...){} File=0; Close(); } private: //CAsyncSocket Overrides: void OnClose (int nErrorCode) {OnClose();} void OnReceive(int nErrorCode) { if(!ArI) return; do OnReceive(); while(!ArI->IsBufferEmpty()); } protected: //Override these two: virtual void OnReceive() {char b; *ArI >> b;} //by default it just throws input data away. virtual void OnClose () {} // Called when disconnected by the Server/Network etc. public: static CString GetLastSocketError() { switch(WSAGetLastError()) { case WSAEINTR : return sWSAEINTR; case WSAEBADF : return sWSAEBADF; case WSAEACCES : return sWSAEACCES; case WSAEFAULT : return sWSAEFAULT; case WSAEINVAL : return sWSAEINVAL; case WSAEMFILE : return sWSAEMFILE; case WSAEWOULDBLOCK : return sWSAEWOULDBLOCK; case WSAEINPROGRESS : return sWSAEINPROGRESS; case WSAEALREADY : return sWSAEALREADY; case WSAENOTSOCK : return sWSAENOTSOCK; case WSAEDESTADDRREQ : return sWSAEDESTADDRREQ; case WSAEMSGSIZE : return sWSAEMSGSIZE; case WSAEPROTOTYPE : return sWSAEPROTOTYPE; case WSAENOPROTOOPT : return sWSAENOPROTOOPT; case WSAEPROTONOSUPPORT: return sWSAEPROTONOSUPPORT; case WSAESOCKTNOSUPPORT: return sWSAESOCKTNOSUPPORT; case WSAEOPNOTSUPP : return sWSAEOPNOTSUPP; case WSAEPFNOSUPPORT : return sWSAEPFNOSUPPORT; case WSAEAFNOSUPPORT : return sWSAEAFNOSUPPORT; case WSAEADDRINUSE : return sWSAEADDRINUSE; case WSAEADDRNOTAVAIL : return sWSAEADDRNOTAVAIL; case WSAENETDOWN : return sWSAENETDOWN; case WSAENETUNREACH : return sWSAENETUNREACH; case WSAENETRESET : return sWSAENETRESET; case WSAECONNABORTED : return sWSAECONNABORTED; case WSAECONNRESET : return sWSAECONNRESET; case WSAENOBUFS : return sWSAENOBUFS; case WSAEISCONN : return sWSAEISCONN; case WSAENOTCONN : return sWSAENOTCONN; case WSAESHUTDOWN : return sWSAESHUTDOWN; case WSAETOOMANYREFS : return sWSAETOOMANYREFS; case WSAETIMEDOUT : return sWSAETIMEDOUT; case WSAECONNREFUSED : return sWSAECONNREFUSED; case WSAELOOP : return sWSAELOOP; case WSAENAMETOOLONG : return sWSAENAMETOOLONG; case WSAEHOSTDOWN : return sWSAEHOSTDOWN; case WSAEHOSTUNREACH : return sWSAEHOSTUNREACH; case WSAENOTEMPTY : return sWSAENOTEMPTY; case WSAEPROCLIM : return sWSAEPROCLIM; case WSAEUSERS : return sWSAEUSERS; case WSAEDQUOT : return sWSAEDQUOT; case WSAESTALE : return sWSAESTALE; case WSAEREMOTE : return sWSAEREMOTE; default: return ""; } } }; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> CSocketClient <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< class CSocketClient : public CSocketBase { public: CSocketClient() {} virtual ~CSocketClient() {try{Disconnect();}catch(...){}} CSocketClient(const CString& RemoteIP, BYTE Port=14) {Connect(RemoteIP,Port);} CString Connect(const CString& _RemoteIP, BYTE _Port=14) { if(ArO || (m_hSocket!=INVALID_SOCKET)) Disconnect(); if(!AfxSocketInit() //Put here to allow multithreaded use (You won't need it in your Application class). If it asserts in AfxSocketInit you're using statically linked multithreaded MFC Version 6... You need VS Servicepack 3 or the fix from http://support.microsoft.com/support/kb/articles/Q193/1/01.asp || !Create()) return sNoSocket+GetLastSocketError(); RemoteIP=_RemoteIP; Port=_Port; if(!CSocket::Connect(RemoteIP, Port)) return sNoSocket+GetLastSocketError(); try { File=new CSocketFile(this); ArI=new CArchive(File, CArchive::load); ArO=new CArchive(File, CArchive::store); }catch(...) { Disconnect(); return sConnectionBroken; } return ""; } CString Send(const CString& Msg="") { try { *ArO << Msg; ArO->Flush(); // If you forget the +"\r\n" or to Flush, the recipient appears to hang, waiting for the rest of the message. return ""; }catch(...) {return sConnectionBroken;} } }; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> CSocketServer <<<<<<<<<<<<<<<<<<<<<<<<<<< For multiple Listening sockets. You'll need to derive a class from CSocketServer and provide a New() function. */ class CConnection; class CSocketServer : public CSocketBase { friend class CConnection; CLock Lock; volatile CConnection* Sockets; void Empty(); public: CSocketServer(BYTE Port=14) : Sockets(0) { if(!AfxSocketInit()) AfxMessageBox(sNoSockets); //Put here to allow multithreaded use (You won't need it in your Application class) else if(Create(Port)) Listen(); } virtual ~CSocketServer() {try{Empty();}catch(...){}} void OnAccept(int nErrorCode) { //This gets called when a new connection is requested by a client: CSocket Socket; if(Accept(Socket)) { CWinThread* pThread=AfxBeginThread((AFX_THREADPROC)ThreadProc, (LPVOID)this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); hSocket=Socket.Detach(); //Have to do this for Threaded Sockets to work. pThread->ResumeThread(); }else AfxMessageBox(sNoSocketConnect+GetLastSocketError()); CAsyncSocket::OnAccept(nErrorCode); } void Broadcast(const CString& Msg); private: SOCKET hSocket; static UINT __cdecl ThreadProc(LPVOID pParam); protected: virtual CConnection* New() =0; // {return new CMyhandler();} where CMyhandler is derived from CConnection }; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> CConnection <<<<<<<<<<<<<<<<<<<<<<<<<< One CSocketServer holds many CConnections. Derive your Socket Handling class from CConnection. */ class CConnection : public CSocketBase { friend class CSocketServer; CLock Lock; volatile bool Destruction; volatile bool Sending; volatile CConnection* Prev; volatile CConnection* Next; CSocketServer* Me; protected: CWinThread* pThread; public: CConnection(CSocketServer* Me) : Me(Me), Destruction(false), Sending(false), pThread(AfxGetThread()), Prev(0), Next(0) {} virtual ~CConnection() {try{Destruction=true; Disconnect();}catch(...){}} CSocketServer* GetServer() const {return Me;} void Connect() { Lock.Lock(); //Add me to the list of Connections: Next=Me->Sockets; Me->Sockets=this; if(Next) Next->Prev=this; Lock.Unlock(); try { File=new CSocketFile(this); ArI=new CArchive(File, CArchive::load); ArO=new CArchive(File, CArchive::store); UINT _Port; GetPeerName(RemoteIP, _Port); OnConnect(); for(MSG Msg;;) { // Instead of using a RUNTIMECLASS version of AfxBeginThread (which would make everything far more complicated) we keep this thread alive with a Message Pump: if( ::PeekMessage(&Msg, 0,0,0, PM_NOREMOVE)) { if(!::GetMessage(&Msg, 0,0,0)) break; // WM_QUIT switch(Msg.message) { case WM_USER+0x519: *(volatile bool*)(Msg.lParam)=true; break; // Notify that Queue is at this Message. ("519" was chosen because it looks like "Sig"nal) case WM_USER+0x50C: { // Send a Message from the Server ("50C" was chosen because it looks like "SOC"ket) CString* ptr=(CString*)(Msg.lParam); Sender(*ptr); delete ptr; Sending=false; break; } default: { //Process some other messge: ::TranslateMessage(&Msg); ::DispatchMessage (&Msg); } } } } } catch(...) {} try { OnDisconnect(); Lock.Lock(); // Remove me from the list of Connections: if(Next) Next->Prev=Prev; if(Prev) Prev->Next=Next; else Me->Sockets=Next; Lock.Unlock(); CSocketBase::Disconnect(); pThread=0; //Used as a semaphore for Disconnect() if(!Destruction) delete this; //If it's the destructor thats got us here, don't try to "delete this" again! }catch(...) {} } void Wait() { //Wait for the Message Queue to empty. if(!pThread) return; if(pThread==AfxGetThread()) return; volatile bool Signal=false; pThread->PostThreadMessage(WM_USER+0x519, 0, (LPARAM)&Signal); while(!Signal) for(MSG Msg; PeekMessage(&Msg, 0,0,0, PM_REMOVE); DispatchMessage(&Msg)) TranslateMessage(&Msg); // This Message Pump just allows the Thread to maintain any Windows it may have } // In any one call to OnReceive(), you can either use Send, or access ArO directly, but not both! void Send(const CString& Msg) { //The Send code makes sure that this Connection's Thread does all the Sending, and allows the method used to send, to be changed by overriding Sender(Msg). if(!pThread) return; //You'd think that if this was our Pump Thread, we could just send the data, but this message would be out of order if other Messages are waiting in the queue. if(pThread==AfxGetThread()) Sender(Msg); else... pThread->PostThreadMessage(WM_USER+0x50C, 0, (LPARAM)new CString(Msg)); //Tell our Thread to Send when it's ready. } void Disconnect() { if(!pThread) return; pThread->PostThreadMessage(WM_QUIT,0,0); if(pThread!=AfxGetThread()) while(pThread) Sleep(250); // Don't destroy anything else until the other Thread has finished cleaning up. } protected: virtual void Sender(const CString& Msg) {*ArO << Msg; ArO->Flush();} //Override to use something like: GetArO()->WriteString(Msg+"\r\n"); GetArO()->Flush(); virtual void OnClose () {Disconnect();} //Called by the Thread running our Messge Pump when the Remote Computer disconnects. virtual void OnDisconnect() {} // Called after disconnection virtual void OnConnect () {} // Called after connection virtual void OnReceive () {} // Called when a Byte is received /* This version will hang the first thread in. This demonstrates that the server continues to run subsequent threads: void OnReceive() { Handler.Receive(); static bool t=true; if(t) { t=false; MsgWaitForMultipleObjects(0,0,TRUE,INFINITE,0); } } */ }; #endif // SocketInterfaceh