ty list indicate that the caller is not interested in monitoring one of the passed lists. In itself, this is not a big deal but, if I want to reuse Select() as in the preceding code, it turns out to be rather inconvenient.

The second part of the code, which deals with restarting Select() for time-outs greater than 35 minutes, also gets rather complex, both because of the awkward test needed to detect whether a time-out has indeed occurred and because of the need to deal with the case in which milliseconds 1000 does not divide Int.Ma xValue without leaving a remainder.

We are not finished yet: the preceding code still contains comments in place of copying the input parameters and copying the results back into those parameters. One would think that this is easy: simply call a Clone() method, as one would do in Java. Unlike Java, however, .NET’s type Object (which is the ultimate base type of all types) does not provide a Clone method; instead, for a type to be cloneable, it must explicitly derive from an ICloneable interface. The formal parameter type of the lists passed to Select() is IList, which is an interface and, therefore, abstract: I cannot instantiate things of type IList, only things derived from IList. The problem is that IList does not derive from ICloneable, so there is no convenient way to copy an IList except by explicitly iterating over the list contents and doing the job element by element. Similarly, there is no method on IList that would allow it to be easily overwritten with the contents of another list (which is necessary to copy the results back into the parameters before doSelect() returns). Again, the only way to achieve this is to iterate and copy the elements one at a time.

Another problem with Select() is that it accepts lists of sockets. Lists allow the same socket to appear more than once in each list, but doing so doesn’t make sense: conceptually, what is passed are sets of sockets. So, why does Select()use lists? The answer is simple: the .NET collection classes do not include a set abstraction. Using IList to model a set is unfortunate: it creates a semantic problem because lists allow duplicates. (The behavior of Select() in the pres-

figure 1: the .net socket select() in c++.

public static void Select(List checkRead, List checkWrite,

List checkError, int microseconds);

// Server code int timeout = ...; ArrayList readList = ...; // Sockets to monitor for reading. ArrayList writeList = ...; // Sockets to monitor for writing. ArrayList errorList; // Sockets to monitor for errors.

while (!done) {

SocketList readTmp = readList.Clone();

SocketList writeTmp = writeList.Clone();

SocketList errorTmp = readList.Clone();

Select(readTmp, writeTmp, errorTmp, timeout); for (int i = 0; i < readTmp.Count; ++i) {

// Deal with each socket that is ready for reading... } for (int i = 0; i < writeTmp.Count; ++i) {

// Deal with each socket that is redy for writing... } for (int i = 0; i < errorTmp.Count; ++i) {

// Deal with each socket that encountered an error... } if ( readTmp.Count == 0 &&

writeTmp.Count == 0 && errorTmp.Count == 0) {

// No sockets are ready... }

}

 

ence of duplicates is anybody’s guess because it is not documented; checking against the actual behavior of the implementation is not all that useful because, in the absence of documentation, the behavior can change without warning.) Using IList to model a set is also detrimental in other ways: when a connection closes, the server must remove the corresponding socket from its lists. Doing so requires the server either to perform a linear search (which does not scale well) or to maintain the lists in sorted order so it can use a split search (which is more work). This is a good example of how design flaws have a tendency to spread and cause collateral damage: an oversight in one API causes grief in an unrelated API.

I will spare you the details of how to complete the wrapper code. Suffice it to say that the supposedly simple wrapper I set out to write, by the time I had added parameter copying, error handling, and a few comments, ran to nearly 100 lines of fairly complex code. All this because of a few seemingly minor design flaws:

˲ Select()overwrites its arguments.

˲ Select()does not provide a simple indicator that would allow the caller to distinguish a return because

of a time-out from a return because a socket is ready.

˲ Select()does not allow a time-out longer than 35 minutes.

˲ Select()uses lists instead of sets of sockets.

Here is what Select() could look like instead:

 

public static int Select(ISet checkRead,

ISet checkWrite, Timespan seconds, out ISet readable, out ISet writeable, out ISet error);

 

With this version, the caller provides sets to monitor sockets for reading and writing, but no error set: sockets in both the read set and the write set are automatically monitored for errors. The time-out is provided as a Timespan (a type provided by .NET) that has resolution down to 100 nanoseconds, a range of more than 10 million days, and can be negative (or null) to cover the “wait forever” case. Instead of overwriting its arguments, this version returns the sockets that are ready for reading, writing, and have encountered an error as separate sets, and it returns the number of sockets

References:

Archives