This chapter surveys three completely different subjects that don't really fit together, but don't belong anywhere else either:
?Serial communications, otherwise known as your COM port
?Remote access services, a set of Windows functions for configuring, making, and breaking dial-up connections to another computer
?Sockets, the interface to the TCP/IP protocol and the layer on top of which the various Internet protocols such as FTP and HTTP are built
Each is a subject in its own right, and the techniques are not specific to Python. We'll show enough to get you started with the right Python extensions and features and try to point you at some of the right sources to learn more.
The serial port has been a part of the standard PC since its inception, and a wide range of PC accessories, scientific, and medical devices use it; modems and mice are the most common. Recently, infrared ports have been gaining in popularity, and these are essentially just serial ports without wires. We will learn a little about serial ports and use that to talk to some external devices.
Serial data is carried as a sequence of high and low voltages over a number of wires with fairly precise timing characteristics. Fortunately for the programmer, it isn't necessary to know much about how serial ports work. Under DOS, you had to work with a very low-level interface; a certain range of bytes (starting at 0x03F8 for COM1 and 0x02F8 for COM2) contained information about the serial port. Your program would loop continually checking the values of bits and bytes in this region; some of these signaled the arrival of data, others the data values itself, and others error values. It was messy and painful to program.
Windows 3.1 exposed a higher-level API, with a layer of buffering; functions such as ReadComm and WriteComm that provide insulation from the hardware and a somewhat file-like interface; and event-driven communications, which allowed you to have messages sent to a window when data arrived at the COM port. Windows NT drops the windows message concept and completes the move to a file-like interface, using the same functions to open, read and write COM ports as regular files. It also prevents programs from accessing the serial port (i.e., those previous addresses) directly.
Example programs give the impression of reading and writing from files, but this can be misleading. Files on the disk are all there at once, and you know when you have got to the end. Furthermore, if you open a file for reading and/or writing, you know where you are in the file, and the disk operations take place in exactly the order you specify. Serial I/O is all about timing. Consider a port running at 9600 bits per second on a Pentium 400. One byte arrives every millisecond; your computer can execute more than a hundred thousand instructions between each byte arriving. Your PC can also wander off and do something else (load a file, wait for the user, open a Word document or something) while several bytes arrive at the port unnoticed.
To resolve this, the operating system provides input and output buffers. A typical size is 1024 bytes. If your program is doing something else, up to 1024 bytes will accumulate and be saved for you, so you don't have to check the port continually. If you read from the port, it returns as many bytes as you asked for from the buffer or nothing if there is no more data. There is no way to know whether data has finished arriving, or whether more might arrive in another millisecond; each application needs to define a protocol so that the application knows whether to wait for more or to stop listening. Protocols can involve fixed-size transmissions, the use of delimiters such as a carriage return to say "this message has finished arriving," or time limits.
If you want to work at a low level, you can use the CreateFile API call exposed by the Python Win32 extensions, passing in a filename of "COM1" or "COM2." You can then read from and write to this file. Communications functions are well documented in the MSDN Library. However, for common tasks, there are easier ways to do it. We'll look at two of them, using a public domain C-level extension package and a proprietary COM interface.
Our preferred route for serial I/O with Python is Roger Burnham's Serial package. This is free and comes with an installer and several examples.
The Serial package is based on a set of serial communications libraries developed by MarshallSoft (www.marshallsoft.com) over the last eight years. These libraries are available as a 16- and 32-bit DLL with identical interfaces, which makes it easy to migrate applications from 16- to 32-bit and back again.* The libraries are available as shareware packages for Visual Basic, C, and Delphi. MarshallSoft have kindly allowed their DLL to be used free of charge as part of the Python package provided a brief message is included with any distribution. The core DLL is not Open Source but has been heavily tested and won several awards.
The Serial package uses SWIG (see Chapter 22, Extending and Embedding with Visual C++ and Delphi) to create a Python DLL wrapper around the library, and provides easy-to-use Python wrappers and examples. The Python wrapper class is Open Source and provides a simple high-level API that simplifies many common communications tasks. The Serial package provides functions to open and close serial ports and to read and write from them.
The "Hello, World" of the communications field is to send an AT command to a modem and get back a response, so we will take care of this formality first. If you are not familiar with modems, they almost all speak a common command language defined by Hayes; the computer sends a line of text beginning with the letters AT and ending in a carriage return and linefeed, and the modem sends back a line of text with a response. In particular, the command ATI asks a modem to identify itself.
We tried hard to find a way of making this more interesting. Instead of using a conventional internal or external modem, we decided to make a laptop to talk to a mobile phone with a built-in modem, via its infrared port. It turned out that this didn't involve any special programming at all (IR ports are just plain old serial ports from the programmer's viewpoint) but it does far more to satisfy technolust! The console session below shows a basic conversation with the modem, which was sitting a few feet away:
>>> from Serial import Serial
>>> # create a 'configuration dictionary' with the port settings
>>> myconfig = Serial.PortDict()
>>> myconfig['port'] = Serial.COM4
|* It would be nice to think 16-bit Windows was dead in 1999, but you would be amazed how many cashstrapped hospitals and university laboratories still have old Windows 3.1 PCs collecting data.|
>>> # create and open a port with these settings
>>> myport = Serial.Port(myconfig)
>>> myport.send('ATI\015') #ask modem to identify itself
>>> print myport.read() #get back any response
Ericsson SH 888 Infrared Modem
>>> # Python supports Infrared! Cool!
The Serial package contains two classes. A PortDict holds the configuration information needed to begin using a serial port; this comes with a sensible set of defaults, and is smart enough to check the values you assign it. If you ask it to display itself at a command prompt, you can see all the parameters, many of which are self-explanatory:
baud = 9600
cmdTerm = "
cmdsEchoed = 0
dataBits = 8
debug = 0
parity = None
port = COM4
rspFixedLen = 0
rspTerm = "
rspTermPat = None
rspType = RSP_BEST_EFFORT
rxBufSize = 1024
stopBits = 1
timeOutMs = 500
txBufSize = 1024
This is passed as an argument to the constructor of a Port object, which manages the conversation. You've just seen the key methods of a Port object: open(), read(), write(), and close(). We relate what they do in more detail later.
The most widely available serial devices are mice and modems; however, there is not much likelihood you will need to write a driver for either, so we'll look instead at some laboratory instruments that together illustrate some key principles. The first stage in talking to any remote device is to learn about its communications protocol and think through the timing issues involved.
Andy maintains a data-analysis package for two devices called the Mark III and Mark IV GastrograpH, made by Medical Instruments Corporation of Switzerland. These are used to diagnose chronic stomach pain; patients have a probe inserted into their stomachs, and carry a small battery-powered device for 24 hours that captures the pH values, as well as user-input information about meals, sleep and pain. At the end of the 24-hour period, the data is downloaded to a PC. Fortunately it comes with a test signal generator, which removed the need to self-intubate while writing this chapter!
The Mark III GastrograpH is a very simple instrument from a communication viewpoint. You press a transmit button, and it sends a fixed-size block of data (essentially dumping the contents of its RAM). It neither knows nor cares if there is a PC listening at the far end.
The port settings are 9600 baud, eight data bits, one stop bit, and no parity. One byte of data thus needs 9 bits (8 data + 1 stop), so you can expect about 1067 bytes per second (9600 / (8+1)) or one per millisecond. The device sends a header of 22 bytes, followed by 24 separate blocks of 4102 bytes each (each of these is 4 KB of RAM plus a few checksums), totaling exactly 98,470 bytes. Between each block there is a 200ms pause. The whole download takes about 97 seconds.
Let's use the Serial class to capture this data. The read() method we saw earlier is actually extremely flexible; the full header is read(cnt=None, timed= FALSE). When you initialize a Serial class instance, you specify a timeout, which has a default value of half a second. The read method can be used in three totally different ways:
?MySerialPort.read() returns any data that has arrived at the serial port since you last called read() or since it was opened. The data is returned as a string.
?MySerialPort.read(1024) reads 1024 bytes from the serial port, waiting all night if necessary.
?MySerialPort.read(1024, timed=1) reads 1024 bytes from the serial port, or raise an error if that many bytes haven't before the timeout limit. The default timeout is half a second, which is ample time to receive short responses from most devices. You can specify a timeout of up to 60 seconds when initializing a port.
The first approach that occurred to us was to change one line of Serial.py to allow a two-minute timeout and read the whole lot in one command. That works, but is not desperately user-friendly; if the device isn't transmitting, you still have to wait two minutes to find out. Instead, we grabbed it in a number of chunks matching the device protocol itself. Each takes about four seconds to arrive, so set a timeout of 10 seconds. This should be long enough for the user to start Python listening, then hit the Transmit button on the device. Here's the capture function:
from Serial import Serial
#fill a config dictionary with the settings we want
cfg = Serial.PortDict()
cfg['port'] = port
cfg['baud'] = Serial.Baud9600
cfg['parity'] = Serial.NoParity
cfg['stopBits'] = Serial.OneStopBit
cfg['timeOutMs'] = 10000 # ten seconds
# create a Port object based on these settings
prt = Serial.Port(cfg)
print 'Port open?
blocks = 
#read some data
# first section is 22 bytes, followed by a pause
header = prt.read(22,timed=1)
print 'read header'
# followed by 24 blocks of 4102 bytes, and pauses
for i in range(24):
block = prt.read(4102, timed=1)
print 'read block', i, 'of 24'
alldata = string.join(blocks, ")
# close the port but print the error message
Note that a try卐xcept?/TT> handler was used to ensure that the port is always closed. Leaving a COM port open prevents other programs (or your own) from using it for some time; on Windows 95, this can persist until a reboot.
The first thing to do is try running the function without even connecting the GastrograpH to the port. This results in a timeout error after ten seconds, which is handled gracefully:
>>> import commdemos
>>> data = commdemos.captureMark3Data()
Traceback (innermost last):
commdemos.py", line 30, in captureMark3Data
header = prt.read(22, timed=1)
File "C:\Program Files\Python\Serial\Serial.py", line 456, in read
raise SioError, \
SioError: Timed out waiting for input chr 0 of 22, read ".
Having done that, you can try it for real. Call the function; then press Transmit:
>>> import commdemos
>>> data = commdemos.captureMark3Data()
Port open?BR> read header, blocks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 \
18 19 20 21 22 23 24 ..done!
The Mark III downloaded its whole memory. Capturing data was trivial; deciphering it was a lot work. The Mark IV GastrograpH, which replaces the Mark III, communicates in a different way and aims to return meaningful information. The protocol is simple: you send one byte, and it replies with another in three to four milliseconds. There are about 20 separate requests you can make; one request returns start date information, another returns the next byte in a time series, and so on. This is another common serial programming paradigm; you send a short sequence and get something back promptly. This enables us to talk to the device at the command prompt. Note that you need to specify timed=1 so that Python waits for a response; otherwise, it reads an empty string from the port long before the device has time to respond.
The Serial.Port class has a method to send a command and get a response that is highly configurable, but doesn't do quite what you want. Let's make a subclass that has a method to carry out the kind of dialog we want:
def talk(self, byte):
#send a byte, wait up to 500ms for one byte back
resp = self.read(cnt=1,timed=1)
The needed dialog requires only one three-line method. Armed with this, you can talk to the device on the command prompt in real time:
>>> from Serial import Serial
>>> cfg = Serial.PortDict()
>>> M4 = commdemos.Mark4Port(cfg)
>>> M4.talk(2) # how many hours recording planned?
>>> M4.talk(10) # how many hours took place?
>>> M4.talk(9) # how many minutes took place?
When you have limited documentation, the ability to interrogate a device in interactive mode like this is a real help.
Naturally, you won't have either of these devices available, but there are two things you can play with quite easily: serial mice and modems. With a mouse, all you need is a loop that listens to the port and displays the bytes that are generated whenever you move it. Furthermore, if you look around, a growing number of peripherals and palmtop computers offer infrared ports, and it can be fun (and potentially even useful) to attempt to talk to them.
Microsoft has provided a COM interface to the serial port in the form of an OCX. This gives less detailed control than the Python Serial package, but is adequate for the kind of examples above. It's distributed with Visual Basic, Visual Studio, and most Microsoft development tools; you need one of these packages to redistribute it. In a corporate setting this isn't usually a problem. Unlike the Serial package, it requires the Python COM framework. Let's talk to a modem with it, this time reverting to a plain old system connected with real wires:
#talks to a modem on COM1
from win32com.client import Dispatch
comm = Dispatch ('MSCOMMLib.MSComm')
comm.CommPort = 1 #COM1
comm.PortOpen = 1
comm.Output = "AT\015" # add a carriage return
inbuf = "
now = time.time()
elapsed = time.time() - now
while (string.find(inbuf, 'OK') < 0) and (elapsed < 2):
inbuf = inbuf + str (comm.Input) #remember the Unicode string!
elapsed = time.time() - now
comm.PortOpen = 0
When run, you should see your command of AT echoed, followed by the response OK. Note that you don't know how long it will take to respond, so you loop until you get the desired data or until some time limit has elapsed. This behavior was wrapped for us by Serial.py, and you could wrap it here as well if you were going to use the MSComm control a lot.
One truly amazing thing about the MSComm control is the syntax. Microsoft loves properties; we saw that the Excel and Word object models used property assignments for lots of things, but this control takes it to new limits. MSComm has nocount 'emno methods. One major reason for using properties is that they can be set at design time in the Visual Basic development environment, but the key properties are runtime only. You can argue that a PortOpen property has its uses; you can at least query it. Assigning a string to a write-only Input property, instead of providing a Write() method, is weird. Getting a return value from an Output property, and being able to ask for it only once, is even less natural. We can't see any design rational behind this. Nevertheless, it works.
There is no real need to talk to modems these days, because Windows provides a standard set of dialogs and APIs for making and breaking dial-up connections. These are collectively known as Remote Access Services, or RAS for short. Users can create and save connections that specify many things, including the number to dial, usernames and passwords, and network protocols and settings.
The dialogues vary considerably between Windows 95, 98, and NT, but the principles under the hood are the same. Windows thinks in terms of a phonebook. NT can have many phonebooks, stored in files with extension PBK; on Windows 95 and 98 there is a single default phonebook. The machine in Figure 19-1 has three entries.
Dial-Up Networking folder in Windows 98
The win32ras module supplied with PythonWin provides a number of functions to manipulate these. EnumEntries() returns a list of tuples with the names of the entries in the phonebook:
>>> import win32ras
>>> win32ras.EnumEntries() # what is in the phonebook?
[('Assi Modem Pool',), ('Demon',), ('Demon Private',)]
>>> win32ras.EnumConnections() # show what's active now
To make a connection, you need to specify a tuple of up to six strings. This matches the RASDIALPARAMS structure in Windows. The values are:
?Callback number (used if you are dialing an NT server configured to call you right back)
?Username for the remote network
?Password for the remote network
?Domain name for the remote network (only for NT)
You can obtain such a list from an existing phonebook entry with the GetEntryDialParams (PhoneBook, EntryName) function. This displays some entries but hides passwords for obvious reasons:
>>> params = win32ras.GetEntryDialParams(None, 'Demon Internet')
(('Demon Internet', ", ", 'username', ", "), None)
However, in most cases you just want to make a call, so you can provide the attributes you need yourself. Let's dial up an Internet provider, specifying the name of the phonebook entry to use:
>>> import win32ras
>>> myParams = ('Demon Internet', '0845-3535666', ", \
'username', 'password', ")
>>> (hras, success) = win32ras.Dial(None, None, myParams, None)
>>> # do your stuff on the network now
When you hit Return after Dial, Windows goes through its usual connection procedure, and control is returned to Python only after a connection is made or an error has occurred. If success is zero or greater, you can assume the connection works.
It's also possible to supply a callback function as the fourth argument to the Dial() function; this is called whenever a RAS event occurs, such as a successful connection, the line being dropped, or an error. See the win32ras documentation in the PythonWin help file for more details.
A typical use for RAS would be writing a script to connect at various times of the day, and conversely to ensure that connections are brought down again after a certain amount of time. What you can do when you get connected depends on the type of network, which is most easily specified in the various dialogs for the phonebook entry. If dialing into a corporate NT network, you could begin mapping network drives after connecting; with a TCP/IP network, you can start using Python's many TCP/IP libraries to automate web, email and FTP operations.
Our third and final topic under the theme of communications is the Sockets API. This is the programmer's interface to the TCP/IP protocol itself. Sockets are supported by all major operating systems, via a standard C-level interface. This interface is available in Python on all platforms, enabling you to write custom communications protocols. Chapter 15, Using the Basic Internet Protocols, describes a number of Python library modules that implement the standard protocols. The Python standard library contains many examples of how to write applications using sockets.
The key concepts bear some similarity to the serial communications we saw earlier: there is a file-like interface that permits reading and writing, and the same issue of not knowing when a transmission has finished. However, you can't guarantee a response in a given timeframe, and indeed responses on the Internet can vary widely from fractions of a second up to several seconds.
Sockets are great fun to experiment with, particularly in Python, where the client socket can be scripted from an interactive console session. Ideally you need two computers close to each other on the same TCP/IP network; however, one machine works fine as long as it has TCP/IP installed. Sockets can communicate between processes on the same machine.
The following function implements a short socket server:
# socketserver1.py - runs forever,
# reverse each message received.
from socket import *
HOST = " # this means local
PORT = 8578 # arbitrary, high number
# this reverses each message received, lasts forever
serversock = socket(AF_INET, SOCK_STREAM)
serversock.listen(1) # max bcklog of 1-5 connections
print 'socket listening for connections?
handlersock, addr = serversock.accept()
# now do something with the handler socket
print 'handler connected by', addr
data = handlersock.recv(1024)
print 'received', data
print 'handler closed'
if __name__ == '__main__':
You can start this server by running it from a DOS prompt. Let's step through a line at a time:
serversock = socket(AF_INET, SOCK_STREAM)
This creates the server socket. Don't worry about the constants; there are other types of sockets, but their use is unusual and definitely out of scope for this chapter.
This associates the socket with a TCP/IP hostname and port number. An empty string, the hostname (if you know it), or the result of the function gethostname() all mean the local machine. PORT is a number between 0 and 65535 and can be thought of like a radio channel or telephone line. The lower port numbers are used for standard services, so pick a big one.
This places the socket in a passive state and specifies a backlog of connections. We have told it that only one connection at a time is allowed; you can specify up to five. For socket applications use short-lived connections, this is usually plenty; the network buffers requests from subsequent sockets so that the server only has to deal with one at a time.
We now go into a loop. The server socket runs until you kill the process. The next line is where the action happens:
handlersock, addr = serversock.accept()
At this point the program waits until a connection is received from a client socket somewhere else on the network. This could be some time. We'll look at the client side in a moment. When a client does connect, the network software creates a totally new socket, which we will call a handler socket. We are
also passed the address of the socket on the far end of the connection. The handler socket is a totally different beast from the server socket; in some ways it's a pity they are implemented as the same object. In fact, it's identical to the client socket we will look at in a moment. The two are welded together, with the input of one hooked up to the output of the other. They are also communicating on another network port to the one we originally specified, something assigned randomly by TCP/IP. The original server socket can continue listening on the specified port.
At this point, it doesn't really matter what the client and handler sockets do. The server socket has handled the request, the incoming port is free, and it's now free (in theory) to handle the next incoming request. A more complex server than ours might actually set up a separate thread to handle the request.
Now let's pop over to the client side and see what is going on there. We've implemented the client side of the conversation from a console session:
>>> from socket import *
>>> clientsock = socket (AF_INET, SOCK_STREAM)
>>> clientsock.connect('tahoe', 8578)
The connect method can take the name of the machine or the IP address (represented as the usual dotted string), or 搇ocalhost?/TT> if you are talking to yourself on one machine. If you don't get an error message, you're connected. Now let's look back at the console for the server:
socket listening for connections?BR> handler connected by ('22.214.171.124', 1029)
This identifies the machine from which the connection arrived, and the port on which the client socket and handler socket are now talking.
The client is in position to send requests to the handler socket and hold a conversation. We've provided a simplistic example; the client sends a short string, and the server sends it back in uppercase. After one such exchange, the two sockets close. This is dangerously simplistic (we'll discover why later). Now they can talk; it's back to the client side:
>>> clientsock.send('hello') # returns bytes sent
>>> clientsock.recv(1024) # max bytes to receive
The conversation is over, and the server socket is now ready to accept and process the next request. It's essential to close down sockets when finished by calling the close() method. This notifies the remote socket that no more data will be sent. If your program fails to close a socket cleanly, the one at the far end of the connection may hang indefinitely.
The previous example covered the key features of how sockets get connected, but was dangerously simple. We sent a very short string, then waited a couple of seconds while we typed the call to receive a response. We got back the expected data, but were lucky: in real life, there's more to worry about.
The send() call doesn't instantly send a string, however long. It tries to send the string and returns the number of bytes sent. With a big string (e.g., a file-transfer application) or in bad network conditions, the send() call sends only a small part on each call. It might also return zero, which indicates that the network connection has been broken. The only safe way to send a string is to do it in a loop and check the return values to see what has actually been sent. Here's a function to send an arbitrary string safely:
# this sends strings over sockets more safely
def safeSend(sock, message):
msglen = len(message)
totalsent = 0
while totalsent < msglen:
sent = sock.send(msg[totalsent:])
if sent == 0:
raise RuntimeError, 'connection broken'
totalsent = totalsent + sent
At this point you've hit a fundamental problem. There is no way for the receiving socket to know how much data to expect, nor whether a message has finished arriving. You have to design your protocol so that client and handler know what to expect at every stage. There are several schemes for doing this:
?Always use fixed-size messages.
?Add a final delimiter.
?Indicate how long messages are.
?Send once and shut down. The receiving socket loops until it receives a zero and knows it's done.
One effective scheme is to make the first byte of each message a code indicating the type of message and to have a standard length for each message type, padding messages if needed. Another is to send a fixed-length message on the first exchange and include in each exchange the length of the subsequent message.
Once you've designed a protocol, you can write a safeReceive procedure to match it and ensure you get all of the data. Note that you can send binary data safely over sockets.
The design here works for short-lived connections and low traffic. If you want to handle multiple simultaneous requests, there are several different strategies, but they all involve quite a bit of work. One option is to create separate threads to handle each request (look in the thread and threading modules in the Python Library Reference). Another is to use nonblocking sockets and the select statement, which looks at a bunch of sockets and tells you which have data ready to read or are ready to accept outgoing data.*
We won't go any further into building sockets applications here. We've gone about as far as we can simply, and it gets more complex from here on.
Fortunately the work has been done for you. The Python standard library module socketserver.py provides an extremely useful generic server architecture. It includes a generic server class that you might or might not need to subclass and a request-handler class you definitely want to override. This is an excellent place to go next. Several other standard library modules define web servers based on the standard socket server.
We hope we have made it clear that sockets programming is not only feasible but also straightforward on Python. It's comparatively rare to need to develop your own socket-based protocols, and if you do need to do it, you will know far more about the subject than we can cover here.
World-class networking software has been developed in Python; examples include the Infoseek search engine, Zope, and Medusa. Zope and Medusa are discussed briefly in Chapter 3, Python on Windows.
We ought to quickly mention named pipes again. These are a Microsoft technology similar to sockets and have been part of Windows NT since its inception. As with sockets and serial ports, you read and write to named pipes. You open them by passing a suitable network path to CreateFile of the form \\mymachine\
|* Note that on Unix, select() can work with file descriptors as well as sockets; on Windows, it works only with sockets.|
pipe\mypipe. Named pipes function over whatever network protocol NT is using it might be TCP/IP, IPX, or NetBEUI. On Windows, they operate slightly faster than sockets and can also be secured. They are extensively used by Microsoft's networking APIs and back office products.
About the only reason to use named pipes is for their integrated security. Named pipes are fully securable by Windows NT, and you can rely on Windows reporting a valid and authenticated user at the other end of the pipe. For most communications applications that don't have a high requirement for security, sockets are often simpler to use, and have the added advantage of being cross-platform.
RPC is a communications technology originally developed by the DCE consortium. It builds on top of a network protocol such as TCP/IP to provide the ability to call functions on remote machines. When you design a client/server socket application, you work hard to get to the point where you can send a request to a remote object and get a response reliably. In our case, what we wanted was a remote function to reverse strings, not a load of messages. RPC handles all this, and is supported by Windows. It lets you build tightly coupled client and server applications.
On Windows, Distributed COM (which builds on top of RPC) has made the RPC programming interface pretty much obsolete. Python supports DCOM rather than RPC. For details, see Chapter 12, Advanced Python and COM.
Python provides excellent supports for communications tasks of all kinds, including serial I/O and network programming. While the concepts and interfaces in Python are similar to those in C, Python is very successful at hiding a lot of the mess you deal with working at the C level, and lets you concentrate on the problem. Since communications channels generally run much slower than the host PCs, there is no need to work at a lower level.
If you want to work with serial communications at a low level on Windows NT, there is some information in MSDN Library under Platform SDK/Windows Base Services/Files and IO/Communications.
MarshallSoft, http://www.marshallsoft.com/, makes the libraries underpinning the Serial package; evaluation libraries are available for download, as are numerous example programs in various languages.
Gordon McMillan has written a sockets how-to guide that can be found at www.python.org. Much of the information in this section was based on his guide.