|
As an introduction to socket programming with wxWidgets, let's jump right in to an event-based client/server example. The code is fairly readable with just a basic background in socket programming. For brevity, the GUI elements of the program are omitted, and we focus only on the socket functions; the complete application is available on the CD-ROM in examples/chap18. The detailed socket API reference follows the order of the code in the example. The program performs a very simple task. The server listens for connections, and when a connection is made, the server reads ten characters from the client and then sends those same ten characters back to the client. Likewise, the client creates a connection, sends ten characters, and then receives ten characters in return. The string sent by the client is hard-coded in the example to 0123456789. The server and client programs are illustrated in Figure 18-1. Figure 18-1. Socket server and client programsThe ClientThis is the code for the client program. BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(CLIENT_CONNECT, MyFrame::OnConnectToServer) EVT_SOCKET(SOCKET_ID, MyFrame::OnSocketEvent) END_EVENT_TABLE() void MyFrame::OnConnectToServer(wxCommandEvent& WXUNUSED(event)) { wxIPV4address addr; addr.Hostname(wxT("localhost")); addr.Service(3000); // Create the socket wxSocketClient* Socket = new wxSocketClient(); // Set up the event handler and subscribe to most events Socket->SetEventHandler(*this, SOCKET_ID); Socket->SetNotify(wxSOCKET_CONNECTION_FLAG | wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG); Socket->Notify(true); // Wait for the connection event Socket->Connect(addr, false); } void MyFrame::OnSocketEvent(wxSocketEvent& event) { // The socket that had the event wxSocketBase* sock = event.GetSocket(); // Common buffer shared by the events char buf[10]; switch(event.GetSocketEvent()) { case wxSOCKET_CONNECTION: { // Fill the arry with the numbers 0 through 9 // as characters char mychar = '0'; for (int i = 0; i < 10; i++) { buf[i] = mychar++; } // Send the characters to the server sock->Write(buf, sizeof(buf)); break; } case wxSOCKET_INPUT: { sock->Read(buf, sizeof(buf)); break; } // The server hangs up after sending the data case wxSOCKET_LOST: { sock->Destroy(); break; } } } The ServerThis is the code for the server program. BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(SERVER_START, MyFrame::OnServerStart) EVT_SOCKET(SERVER_ID, MyFrame::OnServerEvent) EVT_SOCKET(SOCKET_ID, MyFrame::OnSocketEvent) END_EVENT_TABLE() void MyFrame::OnServerStart(wxCommandEvent& WXUNUSED(event)) { // Create the address - defaults to localhost:0 initially wxIPV4address addr; addr.Service(3000); // Create the socket. We maintain a class pointer so we can // shut it down m_server = new wxSocketServer(addr); // We use Ok() here to see if the server is really listening if (! m_server->Ok()) { return; } // Set up the event handler and subscribe to connection events m_server->SetEventHandler(*this, SERVER_ID); m_server->SetNotify(wxSOCKET_CONNECTION_FLAG); m_server->Notify(true); } void MyFrame::OnServerEvent(wxSocketEvent& WXUNUSED(event)) { // Accept the new connection and get the socket pointer wxSocketBase* sock = m_server->Accept(false); // Tell the new socket how and where to process its events sock->SetEventHandler(*this, SOCKET_ID); sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG); sock->Notify(true); } void MyFrame::OnSocketEvent(wxSocketEvent& event) { wxSocketBase *sock = event.GetSocket(); // Process the event switch(event.GetSocketEvent()) { case wxSOCKET_INPUT: { char buf[10]; // Read the data sock->Read(buf, sizeof(buf)); // Write it back sock->Write(buf, sizeof(buf)); // We are done with the socket, destroy it sock->Destroy(); break; } case wxSOCKET_LOST: { sock->Destroy(); break; } } } Connecting to a ServerThis section explains how to initiate a client connection to a server using the wxSockAddress and wxSocketClient classes. Socket AddressesAll socket address classes derive from the abstract base class wxSockAddress, providing a common parameter type for socket methods regardless of the address protocol being used. The wxIPV4address class provides all of the methods necessary for specifying a remote host using the current standard Internet address scheme, IPv4. A wxIPV6address class is partially implemented and will certainly be completed when IPv6 is more widely available. Note: When representing addresses as unsigned longs, network order is expected, and network order is always returned. Network order corresponds to big endian (Intel or AMD x86 architecture is little endian; Apple's architecture is big endian). Depending on how the unsigned long addresses are stored or entered, you can probably use the byte-order macro wxINT32_SWAP_ON_LE, which will swap the byte order only on little endian platforms. For example: IPV4addr.Hostname(wxINT32_SWAP_ON_LE(longAddress)); Hostname takes either a wxString for a string address (for example, www. wxwidgets.org) or an IP address in 4-byte unsigned long format (in big endian, as noted previously). Without any parameters, Hostname returns the name of the currently specified host. Service sets the remote port, using either a wxString description for a well-known port or an unsigned short for any port. Without any parameters, Service returns the port number currently chosen. IPAddress provides a dotted-decimal notation representation in a wxString of the remote host. AnyAddress sets the address to any of the addresses of the current machine. This is the same as setting an address to INADDR_ANY. Socket ClientsThe wxSocketClient class derives from wxSocketBase and inherits all of the common socket methods. The only methods added to the client class are those necessary to initiate and establish a connection to a remote server. Connect takes a wxSockAddress parameter telling the socket client the address and port for the connection. As mentioned earlier, you would use a class such as wxIPV4address rather than wxSockAddress directly. The second parameter, a boolean, defaults to true, indicating that the call to Connect should block until the connection is established. If this is done from the main GUI thread, the GUI will block while connecting. WaitOnConnect can be used after a call to Connect if Connect was told not to block. The first parameter is the number of seconds to wait, and the second parameter is the number of milliseconds to wait. If the connection succeeds or definitively fails (for example, if the host does not exist), true is returned. If a timeout occurs, false is returned. Passing -1 for the number of seconds specifies the default timeout value, which is 10 minutes unless overridden with a call to SetTimeout. Socket EventsAll socket events are filtered through one event, EVT_SOCKET. EVT_SOCKET(identifier, function) sends socket events for the socket identifier to the specified function. The function should take a wxSocketEvent parameter. The wxSocketEvent class is by itself very simple, but by providing both the event type and the socket for which the event was generated, the need to manually store socket pointers is reduced. Socket Event TypesTable 18-1 lists the event types that are returned from GetSocketEvent.
wxSocketEvent Major Member FunctionswxSocketEvent is used as a parameter to socket event handlers. GetSocket returns a wxSocketBase pointer to the socket that generated this event. GetSocketEvent returns the event type of this socket event, as per Table 18-1. Using Socket EventsIn order to use socket events, you must provide an event handler and specify which events you want to receive for processing. The wxSocketBase class gives you several methods for using events, which you can see being used in the server example program after the socket listener is created. Note that the event handling parameters affect only the socket on which they are set, so you need to specify the events you want to receive for each socket. SetEventHandler takes a reference to an event handler and an event identifier. The event identifier should correspond to an entry in the event table for the event handler class. SetNotify takes a bit-list of the socket events for which you want to be notified. For example, wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG would send an event when there is data to read on the socket or when the socket is closed. Notify takes a boolean indicating whether you want to receive events. This allows you to enable or disable events as needed without reconfiguring the events that you want to receive. Socket Status and Error NotificationsBefore discussing sending and receiving data, we describe the auxiliary methods for status and error notification so that we can refer to them from the data methods' descriptions. Close shuts down the socket, disabling further data transmission. The peer is explicitly notified that you have closed the socket. Note that events might have been queued already when you close the socket, so you must be prepared to continue receiving socket events even after closing the socket. Destroy is used instead of the delete operator because events might reach the socket after it has been deleted if delete were used. Destroy closes the socket and adds the socket to the list of objects to be deleted on idle time, after all events have been processed. Error returns TRue if an error occurred in the last operation. GetPeer returns a wxSockAddress reference containing information about the peer side of the connection, such as IP address and port. IsConnected returns true if the socket is connected and false otherwise. LastCount returns the number of bytes read or written by the last I/O call. LastError returns the last error. Note that a successful operation does not update the error code, so Error must be used first to determine whether an error occurred. Table 18-2 lists the error code values.
Ok returns true for a socket client only when the client is connected to server, and it only returns true for a socket server if the socket could bind to the port and is listening. SetTimeout specifies how long to wait, in seconds, before a blocking socket operation times out. The default value is 10 minutes. Sending and Receiving Socket DatawxSocketBase provides a variety of basic and advanced methods for reading and writing socket data. All of the read and write operations store the results of the operation and enable you to access the number of bytes read with LastCount and the last error with LastError. ReadingDiscard deletes all incoming data from the socket buffer. Peek enables you to copy data from the socket buffer without removing the data from the buffer. You must provide a buffer for the data and the maximum number of bytes to peek. Read pulls data from the socket buffer and copies it to the specified buffer, up to the maximum size specified. ReadMsg reads data sent by WriteMsg into the specified buffer, up to the maximum size specified. If the buffer becomes full, the rest of the data is discarded. ReadMsg always waits for the full message sent with WriteMsg to arrive unless an error occurs. Unread copies data from the data buffer back into the socket buffer. You must also specify how many bytes to put back. WritingWrite sends data over the socket connection; you must specify a pointer to the data and the number of bytes to send. WriteMsg is similar to Write, except that WriteMsg adds a header to the data being sent so that the call to ReadMsg on the other end will know exactly how much data to read. Note that data sent with WriteMsg should always be read by a call to ReadMsg. Creating a ServerThe wxSocketServer class adds only a few methods to the wxSocketBase class for creating a listener and accepting connections. In order to create a server, you must specify what port to listen on for incoming connections. wxSocketServer uses the same wxIPV4address class used by wxSocketClient, except without specifying a remote host. In most cases, you should call Ok after creating a socket server to verify that the socket is bound and listening. wxSocketServer Major Member FunctionswxSocketServer accepts an address object specifying the listen port, and optional socket flags (see the "Socket Flags" section later in this chapter). Accept returns a new socket connection if one is available, optionally waiting for the connection to be made or returning NULL immediately if no connections are pending. If the wait flag is specified, the GUI will block. AcceptWith works just like Accept, but you must pass in an already existing wxSocketBase object (by reference), and a boolean is returned indicating whether a connection was accepted. WaitForAccept takes a seconds parameter and a milliseconds parameter for how long to wait for a connection, returning TRue when a connection is available, or false if the time period elapses without a connection arriving. Handling a New Connection EventWhen the listening socket detects an incoming connection, a connection event is sent for processing. From the event handler, you can accept the connection and perform any necessary immediate processing. Assuming that the connection has some longevity and isn't immediately closed, you also need to specify an event handler for the new socket. Remember that a listening socket continues to listen until closed, and new sockets are created for each new connection. In the lifetime of a server program, the same listening socket can spawn thousands of new sockets. Socket Event RecapFrom the programmer's standpoint, event-based sockets are a boon for easily processing socket data, eliminating the need for creating and shutting down threads. The example program doesn't use threads, but the GUI will never block waiting for data. Because read commands are not issued until there is data to read, calls to read will immediately succeed and return the available data. If larger amounts of data need to be read, the data can be read in pieces and added to a buffer. Alternatively, a call can be made to Peek to determine how much data is available, and if not enough data has arrived, the application can simply wait for the next input event to arrive. Next, we will look at how to use different socket flags to change a socket's behavior. |
|