Socket class design and implementation

zhaozj2021-02-08  224

Socket class design and implementation

Winsock Basic knowledge is not intended to introduce Socket or Winsock's knowledge. First introduce the Winsock API function, explain the concept of blocked / non-blocking; then introduce the use of socket.

The Winsock API Socket interface is an API of network programming (usually TCP / IP protocol, or other protocols). The earliest Socket interface is a Berkeley interface, implemented in the UNXI operating system. Winsock is also a Socket model-based API and is used in the Microsoft Windows operating system class. It has increased the Windows extension function based on the Berkeley interface function. Winscok1.1 only supports TCP / IP networks, and Winsock 2.0 adds support for more protocols. Here, discuss the API on the TCP / IP network. The Socket interface consists of three types of functions: The first class is the Berkeley Socket function included in the Winsock API. Such a function is divided into two parts. The first part is a function of network I / O, such as Accept, CloseSocket, Connect, Recv, Recvfrom, Select, Send, and Sendto another part is not involved in network I / O, functions completed on local, such as bind, getPeername, getsockname, getsocketopt, htonl, htons, inet_addr, inet_nton database functions Internet information ioctlsocket, listen, ntohl, ntohs, setsocketopt, shutdow, socket, etc. the second category is to retrieve the domain name, such as communication services and protocols, such as gethostbyaddr, gethostbyname, gethostname GetProtolbyName GetProtolbyNumber, GetServerbyName, GetServbyport. The third category is Berkekley socket routine Windows-specific extension function, such as the corresponding gethostbyname WSAAsynGetHostByName (in addition to other database functions gethostname both asynchronous version), the corresponding SELECT WSAAsynSelect, determines whether the blocking function WSAIsBlocking, to obtain the last error Windsock API Information of WsagetLastError, and so on. From another angle, these functions can be divided into two categories, one is the blocking function, one is the non-blocking function. The so-called blocking function means that the program is not allowed to call another function before completing the specified task, and the transmission of this thread message will be blocked under Windows. The so-called non-blocking function means that after the operation is started, if the result can be returned immediately, otherwise the result is returned to the error message that needs to wait, and the function is returned if the task is completed. First, the asynchronous function is a non-blocking function; secondly, the database function for obtaining far information is a blocking function (so Winsock provides its asynchronous version); in the Berkeley Socket function section, no network I / O, local work function It is a non-blocking function; in the Berkeley Socket function section, the function of network I / O is a blockable function, that is, they can block execution, or may not block execution. These functions use a socket if they use the sockets to block, these functions are blocking functions; if they use the sockets, these functions are non-blocking functions. When you create a socket, you can specify whether it is blocked. By default, Berkerley's socket function and Winsock create "block" Socket. Blocking Socket becomes non-blocking under the specified operation by using the SELECT function or the WSaasynselect function. The WSaasyncselect function prototype is as follows.

INT WSAASYNCSELECT (Socket S, HWND, U_INT WMSG, LONG LEVEN); where the parameter 1 specifies the Socket handle to operate; the parameter 2 specifies a window handle; the parameter 3 specifies a message, the parameter 4 specifies the network event, It can be a combination of multiple events, such as fd_read Preparation Read FD_WRITE Preparation Write FD_OOB Extraction Data Reaches FD_ACCEPT Receive Connection FD_Connect Complete Connection FD_Close Close Socket. Use the OR operation to combine these event values, such as the fd_read | fd_write wsaasyncselect function represents the network event specified for the Socket S, if there is an event, send a message WMSG to the window HWND. Assume that a Socket S of the application specifies the monitoring of the FD_READ event, it becomes non-blocking on the FD_READ event. When the read function is called, no matter whether it is read or returned, if it returns an error message, it is still waiting, the message WMSG is sent to the window hWnd, and the application handles the message to read network data. . For the call to the asynchronous function, the result data is finally obtained with similar processes. Take the use of asynchronous versions of gethostByname Take an example. The function prototype is as follows: Handle WSaasyncgetHostByname (HWND HWND, U_INT WMSG, Const Char Far * Name, Char Far * BUF, INT BUFLEN); When calling a WSAAsyncGethostByname startup operation, not only specify the host name Name, but also a window handle HWnd, A message ID WMSG, a buffer and its length. If you cannot get the host address immediately, return an error message indicates still waiting. When the data is reached, the Winsock DLL sends a message WMSG to the window HWND to get the host address, the window process is received from the specified buffer BUF. Using asynchronous functions or non-blocking sockets, mainly in order to do not block the execution of this lever. In the case of multi-process or multi-thread, two threads can be used to complete the functionality of the asynchronous function or the non-blocking function. Socket is available in a DLL in the form of a DLL. You must call the function WSASTARTUP before calling any Winsock API, and finally, the function wsacleanup is called to clean up.

The MFC uses a function AFXSocketInit Pack the function WSASTARTUP, and the AFXSocketinit is called in the initialization function in InInStance in the Winsock application. The program does not have to call Washanup.

Socket is an abstract representation of endpoints during network communication. Socket is created in the implementation of the handle, including five information necessary for network communication: the protocol used, the local host's IP address, the local process, the protocol port of the local process, the IP address of the remote host, far process Protocol port.

To use Socket, you must first create a socket; then configure Socket as required; then, follow the requirements to receive and send data through the Socket; Finally, the program closes this socket.

To create a socket, use the socket function to get a Socket handle:

Socket_handle = socket (protocol_family. socket_type, protocol);

Among them: protocol_family specifies the protocol used by the socket, values ​​the value PF_INET, indicating the Internet (TCP / IP) protocol; socket_type refers to the connection or use of the SOCKET; the third parameter represents the use of TCP or UDP protocols. When a socket is created, Winsock will allocate memory for an internal structure, saving this socket information in this structure, whereby the protocol used by the socket connection is determined.

After creating a socket, configure the socket.

For connected customers, Winsock automatically saves local IP addresses and selection protocol ports, but must use the Connect function to configure remote IP addresses and remote protocol ports:

Result = connect (socket_handle, remote_socket_address, address_length)

Remote_socket_address is a pointer to a specific Socket structure that saves the address family, the protocol port, and the network host address for Socket.

The connection-oriented server uses bind to specify local information, use Listen and Accept to get far information.

Use the datagram or server to use BIND to specify local information to the socket, specify far information when sending or receiving data.

Bind specifies a local IP address and protocol port as follows:

Result = bind (socket_hndle, local_socket_address, address_length)

Parameter types with connect.

Function Listen listens to the port specified by bind. If you have a remote customer request connection, use Accept Receive request, create a new Socket, and save the information.

Socket_new = accept (socket_listen, socket_address, address_length)

After the socket is configured, use the socket to send or receive data.

Connected Socket uses Send to send data, RECV receives data;

Sockets using the datagram use Sendto to send data, RecVFrom receives data.

MFC provides two class CasyncSocket and CSocket to Winsockt API package MFC to encapsulate Winsock API, which provides programmers with a simpler network programming interface. CasyncSocket encapsulates Winsock API at a lower level. By default, Socket created with this class is a non-blocking socket. All operations will return immediately. If no results are obtained, return WSAEWOULDBLOCK, indicating that it is a blocking operation. Csocket is based on CasyncSocket, which is a derived class of CasyncSocket. It is also the default, Socket created by this class is a non-blocking socket, but the CSocket network I / O is blocking, which is returned after completing the task. CSocket is not based on "block" socket, but the blocking operation implemented on the "Non-block" socket, during blocking, CSocket realizes the message loop of this thread, so although it is a blocking operation, but Does not affect the message loop, that is, the user can still interact with the program.

CasyncSocket CasyncSocket encapsulates the low-level Winsock API, and its member variable M_HSocket saves its corresponding Socket handle. The method of using CasyncSocket is as follows: First, construct a CasyncSocket object in the stack or stack, for example: CasyncSocket Sock; or CASYNCSOCKET * PSOCK = New CasyncSocket; Second, call CREATE Creating a socket, for example: Create a connection-oriented socket with the default parameter Sock.create () Specifying parameter parameters Create a Socket using a datagram, local port is 30 psocket.create (30, sock_dgrm); it is three, if it is a client, use Connect to remotely; if it is a service, use Listen listens to the remote connection request. Its four, using member functions to perform network I / O. Finally, destroy CasyncSocket, the destructor calls the Close member function to close the socket. Below, analyze several functions of CasyncSocket, you can see how it encapsulates the low-level Winsock API, simplifies the operation; how it can be seen how non-blocking Socket and non-blocking operations. Create and bundle of Socket objects (1) CREATE functions First, discuss the CREATE function, and analyze how the Socket handle is created and associated with the CasyncSocket object. Create the following implementation: BOOL CAsyncSocket :: Create (UINT nSocketPort, int nSocketType, long lEvent, LPCTSTR lpszSocketAddress) {if (Socket (nSocketType, lEvent)) {if (Bind (nSocketPort, lpszSocketAddress)) return TRUE; int nResult = GetLastError (); CLOSE (); wsasetlasterror (NRESULT);} Return False;} wherein the parameter 1 represents the port of this socket, the default is 0, and if you want to create a Socket, you must specify a port number. Parameter 2 indicates the type of this socket, the default is SOCK_STREAM, indicating the connection type. The parameter 3 is a mask bit, which means that the event you want to monitor this Socket, the default is FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE. The parameter 4 represents the IP address string of this Socket, the default is NULL. CREATE Call the Socket function Create a socket and bundled it on the object referred to this to monitor the specified network event. Parameters 2 and 3 are passed to the socket function, and if you want to create a Socket, do not use the default parameter, the specified parameter 2 is SOCK_DGRM. If the previous step is successful, call Bind to the new socket allocation port and IP address.

(2) Next Socket function, Socket function analysis, which achieve the following: BOOL CAsyncSocket :: Socket (int nSocketType, long lEvent, int nProtocolType, int nAddressFormat) {ASSERT (m_hSocket == INVALID_SOCKET); m_hSocket = socket (nAddressFormat, nSocketType, nProtocolType); if (m_hSocket = INVALID_SOCKET) {CAsyncSocket :: AttachHandle (m_hSocket, this, FALSE); return AsyncSelect (lEvent);} return FALSE;} where:! Socket parameter indicates the type 1, the default is SOCK_STREAM. Parameter 2 indicates a network event that wishes to monitor, the default value is used to specify all events. The parameter 3 represents the protocol used, the default is 0. In fact, the SOCKET of the SOCK_STREAM type uses the TCP protocol, and SOCK_DGRM's socket uses the UDP protocol. The parameter 4 represents the address family (address format), the default is PF_INET (equivalent to AF_INET). For TCP / IP, the protocol and address family are similar. Before the Socket is not created, the member variable M_HSocket is an invalid Socket handle. The socket function passes the protocol, the socket type, the protocol, and other protocols to the Winsock API function Socket, creates a socket. If the creation is successful, bundle it in this refer to this. (3) ATTATCH Bundle Process is similar to other Windows objects, add a pair of new mappings in the WINSOCK map of the module thread state: This object refers to the object and the newly created Socket object. In addition, if the "Socket window" of this module thread is not created, create one, the window is used to receive the WINSOCK notification message when an asynchronous operation, and the window handle is saved in the M_HSocketWindow variable of the module thread state. The function asyncselect will specify the window for the receiving window for the network event message. The implementation of the function Attachhandle is not listed here. (4) Specifying the network event to be monitored After the bundle is completed, call AsyncSelect to specify the newly created socket to monitor the network event. AsyncSelect achieve the following: BOOL CAsyncSocket :: AsyncSelect (long lEvent) {ASSERT (m_hSocket = INVALID_SOCKET!); _AFX_SOCK_THREAD_STATE * pState = _afxSockThreadState; ASSERT (pState-> m_hSocketWindow = NULL!); Return WSAAsyncSelect (m_hSocket, pState-> m_hSocketWindow, WM_SOCKET_NOTIFY , LEVENT! = Socket_ERROR;} The function parameter LEVENT represents the network event you want to monitor.

_ AFXSockThreadState is currently the current module thread state, M_ HSocketWindow is the "Socket Window" of this module in the current thread, specifies the network event that monitors M_HSocket, such as the specified event, send a WM_Socket_notify message to the window M_HSocketWindow. The network I / O corresponding to the specified network event will be an asynchronous operation and a non-blocking operation. For example: Specify fr_read causes Receive to be an asynchronous operation. If you cannot read the data immediately, return an error WSAEWOULDBLOCK. After the data arrives, the Winsock Notification Window M_HSocketWindow causes the OnReceive to be called. Specify fr_write caused Send as an asynchronous operation, even if the data is not sent, it returns an error WSaewouldblock. After the data can be sent, the Winsock Notification Window M_HSocketWindow causes Onsend to be called. Specify fr_connect causing Connect to be an asynchronous operation, there is no connection to the error message WSAEWOULDBLOCK, after the connection is complete, the Winsock Notification window M_HSocketWindow, causes ONConnect to be called. For other online events, it will not explain it. So, when using CasyncSocket, if you create a socket using CREATE default, all network I / O is asynchronous, performing the following related functions: Onaccept, OnClose, ONCONNECT, ONOUTOFBANDDATA, ONRECEIVE, Onsend. (5) The BIND function has been created by the above process, and the socket is created. Here, call the BIND function to specify the local port and IP address to m_hsocket.

Bind to achieve the following: BOOL CAsyncSocket :: Bind (UINT nSocketPort, LPCTSTR lpszSocketAddress) {USES_CONVERSION; // address structure is configured to use the address information of SOCKADDR_IN sockAddr WinSock; memset (& sockAddr, 0, sizeof (sockAddr)); // get the address parameters Value lpstr lpszascii = T2A ((lptstr) lpszsocketaddress; // Specify is an Internet address type sockaddr.sin_family = AF_INET; if (lpszascii == null) // No specified address, automatically get a local IP address // 32 Bit's data is converted into network word sequencing sockaddr.sin_addr.s_addr = htonl (inaddr_any); Else {// Get address DWORD LRESULT = INET_ADDR (LRESULT == INADDR_NONE) {WsasetLastError (WSAEINVAL ); returnaddr.sin_addr.s_addr = LRESULT;} // If the port is 0, Winsock assigns a port (1024-5000) // Transfer 16-bit data from the host word sequential order to network word sequence Sockaddr.sin_port = htons ((u_short) nsocketport; // bind calls the Winsock API function BIND RETURN BIND ((SockAddr *) & SockAddr, SIZEOF (SockAddr));} where: Function Parameters 1 Specify the port; parameter 2 specifies one The string containing the local address, the default is NULL. The function bind first uses the structure SOCKADDR_IN constructor. The domain SIN_FAMILY of this structure represents an address format (TCP / IP with the protocol), assigning a value of AF_INET; domain sin_port represents port, if the parameter 1 is 0, Winsock assigns a port to it, range at 1024 and 5000 Between; domain SIN_ADDR is an address information, which is a consortium, where s_addr represents a string, "28.56.22.8". If the parameter does not specify an address, Winsock automatically obtains a local IP address (if there are several network cards, use one of the addresses). (6) Summarize the process of CREATE First, call the socket function to create a socket; then map the created Socket object to the CasyncSocket object (bundle together), specify the network event to be notified by this Socket, and create a "socket window" to receive Network event message, finally specify local information of Socket. The next step is to use the member function Connect connection remote host, configure the remote information of Socket. Function Connect is similar to bind, converts the specified remote address to the address information indicated by the SockAddr_in object, and then calls the WINSOCK function Connect connection remote host, configures the distant port of Socket and the far IP address.

Processing of asynchronous network event When the network event occurs, the "Socket window" receives the WM_Socket_notify message, the message processing function onsocketNotify is called. The definition and message processing of the "Socket window" is MFC implementation, which is not discussed in detail here. OnSocketNotify callback CasyncSocket member function Docallback, Docallback calls event handlers such as OnRead, OnWrite, etc. The code of Docallback is as follows: Switch (WsagetSelectEvent (LPARAM)) {case fd_read: {dword nbytes; // Get the number of bytes you can read at a time psocket-> ioctl (fionread, & nbytes); if (nbytes! = 0) pSocket-> OnReceive (nErrorCode);} break; case FD_WRITE: pSocket-> OnSend (nErrorCode); break; case FD_OOB: pSocket-> OnOutOfBandData (nErrorCode); break; case FD_ACCEPT: pSocket-> OnAccept (nErrorCode); break; Case fd_connect: psocket-> onConnect (NERRORCODE); Break; Case FD_Close: PSocket-> OnClose (NerrorCode); Break; LPARAM is a message parameter for WM_Socket_nofity, or OnSocketNotify passes the function docallback, indicating that the notification event. Function IOCTL is a member function of CasyncSocket to control the I / O of Socket. The use here means that the Receive function of this call can read the NBYTES bytes. As can be seen from the above discussion, from Creating Socket to Network I / O, CasyncSocket directly encapsulates the low-level Winsock API, simplifies Winsock programming, and implements an asynchronous interface. If you want an action to be blocked, do not specify the network event corresponding to the operation when calling CREATE. For example, I hope that Connect and Send are blocking operations. After returning after the task is completed, you can use the following statement: psocket-> create (0, sock_stream, fr_write | fr_oob | fr_accept | fr_close); This, when connect and send, If it is a user interface thread, it may block the thread message loop. Therefore, it is best to use the blocking operation in the worker thread. Csocket If you want to use blocking sockets in the user interface thread, you can use CSocket. It implements the blocking operation on the basis of non-blocking socket, and the message loop is implemented during blocking. For CSocket, handle online event notifications, oncCept, onclose, OnRecEnnect, OnSend will never be called in CSocket, and other onoutofbanddata is not encouraged in CSocket. CSocket objects After calling connect, send, accept, close, limited, these functions are completed after the task is completed (the connection is established, the data is sent, the connection request is received, the socket is closed, the data is read) return. Therefore, Connect and Send do not cause ONCONNECT and ONSEND being called.

If you override the virtual function onRecEive, OnClose, you don't actively call the Receive, Accept, Close, the corresponding virtual function is called after the network event arrives, and the virtual function should be called Receive, Accept, Close to complete the operation. Below, you will find how CSocket is blocked and the message loop will be examined in a function receive. INT CSocket :: Receive (Void * LPBUF, INT NBUFLEN, INT NFLAGS) {// m_pbblocking is a member variable of CSocket to identify if the current // blocking operation is currently being performed. But two blocking operations cannot be carried out simultaneously. if (m_pbBlocking = NULL!) {WSASetLastError (WSAEINPROGRESS); return FALSE;} // read completion data int nResult; while ((nResult = CAsyncSocket :: Receive (lpBuf, nBufLen, nFlags)) == SOCKET_ERROR) {if ( GetLastError () == wsaewouldblock) {// Enter message loop, waiting for network event fd_read if (! PumpMessages) Return socket_error;} else return socket_ERROR;} return nresult;} in: Parameters 1 Specify a buffer saving read Data; parameter 2 Specifies the size of the buffer; parameter 3 takes the value MSG_PEEK (data copy to the buffer, but does not transfer from the input queue), or MSG_OOB (processing extracted data), or MSG_PEEK | MSG_OOB. The RECEIVE function first determines if the current CSocket object is processing a blocking operation, if yes, return error WSAEINPROGRESS; otherwise, the processing of the data read is started. When reading data, if the base class CasyncSocket's Receive reads the data, then returns; otherwise, if an error is returned, and the error number is WSAEWOULDBLOCK, then the operation is blocked, so call the pumpMessage to enter the message loop waiting for data to arrive (Network event fd_read occur). After the data arrives, the message loop is quit, and then call the CasyncSocket's Receive read data until no data is readable. PumpMessages is a member function of CSocket that completes the following: (1) Set m_pbbplocking, indicating that the blocked operation.

(2) Perform a message loop, if there is an exit message loop: Receive the timing event message of the specified timer, to exit the loop, return TRUE; receive the message sent to this Socket WM_Socket_notify, network event fd_close or wait Network event, exit loop, return true; send an error or receive a WM_QUIT message, exit loop, return false; (3) In the message loop, put the wm_socket_dead message and send the WM_SOCKET_NOFITY to other Socket to the module thread state in the message loop. Notification Message list m_listsocketNotNotifications, processed after the blocking operation is completed; for other messages, the window procedure to the destination window. The CSocketFileMFC also provides a network programming mode that makes full use of the characteristics of CSocket. The basis of this mode is the CSocketFile class. The method is as follows:

First, construct a CSocket object; call the CREATE function Create a socket object (SOCK_STREAM type).

Next, if it is a client program, call the connection to the remote host; if it is a server program, please call the Listen monitoring socket port, and then call the Accept Receive request after receiving the connection request.

Then, create a CSocketFile object associated with the CSocket object, create a CSOCKETFILE object associated with the CSocketFile object, specifying the Carchive object is used to read or write. If you want to read and write, create two CARCHIVE objects.

After the creation work is complete, use the CARCHIVE object to transfer data between customers and servers.

Used, destroy the Carchive object, CSocketFile object, CSocket object.

From the previous chapter, CARCHIVE can perform operation of the two-input stream of the file by << and >> operator based on a CFILE object. So you can derive a class from CFile to implement the operating interface of the CFILE (Read and Write). Since CSocket provides blocking operation, you can read and write Socket data like reading and writing.

Here, the design and implementation of CSocketfile is analyzed.

CSocketFile's constructor and destructive implementation

Implementation of constructor

Csocketfile :: CSocketFile (csocket * psocket, bool barchivecompatible)

{

m_psocket = psocket;

M_BarchiveCompatible = BarchiveCompatible;

#ifdef _Debug

Assert (m_psocket! = Null);

AskERT (m_psocket-> m_hsocket! = Invalid_socket);

INT NTYPE = 0;

INT NTYPELEN = SizeOf (int);

Assert (m_psocket-> getsockopt (so_type, & ntype, & ntyplelen);

AskERT (NTYPE == Sock_Stream);

#ENDIF / / _DEBUG

}

among them:

The parameter 1 of the constructor points points to the associated CSocket object, saved in the member variable m_psocket;

Parameter 2 Specifies whether the object is associated with a CARCHIVE object (not independently used), and is saved in the member variable BarchiveCompatible. The deGug section is used to detect if the m_psocket is a SOCK_STREAM type.

Decree function

Csocketfile :: ~ csocketfile ()

{

}

(2) Implementation of CSocketFile

Analyze how CSocketFile uses file read and write to implement network I / O.

Document reading

UINT CSocketfile :: read (void * lpbuf, uint ncount)

{

Assert (m_psocket! = Null);

Int nread;

// csocketfile object independently

IF (! m_barchivecompatible)

{

INT NLEFT = NCOUNT;

PBYTE PBUF = (pbyte) lpbuf;

// After reading the NCOUNT byte data

While (NLEFT> 0)

{

// csocket's Receive, blocking operation, reading to data continues

NREAD = m_psocket-> Receive (PBUF, NLEFT);

IF (NREAD == Socket_ERROR)

{

INT NERROR = m_psocket-> getLastError ();

AFXTHROWFILEEXCEPTION (CFILEEXCEPTION :: generic, nerror);

Assert (false);

}

ELSE IF (Nread == 0)

{

Return ncount - NLEFT;

}

NLEFT - = NREAD;

PBUF = NREAD;

}

Return ncount - NLEFT;

}

// and a CARCHIVE object association

// Read data, how much is it?

NREAD = m_psocket-> Receive (lpbuf, ncount, 0);

IF (NREAD == Socket_ERROR)

{

INT NERROR = m_psocket-> getLastError ();

AFXTHROWFILEEXCEPTION (CFILEEXCEPTION :: generic, nerror);

Assert (false);

}

Return nread;

}

Document writing

Void CsocketFile :: Write (const void * lpbuf, uint ncount)

{

Assert (m_psocket! = Null);

// csocket's function send, blocking operation, continuing

INT nwritten = m_psocket-> send (lpbuf, ncount);

IF (nwritten == Socket_ERROR)

{

INT NERROR = m_psocket-> getLastError ();

AFXTHROWFILEEXCEPTION (CFILEEXCEPTION :: generic, nerror);

}

}

As can be seen from CSockeffile, CSocketFile can be used independently, in the read operation, because the data is divided into multiple messages, and the data that does not read to the specified length does not represent data read. After taking it. However, with carchive, only read the data to the data. As for whether the data is read, you can use the CARCHIVE's ISBuffeRemPty function to determine.

Other CFILE interfaces, csocketfile is not implemented. From the CSCocketfile design and implementation, CSocketFile is a good example of using CSocket and an example of using cfile.

转载请注明原文地址:https://www.9cbs.com/read-1695.html

New Post(0)