16.3. Network Programming in PythonNow that you know all about client/server architecture, sockets, and networking, let us try to bring this concept to Python. The primary module we will be using in this section is the socket module. Found within this module is the socket() function, which is used to create socket objects. Sockets also have their own set of methods, which enable socket-based network communication. 16.3.1. socket() Module FunctionTo create a socket, you must use the socket.socket() function, which has the general syntax: socket(socket_family, socket_type, protocol=0) The socket_family is either AF_UNIX or AF_INET, as explained earlier, and the socket_type is either SOCK_STREAM or SOCK_ DGRAM, also explained earlier. The protocol is usually left out, defaulting to 0. So to create a TCP/IP socket, you call socket.socket() like this: tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) Likewise, to create a UDP/IP socket you perform: udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) Since there are numerous socket module attributes, this is one of the exceptions where using "from module import *" is somewhat acceptable because of the number of module attributes. If we applied "from socket import *", we bring the socket attributes into our namespace, but our code is shortened considerably, i.e., tcpSock = socket(AF_INET, SOCK_STREAM) Once we have a socket object, all further interaction will occur using that socket object's methods. 16.3.2. Socket Object (Built-in) MethodsIn Table 16.1, we present a list of the most common socket methods. In the next subsection, we will create both TCP and UDP clients and servers, all of which use these methods. Although we are focusing on Internet sockets, these methods have similar meanings when using Unix sockets.
Core Tip: Install clients and servers on different computers to run networked applications
16.3.3. Creating a TCP ServerWe will first present some general pseudocode involved with creating a generic TCP server, then describe in general what is going on. Keep in mind that this is only one way of designing your server. Once you become comfortable with server design, you will be able to modify the pseudocode to operate the way you want it to: ss = socket() # create server socket All sockets are created using the socket.socket() function. Servers need to "sit on a port" and wait for requests, so they all must "bind" to a local address. Because TCP is a connection-oriented communication system, some infrastructure must be set up before a TCP server can begin operation. In particular, TCP servers must "listen" for (incoming) connections. Once this setup process is complete, a server can start its infinite loop. A simple (single-threaded) server will then sit on an accept() call waiting for a connection. By default, accept() is blocking, meaning that execution is suspended until a connection arrives. Sockets do support a non-blocking mode; refer to the documentation or operating systems textbooks for more details on why and how you would use non-blocking sockets. Once a connection is accepted, a separate client socket is returned [by accept()] for the upcoming message interchange. Using the new client socket is similar to handing off a customer call to a service representative. When a client eventually does come in, the main switchboard operator takes the incoming call and patches it through, using another line to the right person to handle their needs. This frees up the main line, i.e., the original server socket, so that the operator can resume waiting for new calls (client requests) while the customer and the service representative he or she was connected to carry on their own conversation. Likewise, when an incoming request arrives, a new communication port is created to converse directly with that client while the main one is free to accept new client connections. Core Tip: Spawning threads to handle client requests
Once the temporary socket is created, communication can commence, and both client and server proceed to engage in a dialog of sending and receiving using this new socket until the connection is terminated. This usually happens when one of the parties either closes its connection or sends an empty string to its partner. In our code, after a client connection is closed, the server goes back to wait for another client connection. The final line of code, where we close the server socket, is optional. It is never encountered since the server is supposed to run in an infinite loop. We leave this code in our example as a reminder to the reader that calling the close() method is recommended when implementing an intelligent exit scheme for the server, for example, a handler that detects some external condition whereby the server should be shut down. In those cases, a close() method call is warranted. In Example 16.1, we present tsTserv.py, a TCP server program that takes the data string sent from a client and returns it timestamped (format: "[timestamp]data") back to the client. ("tsTserv" stands for timestamp TCP server. The other files are named in a similar manner.) Example 16.1. TCP Timestamp Server (tsTserv.py)
Line-by-Line ExplanationLines 14After the Unix start-up line, we import time.ctime() and all the attributes from the socket module. Lines 613The HOST variable is blank, an indication to the bind() method that it can use any address that is available. We also choose an arbitrarily random port number, which does not appear to be used or reserved by the system. For our application, we set the buffer size to 1K. You may vary this size based on your networking capability and application needs. The argument for the listen() method is simply a maximum number of incoming connection requests to accept before connections are turned away or refused. The TCP server socket (tcpSerSock) is allocated on line 11, followed by the calls to bind the socket to the server's address and to start the TCP listener. Lines 1528Once we are inside the server's infinite loop, we (passively) wait for a connection. When one comes in, we enter the dialog loop where we wait for the client to send its message. If the message is blank, that means that the client has quit, so we would break from the dialog loop, close the client connection, and go back to wait for another client. If we did get a message from the client, then we format and return the same data but prepended with the current timestamp. The final line is never executed, but is there as a reminder to the reader that a close() call should be made if a handler is written to allow for a more graceful exit, as we discussed before. 16.3.4. Creating a TCP ClientCreating a client is much simpler than a server. Similar to our description of the TCP server, we will present the pseudocode with explanations first, then show you the real thing. cs = socket() # create client socket As we noted before, all sockets are created using socket.socket(). Once a client has a socket, however, it can immediately make a connection to a server by using the socket's connect() method. When the connection has been established, then it can participate in a dialog with the server. Once the client has completed its transaction, it may close its socket, terminating the connection. We present the code for tsTclnt.py in Example 16.2; it connects to the server and prompts the user for line after line of data. The server returns this data timestamped, which is presented to the user by the client code. Example 16.2. TCP Timestamp Client (tsTclnt.py)
Line-by-Line ExplanationLines 13After the Unix startup line, we import all the attributes from the socket module. Lines 511The HOST and PORT variables refer to the server's hostname and port number. Since we are running our test (in this case) on the same machine, HOST contains the local hostname (change it accordingly if you are running your server on a different host). The port number PORT should be exactly the same as what you set for your server (otherwise there won't be much communication[!]). We also choose the same buffer size, 1K. The TCP client socket (tcpCliSock) is allocated on line 10, followed by (an active) call to connect to the server. Lines 1323The client also has an infinite loop, but it is not meant to run forever like the server's loop. The client loop will exit on either of two conditions: the user enters no input (lines 14-16), or the server somehow quit and our call to the recv() method fails (lines 18-20). Otherwise, in a normal situation, the user enters in some string data, which is sent to the server for processing. The newly timestamped input string is then received and displayed to the screen. 16.3.5. Executing Our TCP Server and Client(s)Now let us run the server and client programs to see how they work. Should we run the server first or the client first? Naturally, if we ran the client first, no connection would be possible because there is no server waiting to accept the request. The server is considered a passive partner because it has to establish itself first and passively wait for a connection. A client, on the other hand, is an active partner because it actively initiates a connection. In other words: Start the Server First (Before Any Clients Try to Connect).In our example running of the client and server, we use the same machine, but there is nothing to stop us from using another host for the server. If this is the case, then just change the hostname. (It is rather exciting when you get your first networked application running the server and client from different machines!) We now present the corresponding (input and) output from the client program, which exits with a simple RETURN (or Enter key) keystroke with no data entered: $ tsTclnt.py The server's output is mainly diagnostic: $ tsTserv.py The "... connected from ..." message was received when our client made its connection. The server went back to wait for new clients while we continued receiving "service." When we exited from the server, we had to break out of it, resulting in an exception. The best way to avoid such an error is to create a more graceful exit, as we have been discussing. Core Tip: Exit gracefully and call server close() method
The interesting thing about this simple networked application is that we are not only showing how our data take a round trip from the client to the server and back to the client, but we also use the server as a sort of "time server," because the timestamp we receive is purely from the server. 16.3.6. Creating a UDP ServerUDP servers do not require as much setup as TCP servers because they are not connection-oriented. There is virtually no work that needs to be done other than just waiting for incoming connections. ss = socket() # create server socket As you can see from the pseudocode, there is nothing extra other than the usual create-the-socket and bind it to the local address (host/port pair). The infinite loop consists of receiving a message from a client, returning a timestamped one, then going back to wait for another message. Again, the close() call is optional and will not be reached due to the infinite loop, but it serves as a reminder that it should be part of the graceful or intelligent exit scheme we've been mentioning. One other significant different between UDP and TCP servers is that because datagram sockets are connectionless, there is no "handing off" of a client connection to a separate socket for succeeding communication. These servers just accept messages and perhaps reply. You will find the code to tsUserv.py in Example 16.3, a UDP version of the TCP server seen earlier. It accepts a client message and returns it to the client timestamped. Example 16.3. UDP Timestamp Server (tsUserv.py)
Line-by-Line ExplanationLines 14After the Unix startup line, we import time.ctime() and all the attributes from the socket module, just like the TCP server setup. Lines 612The HOST and PORT variables are the same as before, and for all the same reasons. The call socket() differs only in that we are now requesting a datagram/UDP socket type, but bind() is invoked in the same way as in the TCP server version. Again, because UDP is connectionless, no call to "listen() for incoming connections" is made here. Lines 1421Once we are inside the server's infinite loop, we (passively) wait for a message (a datagram). When one comes in, we process it (by adding a timestamp to it), then send it right back and go back to wait for another message. The socket close() method is there for show only, as indicated before. 16.3.7. Creating a UDP ClientOf the four highlighted here in this section, the UDP client is the shortest bit of code that we will look at. The pseudocode looks like this: cs = socket() # create client socket Once a socket object is created, we enter the dialog loop of exchanging messages with the server. When communication is complete, the socket is closed. The real client code, tsUclnt.py, is presented in Example 16.4. Example 16.4. UDP Timestamp Client (tsUclnt.py)
Line-by-Line ExplanationLines 13After the Unix startup line, we import all the attributes from the socket module, again, just like in the TCP version of the client. Lines 510Because we are running the server on our local machine again, we use "localhost" and the same port number on the client side, not to mention the same 1K buffer. We allocate our socket object in the same way as the UDP server. Lines 1222Our UDP client loop works in almost the exact manner as the TCP client. The only difference is that we do not have to establish a connection to the UDP server first; we simply send a message to it and await the reply. After the timestamped string is returned, we display it to the screen and go back for more. When the input is complete, we break out of the loop and close the socket. 16.3.8. Executing Our UDP Server and Client(s)The UDP client behaves the same as the TCP client: $ tsUclnt.py Likewise for the server: $ tsUserv.py In fact, we output the client's information because we can be receiving messages from multiple clients and sending replies, and such output helps by telling us where messages came from. With the TCP server, we know where messages come from because each client makes a connection. Note how the messages says, "waiting for message" as opposed to "waiting for connection." 16.3.9. socket Module AttributesIn addition to the socket.socket() function which we are now familiar with, the socket module features many more attributes that are used in network application development. Some of the most popular ones are shown in Table 16.2. For more information, we refer you to the socket Module documentation in the Python Library Reference.
|
Tuesday, November 3, 2009
Section 16.3. Network Programming in Python
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment