?From the previous section (MFC Network Programming 1 – Network Basics and Sockets), we learned some basic knowledge of the network and the use of sockets. In this section, we learn the use of asynchronous sockets.
?Windows Sockets perform I/O operations in two modes, blocking mode and non-blocking mode. In blocking mode, before the I/O operation is completed, the Winsock function that performs the operation will wait forever and will not return immediately. For example, after the recvfrom function is called in the program, if there is no data transmitted on the network at this time, the function It will block the execution of the program, causing the calling thread to pause.
?In non-blocking mode, the Winsock function will return immediately anyway. After the operation performed by the function is completed, the system will use some method to notify the calling thread of the operation result. In many cases, blocking methods will affect the performance of the application, so sometimes it is necessary to implement network applications in a non-blocking method. In order to support the Windows message-driven mechanism and enable application developers to conveniently handle network communications, Windows Sockets adopts a message-based asynchronous access strategy for network events. The asynchronous selection function WSAAsyncSelect of Windows Sockets provides the network event selection of the message mechanism. When the network event registered using it occurs, the corresponding window function of the Windows application will receive a message indicating the network event that occurred and the corresponding Some information related to this event.
?Therefore, you can register for different network events. For example, if you register a network read event, once data arrives, this event will be triggered, and the operating system will notify the calling thread through a message, and the latter can This data is received in the corresponding message response function. Because the operating system sends a notification after the data arrives, the data will definitely be received at this time. Using asynchronous sockets can effectively improve application performance.
?In this section, we use asynchronous sockets to complete a chat room program, as shown in the figure below. The project download link is:
?https://download.csdn.net/download/mary288267/88344113
1 Related functions
?The Windows Socket specification provides a set of extension functions based on Berkeley socket functions. In addition to implementing the Socket function, these extension functions also allow processing based on messages or functions, handle asynchronous network events, and enable overlapping I/O functions. In addition to the WSAStartup() function and WSACleanup() function, writing Socket programs does not require the use of these extended API functions, but it is recommended to use these extended functions to remain consistent with the Windows programming model.
?Next, in order to use asynchronous sockets for programming, we will introduce several more important extension functions. These extension functions all start with WSA.
1.1 WSAStartup
?The WSAStartup function will initialize the Winsock DLL (WS2_32.DLL) used by the process. The prototype is as follows
int WSAAPI WSAStartup(
[in] WORD wVersionRequested,
[out] LPWSADATA lpWSAData
);
parameter
?[in] wVersionRequested: The highest version of the Windows Sockets specification that the caller can use. The high-order byte specifies the minor version number; the low-order byte specifies the major version number.
?[out] lpWSAData: Pointer to a WSADATA data structure for receiving Windows Sockets implementation details.
1.2 WSACleanup
The ?WSACleanup function will terminate the program’s use of the socket library Winsock 2 DLL (WS2_32.DLL). The prototype of this function is declared as follows.
int WSAAPI WSACleanup();
1.3 WSASocket
?The extension function WSASocket in the Winsock library will create a socket, and its prototype is declared as follows:
SOCKET WSAAPI WSASocketW(
[in] int af,
[in] int type,
[in] int protocol,
[in] LPWSAPROTOCOL_INFOW lpProtocolInfo,
[in] GROUP g,
[in] DWORD dwFlags
);
?parameter
?[in] af: address family. For TCP/IP protocol sockets, it can only be AF_INIET.
?[in] type: Type specification of the socket, as shown in the following table. In Windows Sockets 1.1, the only possible socket types were SOCK_DGRAM and SOCK_STREAM. SOCK_STREAM generates a streaming socket; SOCK_DGRAM generates a datagram socket.
Type | Meaning |
---|---|
SOCK_STREAM | A socket type that provides a sequenced, reliable, bidirectional connection-based byte stream through an OOB data transfer mechanism. This socket type uses the Transmission Control Protocol (TCP) for the Internet address family (AF_INET or AF_INET6). |
SOCK_DGRAM | A socket type that supports datagrams, which are connectionless, unreliable, fixed (usually smaller) maximum length buffer. This socket type uses User Datagram Protocol (UDP) for the Internet address family (AF_INET or AF_INET6). |
SOCK_RAW | A socket type that provides a raw socket that allows applications to manipulate the next layer of protocol headers. To manipulate IPv4 headers, the IP_HDRINCL socket option must be set on the socket. To manipulate IPv6 headers, the IPV6_HDRINCL socket option must be set on the socket. |
SOCK_RDM | A socket type that provides reliable message datagrams. An example of this type is the Practical General Multicast (PGM) multicast protocol implementation in Windows, often referred to as Reliable Multicast Programming. This type of value is only supported when the reliable multicast protocol is installed. |
SOCK_SEQPACKET | A socket type that provides pseudo-stream packets based on datagrams. |
?[in] protocol: The protocol to use. If you specify a value of 0, the system automatically selects an appropriate protocol based on the address format and socket class.
?[in] lpProtocolInfo: Pointer to a WSAPROTOCOL_INFO structure that defines the characteristics of the socket to be created. If this parameter is NULL, WinSock2.DLL uses the first three parameters to determine which service provider to use. If this parameter is not NULL, the socket is bound to the provider associated with the specified structure WSAPROTOCOL_INFO.
?[in] g: reserved.
?[in] dwFlags: A set of flags used to specify other socket properties.
1.4 WSAAsyncSelect
The ?WSAAsyncSelect function requests Windows message-based network event notification for the specified socket and automatically sets the socket to non-blocking mode.
int WSAAPI WSAAsyncSelect(
[in] SOCKET s,
[in] HWND hWnd,
[in] u_int wMsg,
[in] long lEvent
);
?parameter
?[in] s: Descriptor identifying the socket that requires event notification.
?[in] hWnd: Identifies the handle of the window that receives messages when a network event occurs.
?[in] wMsg: The message to be received when a network event occurs.
?[in] lEvent: Bit mask that specifies the combination of network events that are of interest to the application.
?Specifies the network events that the application is interested in. This parameter can be one of the values listed in the following table, and multiple events can be constructed using bitwise OR operations.
Value | Meaning |
---|---|
FD_READ | Set up to receive read ready notifications. |
FD_WRITE | Want to receive notification of preparation for writing. |
FD_OOB | Want to receive notifications about the arrival of OOB data. |
FD_ACCEPT | Want to receive notifications of incoming connections. |
FD_CONNECT | Want to receive notification of completed connection or multipoint connection operations. |
FD_CLOSE | Want to receive notification of socket closure. |
FD_QOS | Want to receive notifications of socket Quality of Service (QoS) changes. |
FD_GROUP_QOS | Want to receive notifications about socket group quality of service (QoS) changes, (reserved for future use on socket groups ). reserve. |
FD_ROUTING_INTERFACE_CHANGE | Want to receive routing interface change notifications for the specified destination (). |
FD_ADDRESS_LIST_CHANGE | Want to receive local address list change notifications for a socket protocol family. |
1.5 WSARecvFrom
The ?WSARecvFrom function receives datagram type data and saves the address of the data sender.
int WSARecvFrom(
SOCKETs,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
struct sockaddr FAR* lpFrom,
LPINT lpFromlen,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
?parameter:
?s: Descriptor identifying the socket.
?lpBuffers: A pointer to an array of WSABUF structures. Each WSABUF structure contains a pointer to the buffer and the size of the buffer.
?dwBufferCount: The number of WSABUF structures in the lpBuffers array.
?lpNumberOfBytesRecvd: If the receive operation completes immediately, a pointer to the number of bytes of data received.
?lpFlags: A pointer to flag bits.
?lpFrom: (optional) pointer to the buffer where the source address is stored after the overlap operation is completed.
?lpFromlen: Pointer to the size of the from buffer, only required when lpFrom is specified.
?lpOverlapped: Pointer to the WSAOVERLAPPED structure (ignored for non-overlapped sockets).
?lpCompletionRoutine: A pointer to the completion routine that is called after the receive operation is completed. (Ignored for non-overlapping sockets).
1.6 WSASendTo
The WSASendTo function uses overlapped I/O (if applicable) to send data to a specific destination.
int WSAAPI WSASendTo(
[in] SOCKET s,
[in] LPWSABUF lpBuffers,
[in] DWORD dwBufferCount,
[out] LPDWORD lpNumberOfBytesSent,
[in] DWORD dwFlags,
[in] const sockaddr *lpTo,
[in] int iTolen,
[in] LPWSAOVERLAPPED lpOverlapped,
[in] LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
?parameter:
?[in] s: Descriptor identifying a socket (possibly connected).
?[in] lpBuffers: Pointer to array of WSABUF structures. Each WSABUF structure contains a pointer to the buffer and the length of the buffer in bytes
?[in] dwBufferCount: Number of WSABUF structures in the lpBuffers array.
?[out] lpNumberOfBytesSent: Pointer to the number of bytes sent by this call if the I/O operation completed immediately. If the lpOverlapped parameter is not NULL, use NULL for this parameter to avoid potentially incorrect results. This parameter can be NULL only if the lpOverlapped parameter is not NULL.
?[in] dwFlags: Flags used to modify the behavior of WSASendTo function calls.
?[in] lpTo: Optional pointer to the target socket address in the SOCKADDR structure.
?[in] iTolen: Size (in bytes) of the address in the lpTo parameter.
?[in] lpOverlapped: for non-overlapped sockets), (pointers to WSAOVERLAPPED structures are ignored.
?[in] lpCompletionRoutine: Type _In_opt_ LPWSAOVERLAPPED_COMPLETION_ROUTINE Pointer to the completion routine that is called when the send operation is completed (non-overlapped sockets are ignored).
1.7 gethostbyname
?gethostbyname is not a socket extension function. This function obtains the IP address corresponding to the host name from the host database.
struct hostent* FAR gethostbyname(
__in const char* name
);
2 Implementation of online chat room
?The following uses message-based asynchronous sockets to implement an online chat room program with a graphical interface.
?First, create a new dialog-based project and name the project: ChatAsync.
2.1 Setting dialog box controls
?Delete all existing controls on the dialog resource, then add some controls and set their related properties. The result is as shown in the figure below:
?The IDs and descriptions of each control on the dialog box are as follows (in order of controls from top to bottom and from left to right on the dialog box).
Control name | ID | Description |
---|---|---|
Receive group box | IDC_STATIC | Mark function |
Receive edit box | IDC_EDT_RECV | Display received data |
Send group box | IDC_STATIC | Mark function |
IP Address Control | IDC_IPADDRESS1 | Allows users to enter IP addresses in dotted decimal format |
Send Edit Box | IDC_EDT_SEND | Allows the user to enter content to be sent |
Send button | IDC_BTN_SEND | Click the button to send the content in the send edit box to the recipient of the chat |
Host name edit box | IDC_EDT_HOSTNAME | Allow users to enter the host name of the other party |
2.2 Loading the socket library
?Since AfxSocketInit can only load the socket library version 1.1, and this program needs to use some functions of the socket library version 2.0, it calls the WSAStartup function to initialize the socket library used by the program. The initialization operation of the socket library should be placed in the InitInstance function in the app class.
BOOL CChatAsyncApp::InitInstance() {<!-- --> //Initialize the socket library WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, & amp;wsaData); if (err != 0) {<!-- --> return FALSE; } /* Confirm that the WinSock DLL supports 2.2.*/ /* Note that if the DLL supports versions greater */ /* than 2.2 in addition to 2.2, it will still return */ /* 2.2 in wVersion since that is the version we */ /* requested. */ if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {<!-- --> WSACleanup(); return FALSE; } ...omitted }
?Please note that this implementation file needs to include the header file: winsock2.h. For convenience, this header file can be placed in stdafx.h; in addition, this project needs to link to the ws2_32.lib import library file.
2.3 Create and initialize socket
?Next, create and initialize the socket, and add a SOCKET type member variable m_socket, which is the socket descriptor, to the dialog class. Then, add the following member function declaration in the CChatAsyncDlg class:
BOOL InitSocket();
?The implementation code is:
BOOL CChatAsyncDlg::InitSocket() {<!-- --> m_socket = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, 0); if (INVALID_SOCKET == m_socket) {<!-- --> MessageBox(_T("Failed to create socket!")); return FALSE; } 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*) & amp;addrSock, sizeof(SOCKADDR))) {<!-- --> MessageBox(_T("Binding failed!")); return FALSE; } if (SOCKET_ERROR == WSAAsyncSelect(m_socket, m_hWnd, UM_SOCK, FD_READ)) //if (SOCKET_ERROR == WSAEventSelect(m_socket, m_hWnd, UM_SOCK, FD_READ)) {<!-- --> MessageBox(_T("Registering network read event failed!")); return FALSE; } return TRUE; }
?In the implementation code, the socket is first created using the WSASocket function. Then use the bind function to bind the socket to the local IP address and port (port number 6000). Then, call WSAAsyncSelect to request a network event notification based on Windows messages. The first parameter of this function is the socket descriptor that identifies the request for network event notification; the second parameter is the window handle of the dialog box, that is, m_hWnd of CChatAsyncDlg. member; the third parameter specifies a custom message (UM_SOCK). Once the specified network event occurs, the operating system will send the custom message to notify the calling thread; the fourth parameter is the registered event, which is registered in this example A read event (FD_READ). In this way, once data arrives, the FD_READ event will be triggered, and the system will notify the calling thread through the UM_SOCK message. Therefore, by accepting the data in the response function of the message, the data can be received.
?This function can be called in the OnInitDialog function of the CChatAsyncDlg class so that the program can complete the initialization of the socket.
?In the header file of the CChatAsyncDlg class, define the custom message: UM_SOCK. The definition code is as follows:
#define UM_SOCK WM_USER + 1
2.4 Implement receiver function
?It should be noted here that after the registered event occurs, when the operating system sends a corresponding message to the calling process, it will also pass the corresponding information of the message to the calling process. In fact, the message map macro for custom messages is:
ON_MESSAGE(message, memberFxn)
?The prototype of the message response function (memberFxn) is:
afx_msg LRESULT (CWnd:: * )(WPARAM, LPARAM).
?So the information is actually passed through WPARAM and LPARAM.
?Add the following code in the header file of the CChatAsyncDlg class:
#define UM_SOCK WM_USER + 1 //Define custom message class CChatAsyncDlg : public CDialogEx {<!-- --> .....omitted afx_msg LRESULT OnSock(WPARAM wParam, LPARAM lParam);//Declare the response function of the custom message DECLARE_MESSAGE_MAP() .....omitted };
?In the implementation file, add the message mapping macro about UM_SOCK
BEGIN_MESSAGE_MAP(CChatAsyncDlg, CDialogEx) .....omitted ON_MESSAGE(UM_SOCK,OnSock) .....omitted END_MESSAGE_MAP()
?And the implementation of OnSock function
LRESULT CChatAsyncDlg::OnSock(WPARAM wParam, LPARAM lParam) {<!-- --> switch (LOWORD(lParam)) {<!-- --> case FD_READ: {<!-- --> CString str; CString strTemp; WSABUF wsabuf; wsabuf.buf = new char[200]; wsabuf.len = 200; DWORD dwRead; DWORD dwFlag = 0; SOCKADDR_IN addrFrom; int len = sizeof(SOCKADDR); if (SOCKET_ERROR == WSARecvFrom(m_socket, & amp;wsabuf, 1, & amp;dwRead, & amp;dwFlag, (SOCKADDR*) & amp;addrFrom, & amp;len, NULL, NULL)) {<!-- --> MessageBox(_T("Failed to accept data!")); return 0; } HOSTENT* pHost; pHost = gethostbyaddr((char*) & amp;addrFrom.sin_addr.S_un.S_addr, 4, AF_INET); //Note that it took some effort to go from char*→CSTRing. Char* cannot be converted to CString in the CString constructor. //But the assignment function works, magical CString psz1; //psz1=inet_ntoa(addrFrom.sin_addr); psz1 = pHost->h_name; CString psz2; psz2 = CA2T(wsabuf.buf); str.Format(_T("%s said: %s"), psz1, psz2); str + = _T("\r\ "); GetDlgItemText(IDC_EDT_RECV, strTemp); str + = strTemp; SetDlgItemText(IDC_EDT_RECV, str); break; } default: break; } return 0; }
?Please pay attention to the switch statement in the response function. When requesting network event notification based on the socket, multiple network events can be requested at the same time. In other words, not only can the FD_READ network read event be requested, but the FD_WRITE network write event can also be requested at the same time. Enter events, so switch statements need to be used to distinguish various types of events.
?By reading the low-order words of the lParam parameter, you can know the type of network event currently occurring. Then use WSARecvFrom to receive the data.
2.5 Implement the functions of the sending end
?The click message response function of the send button on the main interface is
void CChatAsyncDlg::OnBnClickedBtnSend() {<!-- --> USES_CONVERSION; DWORD dwIP; CString strSend; WSABUF wsaBuf; DWORD dwSend; int len; SOCKADDR_IN addrTo; CString sHostName; HOSTENT* pHost; if (GetDlgItemText(IDC_EDT_HOSTNAME, sHostName), sHostName == _T("")) {<!-- --> ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP); addrTo.sin_addr.S_un.S_addr = htonl(dwIP); } else {<!-- --> pHost = gethostbyname(T2A(sHostName)); if (!pHost) return; addrTo.sin_addr.S_un.S_addr = *((DWORD*)pHost->h_addr_list[0]); } addrTo.sin_family = AF_INET; addrTo.sin_port = htons(6000); //There is also a special treatment here. CT2A cannot be used, but T2A can be used. I don’t know why. //In addition, because what is passed is bytes, strlen must be used to calculate the number of bytes and then add 1 GetDlgItemText(IDC_EDT_SEND, strSend); len = strSend.GetLength(); wsaBuf.buf = T2A(strSend); wsaBuf.len = len + 1; wsaBuf.len = strlen(wsaBuf.buf) + 1; SetDlgItemText(IDC_EDT_SEND, _T("")); if (SOCKET_ERROR == WSASendTo(m_socket, & amp;wsaBuf, 1, & amp;dwSend, 0, (SOCKADDR*) & amp;addrTo, sizeof(SOCKADDR), NULL, NULL)) {<!-- --> MessageBox(_T("Failed to send data!")); return; } }
?When sending data, the IP address must first be obtained based on the host name entered by the user, which is achieved through gethostbyname; then the data is sent out through WSASendTo.
2.6 Terminating the use of the socket library
?When the APP class is destroyed, the use of the socket library should be terminated.
CChatAsyncApp::~CChatAsyncApp() {<!-- --> WSACleanup(); }
?The socket should be closed when the dialog class is destroyed.
CChatAsyncDlg::~CChatAsyncDlg() {<!-- --> if (m_socket) closesocket(m_socket); //Close the socket }
?The above is the code implementation of this program. This program implements the receiving and sending ends in the same thread. If a blocking socket is used, the thread may be suspended due to the blocking call of the WSARecvFrom function. When programming network applications, the use of asynchronous selection mechanisms can improve the performance of network applications. If combined with multi-threading technology, the performance of the network applications written will be greatly improved.