Today, we come to the eighth day, the socket programming of MFC, using csocket to implement a TCP-based QQ chat program. You will find that MFC is much simpler than Win32. However, if you do not understand the basic knowledge of the specific API socket, you may feel a little puzzled. So before you start, I 'd like to ask you to look at the http://blog.csdn.net/lh844386434/article/details/6664025 first.
At the beginning of the application, we should first talk about the Winsock library, so we will use the following function.
Bool afxsocketinit (wsadata * lpwsadata = NULL); // It is used to initialize the socket, and wsastartup (); is used for initialization. When the application ends, wsacleanup () is automatically called ()
Before starting programming, we should call this function to initialize the socket. If the initialization is successful, non-0 is returned; otherwise, 0 is returned.
Someone may ask, what version of socket library does this function load? By looking at the underlying code, we found that it loads the socket of the 1.1 version.
Note: This function can only be used inCxxwinapp: initinstanceBefore initialization, remember to add the header file afxsock. h.
My server program is netchatserver, so I added it to the cnetchatserverapp: initinstance ().
//////////////////////////////////////// //////////////////////////////////////// /// // Cnetchatserverapp:: initinstance () //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// ///////
If (! Afxsocketinit () {afxmessagebox (_ T ("socket library initialization error! "); Return false ;}
M_isocket is the pointer of A cserversocket *, and the cserversocket class is our own class. I will give the corresponding code later, which inherits from the csocket class.
M_isocket = new cserversocket (); // 1. dynamically create a server socket object. If (! M_isocket) {afxmessagebox (_ T ("An error occurred while creating a server socket dynamically! "); Return false ;}
Create a socket
If (! M_isocket-> Create (8989) {afxmessagebox (_ T ("An error occurred while creating the socket! "); M_isocket-> close (); Return false ;}
8989 is the specified port number, but note that before saving the port 8989 we specified, this port is idle and not occupied by other processes, how can I check whether the port is occupied by other processes?
Open CMD and type netstat-Aon.
You will see all the TCP/UDP information, but it is difficult to check because there are too many. In the bottom tasklist | find "8989"
Now we can see that we have not found anything related to port 8989, so port 8989 is not occupied.
After the socket is created, follow the Win32 steps to configure the BIND port.
However, this is not the case with MFC. The BIND should have been called internally for the create of MFC. below is the underlying code of MFC.
Bool casyncsocket: Create (uint nsocketport, int nsockettype, long Levent, lpctstr lpszsocketaddress) {If (socket (nsockettype, Levent) {If (BIND (nsocketport, lpszsocketaddress )) // call bind return true; int nresult = getlasterror (); close (); wsasetlasterror (nresult);} return false ;}
So we don't have to call bind, so we can directly listen on the socket.
If (! M_isocket-> listen () {afxmessagebox (_ T ("listening failed! "); M_isocket-> close (); Return false ;}
//////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// /////////////
Reload the exitinstance and clear it when exiting.
int CNetChatServerApp::ExitInstance(){if(m_iSocket){delete m_iSocket;m_iSocket = NULL;}return CWinApp::ExitInstance();}
//////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////
The following describes the specific implementation of cserversocket.
# Pragma once # include "clientsocket. H" class cserversocket: Public csocket {public: cserversocket (); Virtual ~ Cserversocket (); Public: cptrlist m_listsockets; // used to save the clientsocketpublic: Virtual void onaccept (INT nerrorcode) after the server successfully connects to all clients );};
# Include "stdafx. H" # include "netchatserver. H" # include "serversocket. H" cserversocket: cserversocket () {} cserversocket ::~ Cserversocket () {} void cserversocket: onaccept (INT nerrorcode) {// receives a connection request cclientsocket * theclientsock (0); theclientsock = new cclientsocket (& m_listsockets ); if (! Theclientsock) {afxmessagebox (_ T ("the client fails to connect to the server because the memory is insufficient! "); Return;} accept (* theclientsock); // you can add m_listsockets.addtail (theclientsock) to the list to facilitate management; csocket: onaccept (nerrorcode );}
We can see that there is another cclientsocket class in cserversocket. This class, like cserversocket, is also derived from the csocket class, but is specially used for the socket of the client.
Here, you must reload the onaccept (INT nerrorcode) function so that cserversocket can receive client requests and must call the accept () function in onaccept to respond to connection requests.
In onaccept (), we use a list to save the clientsocket pointer for future access calls.
//////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// ///////////////////
Next let's take a look at the cclientsocket class.
# Pragma once # include "stdafx. H "////////////////////////////////////// //// // description, this class is used to establish a socket //////////////////////////////// //// // class cclientsocket: public csocket {public: cclientsocket (cptrlist * plist); Virtual ~ Cclientsocket (); Public: cptrlist * m_plist; // Save the list items in the clientsocket on the server. This is the cstring m_strname passed in the cserversocket; // The connection name is public: virtual void onclose (INT nerrorcode); Virtual void onreceive (INT nerrorcode); void onlogoin (char * buff, int nlen); // process the login message void onmsgtranslate (char * buff, int nlen); // forward the message to other chat groups cstring updateserverlog (); // the server side updates and records the log void updatealluser (cstring struserinfo ); // update the server's online staff list PRIVATE: bool wchar2mbyte (lpcwstr srcbuff, lpstr destbuff, int nlen); // multibyte conversion };
We can see that the onclose () and onreceive () functions are reloaded, so that when the socket is closed and data arrives, these two functions are automatically called, we can respond to and process events in these two functions.
Because I use vs2010 and use Unicode encoding, it often involves Unicode to multi-byte conversion, so I wrote wchar2mbyte () for conversion.
# Include "stdafx. H "# include" netchatserver. H "# include" clientsocket. H "# include" header. H "# include" netchatserverdlg. H "cclientsocket: cclientsocket (cptrlist * plist): m_plist (plist), m_strname (_ T (" ") {} cclientsocket ::~ Cclientsocket () {}////////////////////////////////////// /// // void cclientsocket:: onreceive (INT nerrorcode) {// receive a message // obtain the header head first; int nlen = sizeof header; char * phead = NULL; phead = new char [nlen]; If (! Phead) {trace0 ("cclientsocket: onreceive insufficient memory! "); Return;} memset (phead, 0, sizeof (char) * nlen); receive (phead, nlen); head. type = (lpheader) phead)-> type; head. ncontentlen = (lpheader) phead)-> ncontentlen; Delete phead; phead = NULL; // receives the message again. This is the data class phead = new char [head. ncontentlen]; If (! Phead) {trace0 ("cclientsocket: onrecive insufficient memory! "); Return;} If (receive (phead, head. ncontentlen )! = Head. ncontentlen) {afxmessagebox (_ T ("error in receiving data! "); Delete phead; return;} // depending on the message type, process Data // switch (head. type) {Case msg_logoin: onlogoin (phead, head. ncontentlen); break; Case msg_send: onmsgtranslate (phead, head. ncontentlen); break; default: break;} Delete phead; csocket: onreceive (nerrorcode);} // close the connection void cclientsocket: onclose (INT nerrorcode) {ctime time; time = ctime: getcurrenttime (); cstring strtime = time. fo Rmat ("% Y-% m-% d % H: % m: % s"); strtime = strtime + this-> m_strname + _ T ("exit... \ r \ n "); (cnetchatserverdlg *) theapp. getmainwnd ()-> displaylog (strtime); m_plist-> removeat (m_plist-> Find (this )); // change the server online list cstring str1 = This-> updateserverlog (); // notify the client to refresh the online list this-> updatealluser (str1); this-> close (); // destroy this socket Delete this; csocket: onclose (nerrorcode);} // log on to void cclientsocket: onlogoin (char * buff, int n Len) {// verify the received user information //... (to simplify this step) // ctime time for Successful Logon; time = ctime: getcurrenttime (); cstring strtime = time. format ("% Y-% m-% d % H: % m: % s"); cstring strtemp (buff ); strtime = strtime + strtemp + _ T ("login... \ r \ n "); // record the log (cnetchatserverdlg *) theapp. getmainwnd ()-> displaylog (strtime); m_strname = strtemp; // update the service list cstring str1 = This-> updateserverlog (); // update this-> updatealluser (str1);} // forward Message void cclientsocket: onmsgtranslate (char * buff, int nlen) {header head; head. type = msg_send; head. ncontentlen = nlen; position PS = m_plist-> getheadposition (); While (PS! = NULL) {cclientsocket * ptemp = (cclientsocket *) m_plist-> getnext (PS); ptemp-> send (& head, sizeof (header )); ptemp-> send (buff, nlen) ;}} bool cclientsocket: wchar2mbyte (lpcwstr srcbuff, lpstr destbuff, int nlen) {int n = 0; n = cursor (cp_oemcp, 0, srcbuff,-1, destbuff, 0, 0, false); If (n <nlen) return false; widechartomultibyte (cp_oemcp, 0, srcbuff,-1, destbuff, nlen, 0, false); Return true;} // Follow the void cclientsocket: updatealluser (cstring struserinfo) {header _ head; _ head. type = msg_update; _ head. ncontentlen = struserinfo. getlength () + 1; char * psend = new char [_ head. ncontentlen]; memset (psend, 0, _ head. ncontentlen * sizeof (char); If (! Wchar2mbyte (struserinfo. getbuffer (0), psend, _ head. ncontentlen) {afxmessagebox (_ T ("character conversion failed"); Delete psend; return;} position PS = m_plist-> getheadposition (); While (PS! = NULL) {cclientsocket * ptemp = (cclientsocket *) m_plist-> getnext (PS); // send protocol header ptemp-> send (char *) & _ head, sizeof (_ head); ptemp-> send (psend, _ head. ncontentlen);} Delete psend;} // returns the stringcstring cclientsocket: updateserverlog () {cstring struserinfo = _ T ("") to the online user list of the new server (""); position PS = m_plist-> getheadposition (); While (PS! = NULL) {cclientsocket * ptemp = (cclientsocket *) m_plist-> getnext (PS); struserinfo + = ptemp-> m_strname + _ T ("#");} (cnetchatserverdlg *) theapp. getmainwnd ()-> updateuserinfo (struserinfo); Return struserinfo ;}
//////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// ///////////////
The above Code also involves a header struct. This is a custom header structure, which is equivalent to a custom protocol, but this is simplified. In this protocol, we want to specify that we want to send data this time.
The type is the data of the message we send. There is also the length of the data. In order not to waste space, we choose to send twice. Each time you send data to the server, a protocol header is sent before the data itself is sent.
In fact, it can be neither a waste of space nor a single message. However, the data is serialized before being sent. The data is deserialized after the receiver receives the data. However, C ++ does not provide the corresponding method, So we either write it ourselves or use a third-party library class. However, the cost of this method is much higher than that of the method. Therefore, we can send the method twice for convenience.
//////////////////////////////////////// //////////////////////////////////////// //////////////////////////////
/// Define the protocol header because the class capacity to be transmitted directly has a class capacity that is not long.
/// To avoid wasting space, define a header
//////////////////////////////////////// //////////////////////////////////////// ////////////////////////////
# Pragma once
//// // Custom protocol ///////////////////
Const int msg_logoin = 0x01; // log on
Const int msg_send = 0x11; // send a message
Const int msg_close = 0x02; // exit
Const int msg_update = 0x21; // update information
# Pragma pack (push, 1)
Typedef struct tagheader {
Int type; // protocol type
Int ncontentlen; // The length of the content to be sent
} Header, * lpheader;
# Pragma pack (POP)
This involves a knowledge of byte alignment, please check the http://blog.csdn.net/lh844386434/article/details/6680549
//////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////
At this point, the basic server-side framework for sending is all set up, and the rest is some interface programming, such as what to display and so on. I will not post the code here, however, I will give the whole project at the end. Let's take a look at the client code.
//////////////////////////////////////// /// // Client /////////////////////// ///////////////////////////////////////
The client is much simpler. It only involves one cclientsocket. However, this class is not the same as that on the server, but the same name.
First, we should initialize the socket library. The location is the same as the method used to add the client. Then, create the client socket and connect to the server.
If (! Afxsocketinit () {afxmessagebox (_ T ("failed to initialize the socket library! "); Return false;} m_psocket = new cclientsocket (); If (! M_psocket) {afxmessagebox (_ T ("insufficient memory! "); Return false;} If (! M_psocket-> Create () {afxmessagebox (_ T ("failed to create socket! "); Return false;} clogoindlg * plogoindlg; // The logon dialog box plogoindlg = new clogoindlg (); If (plogoindlg-> domodal () = idok) // The button is actually clicked here, but the id I used is idok, not modified {// do not log on to delete plogoindlg; m_psocket-> close (); return false;} else {Delete plogoindlg ;}
(There is also a clogoindlg class above, which is the class of a logon dialog box and some of its code will be provided later .)
Reload exitinstance () Like the server ();
int CNetChatClientApp::ExitInstance(){if(m_pSocket){delete m_pSocket;m_pSocket = NULL;}return CWinApp::ExitInstance();}CClientSocket* CNetChatClientApp::GetMainSocket() const{return m_pSocket;}
//////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////
Then let's look at the implementation of cclientsocket on the client.
# Pragma onceclass cclientsocket: Public csocket {public: cclientsocket (); Virtual ~ Cclientsocket (); Public: Virtual void onreceive (INT nerrorcode); // The client receives the message bool sendmsg (lpstr lpbuff, int nlen); // the client sends the message bool logoin (lpstr lpbuff, int nlen); // client logon cstring m_strusername; // user name };
Obviously, we must reload the onreceive function to process the received data. Other functions are some event processing functions, which are the same as the description.
# Include "stdafx. H "# include" netchatclient. H "# include" clientsocket. H "# include" header. H "# include" netchatclientdlg. H "// cclientsocketcclientsocket: cclientsocket (): m_strusername (_ T (" ") {} cclientsocket ::~ Cclientsocket () {} void cclientsocket: onreceive (INT nerrorcode) {// first accept head header head; char * phead = NULL; phead = new char [sizeof (head)]; memset (phead, 0, sizeof (head); receive (phead, sizeof (head); head. type = (lpheader) phead)-> type; head. ncontentlen = (lpheader) phead)-> ncontentlen; Delete phead; phead = NULL; char * pbuff = NULL; pbuff = new char [head. ncontentlen]; If (! Pbuff) {afxmessagebox (_ T ("insufficient memory! "); Return;} memset (pbuff, 0, sizeof (char) * head. ncontentlen); If (head. ncontentlen! = Receive (pbuff, head. ncontentlen) {afxmessagebox (_ T ("An error occurred while receiving the data! "); Delete pbuff; return;} cstring strtext (pbuff); Switch (head. type) {Case msg_update: {cstring strtext (pbuff); (cnetchatclientdlg *) (afxgetapp ()-> getmainwnd ()-> updateuserinfo (strtext);} break; case msg_send: {// display the received message cstring STR (pbuff); (cnetchatclientdlg *) (afxgetapp ()-> getmainwnd ()-> updatetext (STR ); break;} default: break;} Delete pbuff; csocket: onreceive (nerrorcode);} bool cclientsocket: sendmsg (lpst R lpbuff, int nlen) {// generate the header head of the protocol header; head. type = msg_send; head. ncontentlen = nlen; If (send (& head, sizeof (header) = socket_error) {afxmessagebox (_ T ("sending error! "); Return false ;}; if (send (lpbuff, nlen) = socket_error) {afxmessagebox (_ T (" sending error! "); Return false;}; return true;} bool cclientsocket: logoin (lpstr lpbuff, int nlen) {header _ head; _ head. type = msg_logoin; _ head. ncontentlen = nlen; int _ nsnd = 0; If (_ nsnd = Send (char *) & _ head, sizeof (_ head) = socket_error) return false; if (_ nsnd = Send (lpbuff, nlen) = socket_error) return false; return true ;}
//////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////////////////////////////////// //////////
Finally, let's take a look at some of the codes for logon and message sending, which are the same as those on the server side. They are divided into two steps: sending, sending protocol headers, and then sending content.
Void clogoindlg: onbnclickedbtnlogoin () {// log on to updatedata (); If (m_struser.isempty () {afxmessagebox (_ T ("user name cannot be blank! "); Return;} If (m_dwip = 0) {afxmessagebox (_ T (" invalid IP Address "); return;} cclientsocket * psock = theapp. getmainsocket (); in_addr ADDR; ADDR. s_un.s_addr = htonl (m_dwip); cstring strip (inet_ntoa (ADDR); If (! Psock-> connect (strip. getbuffer (0), 8989) {afxmessagebox (_ T ("failed to connect to the server! "); Return ;}// send psock-> m_strusername = m_struser; char * pbuff = new char [m_struser.getlength () + 1]; memset (pbuff, 0, m_struser.getlength (); If (evaluate (m_struser.getbuffer (0), pbuff, m_struser.getlength () + 1) psock-> logoin (pbuff, m_struser.getlength () + 1); Delete pbuff; cdialogex: oncancel ();} void clogoindlg: onbnclickedok () {// exit cclientsocket * psock = theapp. getmainsocket (); psock-> close (); cdialogex: onok ();}
/// Message sending
Void cnetchatclientdlg: onbnclickedbtnsend () {// send message updatedata (); If (m_strsend.isempty () {afxmessagebox (_ T ("sending class capacity cannot be blank! "); Return;} cstring temp; ctime time = ctime: getcurrenttime (); temp = time. format ("% H: % m: % s"); // name + _ T ("\ n \ t") Time m_strsend = theapp. getmainsocket ()-> m_strusername + _ T ("") + temp + _ T ("\ r \ n") + m_strsend + _ T ("\ r \ n "); char * pbuff = new char [m_strsend.getlength () * 2]; memset (pbuff, 0, m_strsend.getlength () * 2); // convert to multi-byte wchar2mbyte (m_strsend.getbuffer (0 ), pbuff, m_strsend.getlength () * 2); // theapp. getmainsocket ()-> sendmsg (pbuff, m_strsend.getlength () * 2); Delete pbuff; m_strsend.empty (); updatedata (0 );}
void CNetChatClientDlg::UpdateUserInfo(CString strInfo){CString strTmp;CListBox* pBox = (CListBox*)GetDlgItem(IDC_LB_ONLINE);pBox->ResetContent();while(!strInfo.IsEmpty()){int n = strInfo.Find('#');if(n==-1)break;strTmp = strInfo.Left(n);pBox->AddString(strTmp);strInfo = strInfo.Right(strInfo.GetLength()-n-1);}}void CNetChatClientDlg::UpdateText(CString &strText){((CEdit*)GetDlgItem(IDC_ET_TEXT))->ReplaceSel(strText);}
//////////////////////////////////////// /All the above are part of the code, I will give the project /////////////////////////////////// ///////
Conclusion: We have simply passed network programming in windows. Due to my limited level, I have just begun to learn to write a blog, so it is inevitable to make mistakes. Sorry. In fact, the above Code is only implemented, group chat room.
I didn't implement one-to-one chat similar to QQ. But to do this, it should be very easy to implement the chat like QQ. Just add a little code. In addition, I did not write the interface due to the tight schedule recently. In that case, more code will be added, but I will repeat the notes in VC ++ to review the Interface Programming and implement the interface effect in qq2011. It will not take much time here.
Logon:
Server logging:
Two users chat:
Note: This program is passed in vs2010 + win7 x64
Engineering: http://download.csdn.net/source/3513082