In-depth explanation of VC ++ (15): Implementation of Simple chat tools

Source: Internet
Author: User

Let's look at a comprehensive example: Using MFC to implement a network chat software. It may be strange to see this example. I mentioned a similar console application in network programming. Why should I put it here? The reason is that, in the previous example, we have to wait for another person to say it. We cannot say it in a row. This is determined by its implementation code; what we want to achieve here is a "free" conversation, which can send or receive data at any time. This requires the knowledge of this section to help: we can use a thread to receive messages.
Next we will start step by step to design the appearance of the program: first use a dialog box application, and then delete the default buttons "OK" and "cancel" on the dialog box. Add the following controls to it:

Control name ID Action Group box idc_static ID: receive data edit box idc_edit_recv show all chat data send group box idc_static ID: data sending IP address space idc_ipaddress1 allows the user to enter the dot decimal IP address sending and editing box idc_edit_send allows the user to enter the sent content Sending button idc_btn_send and then send the information to the specified IP Address

After finishing the design, we are about to write a network program. Let's recall the steps for writing a UDP-based socket:
Server:
1. Load the socket library and negotiate the version number
2. Create a socket.
3. Bind the port
4. Send/receive data
5. Disable socket
6. release resources
Client:
1. Load the socket library and negotiate the version number
2. Create a socket
3. Send/receive messages
4. Disable socket
5. release resources

MFC provides the afxsocketinit function for us to load sockets and negotiate the version number. According to msdn, we should put it in the initinstance of my own application class:

Bool cch_15_chatapp: initinstance () {If (! Afxsocketinit) {afxmessagebox ("failed to load socket"); Return false;} // other Code omitted}

Note that to use this function, the header file afxsock. h must be included. We should include this header file in stdafx. h. Stdafx. H is a pre-compiled header file that contains some necessary header files required by the MFC application.

Next, it is not the caboutdlg class of the two classes derived from cdialog (caboutdlg is used to generate a dialog box, which is useless here) add a member variable m_socket of the private socket type and the member function initsocket of the bool type to initialize the socket:

Bool cch_15_chatdlg: initsocket () {m_socket = socket (af_inet, sock_dgram, 0); If (invalid_socket = m_socket) {MessageBox ("failed to create socket"); Return false ;} sockaddr_in addrsock; addrsock. sin_family = af_inet; addrsock. sin_port = htons (6000); addrsock. outputs = htonl (inaddr_any); int retval; retval = BIND (m_socket, (sockaddr *) & addrsock, sizeof (sockaddr); If (socket_error = retval) {closesocket (M_socket); MessageBox ("binding failed! "); Return false;} return true ;}

Our program needs to receive and send messages. For the receiving program, we need to specify the port used to receive messages from which IP addresses. We should call this function in oninitdialog to complete socket initialization.
The following provides the receiving function. If no data arrives, the recvfrom function is blocked, causing the program to stop running. Therefore, we can place the operations to receive data in a separate thread and pass two parameters to this thread. One is a socket and the other is a dialog box handle. In this way, after receiving the imported data, you can send the data to the dialog box. After processing, the data is displayed in the receiving and editing box control. However, we know that the createthread function only has 4th parameters that can be used to pass parameters to the created thread. What should we do? We found that the 4th parameter is an lpvoid, and its actual type is (void *), which is also a null pointer. Therefore, we can define a struct, this struct contains the two parameters mentioned above, and then transmits the address of this struct to the function.
First, we define a struct in the header file of this class:

struct RECVPARAM  {      SOCKET sock;      HWND hwnd;  };

In oninitdialog, assign values to the struct and create a thread:

RECVPARAM *pRecvParam = new RECVPARAM;  pRecvParam->sock = m_socket;  pRecvParam->hwnd = m_hWnd;    HANDLE hThread = CreateThread(NULL,0,RecvProc,(LVOID)pRecvParam,0,NULL);  CloseHandle(hThread); 

The thread function recvproc has not been written yet. How should we write it? We can write a global function, but what should we do if we are in Object-Oriented consideration and cannot write a global function? We cannot simply write it as a class member function. Because class member functions are called through class objects, we do not have any objects here. One way to solve this problem is to use static member functions.

DWORD winapi cch_15_chatdlg: recvproc (lpvoid lpparameter) {// obtain the socket and window handle from the parameter Socket socket = (recvparam *) lpparameter)-> sock; hwnd = (recvparam *) lpparameter)-> hwnd; // release the memory Delete lpparameter; sockaddr_in addrfrom; int Len = sizeof (sockaddr); char recvbuf [200]; char tempbuf [300]; int retval; // always in the receiving status while (true) {// retval = recvfrom (socket, recvbuf, 0, (sockaddr *) & addrfrom, & Len); If (socket_error = retval) break; // format the received data to sprintf (tempbuf, "% s said: % s ", inet_ntoa (addrfrom. sin_addr), recvbuf); // delivery message: postmessage (hwnd, wm_recvdata, 0, (lparam) tempbuf);} return 0 ;}

Wm_recvdata is a custom message. We should define in the header file:

#define WM_RECVDATA WM_USER+1 

Next, the message is matched, or step 3: the Declaration of the response function:

afx_msg void OnRecvData(WPARAM wParam, LPARAM lParam);  

Message ing:

ON_MESSAGE(WM_RECVDATA,OnRecvData)

Function Definition:

Void metadata: onrecvdata (wparam, lparam) {cstring STR = (char *) lparam; cstring strtemp; // put the original message in strtemp getdlgitemtext (idc_edit_recv, strtemp); STR + = "\ r \ n"; // STR contains the original information and the current information STR + = strtemp; // output them together with setdlgitem (idc_edit_recv, str );}

Next we will implement the sending function. First, when the user clicks the "send" button, it should be sent. we add a message response function for it:

Void cch_15_chatdlg: onbtnsend () {// todo: add your control notification handler code here // get the IP address of the sending object DWORD dwip; (cipaddressctrl *) getdlgitem (idc_ipaddress1 )) -> getaddress (dwip); sockaddr_in addrto; addrto. sin_addr.s_un.s_addr = htonl (dwip); addrto. sin_family = af_inet; addrto. sin_port = htons (6000); // obtain the cstring strsend text to be sent; getdlgitemtext (idc_edit_send, strsend); // send the message sendto (m_socket, strsend, strsend. getlength () +, (sockaddr *) & addrto, sizeof (sockaddr); // clear the send message box setdlgitemtext (idc_edit_send ,"");}

Then, we set the edit box control of the displayed message to support multiple lines and set the send button to the default button!

I am not quite satisfied with this program: I can't see what I sent to it before? How to modify it? With the previous program, the problem can be solved as follows:

Void cch_15_chatdlg: onbtnsend () {// todo: add your control notification handler code here // get the IP address of the sending object DWORD dwip; (cipaddressctrl *) getdlgitem (idc_ipaddress1 )) -> getaddress (dwip); sockaddr_in addrto; addrto. sin_addr.s_un.s_addr = htonl (dwip); addrto. sin_family = af_inet; addrto. sin_port = htons (6000); // obtain the text to be sent: Char sendbuf [300]; char tempbuf [300]; getdlgitemtext (idc_edit_send, sendbuf, 300 ); // send the message sendto (m_socket, sendbuf, strlen (sendbuf) +, (sockaddr *) & addrto, sizeof (sockaddr); sprintf (tempbuf, "I said: % s ", sendbuf);: postmessage (m_hwnd, wm_recvdata, 0, (lparam) tempbuf); // clear the sending message box setdlgitemtext (idc_edit_send ,"");}

This is all done! In fact, there is a simpler way to directly obtain and set the text content, which we will use below.

In fact, Windows Sockets perform I/O operations in two modes: Blocking Mode and non-blocking mode. In blocking mode, before the I/O operation is complete, the Winsock function that executes the operation will wait and will not return immediately. The recvfrom function in our program is like this. If no data is obtained, it will be blocked. Of course, because we write the function that receives data in a thread, its blocking does not affect the running of other threads.
In non-blocking mode, Winsock returns immediately. After the function is executed, the system notifies the thread of the operation result, and the thread determines whether the operation is normal based on the notification information.

Because the blocking method affects the system performance, it is sometimes necessary to use a non-blocking method. To support the message-driven mechanism of windows, windowssocket adopts a message-based Asynchronous access policy for network events. Specifically, when a network event registered by the wsaasyncselect function occurs, the window function of the Windows application receives a message indicating a network event, and some information related to the event.
Let's look at the related functions:
Wsaasyncselect: network event notification based on Windows messages for the specified socket request, and the socket is set to non-blocking mode.
Wsaenumprotocols: obtains information about the network protocol installed by the system.
Wsastartup: ws2_32.dll used by the initialization process
Wsasocket: Create a socket
Wsarecvfrom: receives data of the datagram type and saves the address of the Data sender.
Wsasendto: send data to the specified destination.

The following describes how to use these functions. We still write the previous online chat room program.
The design of the exterior part is the same as that of the previous one. Do not forget to add ws2_32.lib to the connector.
The following describes how to load a socket. We used the afxsocketinit function before, but this function can only load the socket library of Version 1.1. This program needs some functions of Version 2.0, so we should call wsastartup to manually load the function. Similarly, the loading function should also be implemented in initinstance:

// Load the socket library word wversionrequested; wsadata; int err; wversionrequested = makeword (2, 2); err = wsastartup (wversionrequested, & wsadata); If (Err! = 0) {return false;} If (lobyte (wsadata. wversion )! = 2 | hibyte (wsadata. wversion )! = 2) {wsacleanup (); Return false ;}

Similarly, stdafx. h must contain: # include <winsock2.h>

The following task is to create and initialize the socket: Add a private socket member variable m_socket for our dialog box class, And a bool member function initsocket

Bool cch_16_chatdlg: initsocket () {// create socket m_socket = wsasocket (af_inet, // address family sock_dgram, // service type: datagram 0, // protocol type, automatically select Null Based on the service type, // use the first three parameters to determine the feature 0 of the created socket, // retain 0); // No attribute if (invalid_socket = m_socket) {MessageBox ("failed to create socket! "); Return false;} // bind the socket sockaddr_in addrsock; addrsock. sin_addr.s_un.s_addr = htonl (inaddr_any); addrsock. sin_family = af_inet; addrsock. sin_port = htons (6000); If (socket_error = BIND (m_socket, (sockaddr *) & addrsock, sizeof (sockaddr) {MessageBox ("binding failed "); return false;} // register the network event if (socket_error = wsaasyncselect (m_socket, // identifies the socket descriptor m_hwnd, // The Window handle um_sock for receiving messages when a network event occurs, // specify the message fd_read received by the window when a network event occurs )) // network event {MessageBox ("failed to register network read event"); Return false;} return true ;}

Note: after using the wsaasyncselect function, the wparam of the custom message specifies the socket, while the low bytes of lparam specify the network event, and the high bytes specify the error code.

We can call this function in oninitdialog. At the same time, do not forget to add the um_sock definition in the header file of our dialog box class:

#define UM_SOCK WM_USER+1

The following describes the implementation of the receiving function. When a registered event occurs, the operating system sends a Response Message to the calling process and sends the corresponding information of the event to the calling process, the write information can be passed through the message parameters. Now we write the message response function um_sock:

Void cch_16_chatdlg: onsock (wparam, lparam) {Switch (loword (lparam) {Case fd_read: // wsabuf; wsabuf. buf = new char [200]; wsabuf. len = 200; DWORD dwread; DWORD dwflag = 0; sockaddr_in addrfrom; int Len = sizeof (sockaddr); cstring STR; cstring strtemp; // receive data if (sock_error = wsarecvfrom (m_socket, // socket & wsabuf, // Receiving address 1, // 1 address & dwread, // actually accept & dwflag, // No (sockaddr *) & addrfrom, // buffer where the source address is stored & Len, // the buffer size is null, // ignore NULL for non-overlapping words) // ignore for non-overlapping words {MessageBox ("failed to receive data"); Delete [] wsabuf. buf; return;} Str. format ("% s: % s", inet_ntoa (addrfrom. sin_addr), wsabuf. buf); STR + = "\ r \ n"; getdlgitemtext (idc_edit_recv, strtemp); STR + = strtemp; setdlgitemtext (idc_recv, STR); Delete [] wsabuf. buf; break ;}}

Finally, click the send button to send the message:

Void cch_16_chatdlg: onbtnsend () {// todo: add your control notification handler code heredword dwsend; cstring strsend; getdlgitemtext (idc_edit_send, strsend); int Len = strsend. getlength (); wsabuf. buf = strsend. getbuffer (LEN); wsabuf. len = Len + 1; DWORD dwip; (cipaddressctrl *) getdlgitem (idc_ipaddress1)-> getaddress (dwip); sockaddr_in addrto; addrto. sin_addr.s_un.s_addr = htonl (dwip); addrto. Sin_family = af_inet; addrto. sin_port = htons (6000); If (socket_error = wsasendto (m_socket, // socket & wsabuf, // send data 1, // 1 address & dwsend, // The number of actually sent messages is 0, // enter 0 (sockaddr *) & addrto, // the sizeof (sockaddr) Address of the target sending Address, // The address size is null, // null is not used) // {MessageBox ("failed to send! "); Return;} // clear the sending region setdlgitemtext (idc_edit_send ,"");}

Finally, terminate the use of the socket library in the destructor of our app class:

CCH_16_CHATApp::~CCH_16_CHATApp(){WSACleanup();}

Terminate socket usage in the empty dialog box:

CCH_16_CHATDlg::~CCH_16_CHATDlg(){if(m_socket)closesocket(m_socket);}

Next, let's change the way, because the IP address is hard to remember. Can we use host name P for communication? In fact, when sending data to our program, we always need to fill in the IP addresses in the sockaddr_in class. So, as long as we can convert the host name to the IP address, it will be OK.
This can be achieved through gethostbyname. The return value of this function is the hostent struct:

struct hostent {  char FAR *       h_name;  char FAR * FAR * h_aliases;  short            h_addrtype;  short            h_length;  char FAR * FAR * h_addr_list;};

The last element in the array is a pointer array. Each element in the array is a struct of IP addresses (computers with multiple NICs may have multiple IP addresses ).
After the analysis, let's take a look at the specific operations:
Add a group box for the dialog box resources, change the name to the Host Name, overwrite an edit box, and change the ID to idc_edit_hostname.
Then, slightly modify the sender program:

Sockaddr_in addrto; DWORD dwip; cstring strhostname; hostent * phost; // if no host name is obtained, use an IP address. Otherwise, convert the host name to an IP address if (getdlgitemtext (idc_edit_hostname, strhostname ), strhostname = "") {(cipaddressctrl *) getdlgitem (idc_ipaddress1)-> getaddress (dwip); addrto. sin_addr.s_un.s_addr = htonl (dwip);} else {phost = gethostbyname (strhostname); // h_addr_list [0] is a pointer to an IP address, but the IP address is of the DWORD * type, therefore, you must first convert the data and then obtain the content addrto. sin_addr.s_un.s_addr = * (DWORD *) phost-> h_addr_list [0]);}

In this way, the IP address is displayed in the message receiving area. Can the sender be changed to the host name when the message is displayed? It is also possible to use the gethostbyaddr function. We only need to make a slight modification in onsock:

Hostent * phost; phost = gethostbyaddr (char *) & addrfrom. sin_addr.s_un.s_addr, 4, af_inet); If (phost) {Str. format ("% s: % s", phost-> h_name, wsabuf. buf);} else {Str. format ("% s: % s", inet_ntoa (addrfrom. sin_addr), wsabuf. buf );}

The following are the same questions: how to enable the receive message dialog box to display the messages we sent after clicking send. First, let's recall how the blocked version displays the message: in that version, if a message is received, a custom message will be sent, and the message parameters can pass the sent content; when you click "send", you can also send a message. The parameters of the message include the content to be sent. In the message response function, it specifically displays the content in a reasonable manner.
In fact, this method is affected. We can directly obtain and set the control content in onbtnsend:

Cstring strshow; getdlgitemtext (idc_edit_recv, strshow); strshow + = "I said:"; strshow + = strsend; strshow + = "\ r \ n"; setdlgitemtext (idc_edit_recv, strshow );

However, you need to slightly adjust the Character Sequence in onsock to insert the content in the original edit box to the front of the received content:

// STR is the received content STR + = "\ r \ n"; // strtemp is the existing content in the edit box getdlgitemtext (idc_edit_recv, strtemp); Str. insert (0, strtemp. getbuffer (200); setdlgitemtext (idc_edit_recv, STR );

This is almost the case.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.