New Socket Module

Introduction

This page descibes the socket module for jython 2.5, which now has support for asynchronous or non-blocking operations. This support makes possible the use of event-based server frameworks on jython. Examples of cpython event-based frameworks are Twisted, Zope and Medusa. It is considered by some that Asynchronous IO is the only way to address The C10K problem

The socket module is pretty much feature complete. It is written to use java.nio apis as much as possible, i.e. all reads and writes are carried out through the sockets java.nio.channel based interface. However, there are some circumstances, specifically timeout operations, where the java.net.socket interfaces must be used.

The socket module is quite stable; a number of bugs in the functionality have been found and fixed.

There are necessarily some differences between the behaviour of the cpython and jython socket modules, because jython is implemented on the java socket model, which is more restrictive than the C language socket interface that cpython is based on. It is the purpose of this document to describe those differences. If you find a difference in behaviour between cpython and jython that is not documented here, and where jython is behaving differently to the cpython socket documentation, then that should be considered a bug and reported, please.

Requirements

Non-blocking support

SSL support

Cpython compatibility

The new socket module has been written to comply as closely as possible with the cpython 2.5 API for non-blocking sockets, therefore you should use the cpython socket documentation as your reference when writing code.

http://www.python.org/doc/2.5/lib/module-socket.html

If the jython module exhibits behaviour that differs from that described in the cpython documentation, then that should be considered a bug and reported as such, except for certain unavoidable differences, desribed below.

SSL Support

The module includes an implementation of client side SSL support, which is compatible with the cpython SSL API. The client side ssl example from the cpython documentation should just work.

http://docs.python.org/lib/socket-example.html

However, the cpython SSL API is extremely basic, and essentially only permits the formation of SSL wrapped sockets. It does NOT include support for any of the following

All of the above are possible, but since no other python version includes that support in the base distribution, I'm not going to do it for jython either; trying to design an API would be complex enough; implementing would be a lot of work beyond that.

If you have serious SSL or crypto requirements, then I strongly recommend using the java crypto libraries, or one of the excellent third-party crypto libraries for java, such as that from the Legion of the Bouncy Castle.

Certificate Checking

By default, the cpython socket library does not carry out certificate checking, which means that it will accept expired certificates, etc. This is acceptable when the validity of the remote certificate is not important, such as in testing.

In Java, by default, certificate checking is enabled. This means that the jython ssl routines will verify the validity of the remote certificate, and refuse to form the connection if it is not valid. If this is a problem for you, then you can get around it as described here. I have also written a blog post about this: Installing an all trusting security provider on java and jython

There is also a pure Jython implementation of this same thing here: Trusting All Certificates in Jython

By configuring your JVM

If you are using self-generated certificates for testing, then you can import those certificates into the JVM running your jython scripts, and the certificate will then be recognised. See this article from Sun on how to install certificates on a JVM.

By installing your own Security Provider

The following technique has been adapted from the ZXTM KnowledgeHub article Using the Control API with Java.

WARNING: Installing this all-trusting security provider means that all certificates will be accepted, regardless of validity! Do not use this technique if you need to verify the trustworthiness of an SSL certificate!

In order to accept all certificates, valid or not, you can install your own security provider. Here is a sample Security Provider, written in java, which trusts all certificates. This code should be saved in a file called MyProvider.java, compiled and the resulting MyProvider.class file placed in the class path.

import java.security.Security;
import java.security.KeyStore;
import java.security.Provider;
import java.security.cert.X509Certificate;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactorySpi;
import javax.net.ssl.X509TrustManager;

public class MyProvider extends Provider
{
  public MyProvider()
  {
    super("MyProvider", 1.0, "Trust certificates");
    put("TrustManagerFactory.TrustAllCertificates", MyTrustManagerFactory.class.getName());
  }

  protected static class MyTrustManagerFactory extends TrustManagerFactorySpi
  {
    public MyTrustManagerFactory()
      {}
    protected void engineInit( KeyStore keystore )
      {}
    protected void engineInit(ManagerFactoryParameters mgrparams )
      {}
    protected TrustManager[] engineGetTrustManagers()
    {
      return new TrustManager[] {new MyX509TrustManager()};
    }
  }

  protected static class MyX509TrustManager implements X509TrustManager
  {
    public void checkClientTrusted(X509Certificate[] chain, String authType)
      {}
    public void checkServerTrusted(X509Certificate[] chain, String authType)
      {}
    public X509Certificate[] getAcceptedIssuers()
      { return null; }
  }

}

To install this custom Security Provider from jython, execute the following code

import java
import MyProvider
# Install the all-trusting trust manager
java.security.Security.addProvider(MyProvider())
java.security.Security.setProperty("ssl.TrustManagerFactory.algorithm", "TrustAllCertificates")

All code which creates SSL socket connections should now accept all certicates, whether they are valid or not.

Cpython versions

It is the intention that these modules be fully API compatible with cpython, as far as is possible or sensible. This means that any cpython socket code that is syntax compatible with your selected jython version should produce identical behaviour to the same code running on cpython.

The unit-tests provided with these modules should also run on all versions of cpython. See below under unit-tests for more details.

Other I/O

The design of the cpython non-blocking API is derived from the UNIX C api, which deals with FILE DESCRIPTORS. Since file descriptors can describe any type of I/O channel on unix OSes, cpython can deal with selecting and polling on multiple channel types, such non-blocking files, pipes and named-pipes, fifos, etc.

The java model for selecting on channels is much more restrictive. There is a java abstract class, java.nio.channels.SelectableChannel, which other channel classes must subclass if they are to be multiplexed. On 1.4 JVMs, only the following classes subclass SelectableChannel.

Specifically, FileChannel's do not subclass SelectableChannel, and thus it is not possible to include files in non-blocking multiplex operations on Java platforms.

Of the two channel types listed above, these modules only support socket channels. This is because it was necessary to rewrite all of the socket creation calls to return SelectableChannels. To do so for Pipes, which are used for communication with sub-processes, it would be necessary to rewrite the jython sub-process creation modules, i.e. popen, etc, to create SelectableChannels. Although it should be reasonably straightforward to implement this, I have no plans to do this work.

Socket options

The following socket options are supported on jython

For TCP client sockets

For TCP server sockets

For UDP sockets

If an option is not explicitly listed above, it is explicitly not supported, i.e. jython cannot because java does not support the option.

Note that the level at which all but one of the options above are get and set is socket.SOL_SOCKET. The exception is TCP_NODELAY, which is at level socket.IPPROTO_TCP.

All but one of the above take either a single boolean/integer or a single integer as a parameter, so they might be called like so

mysock.setsockopt(socket.SOL_SOCKET, socket.SO_OOBINLINE, 1)
mysock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 32768)
mysock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

The exception is SO_LINGER, which (thanks to cpython's C heritage) takes a packed struct containing two values, the first is the flag to enable or disable SO_LINGER, the second specifies the linger time. The return value from getting the option is similarly packed. Here is a code snippet

import struct
linger_enabled = 1
linger_time = 10
linger_struct = struct.pack('ii', linger_enabled, linger_time)
mysock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, linger_struct)
linger_status = mysock.getsockopt(socket.SOL_SOCKET, socket.SO_LINGER)
linger_enabled, linger_time = struct.unpack('ii', linger_status)

Differences between cpython and jython

Socket shutdown

On sockets, the shutdown method is closely related to the close method, in that it terminates network communication on the socket. However, there are differences, because close relates to the file descriptor connected to the socket, whereas the shutdown method relates to the socket itself. But java doesn't have file descriptors, and doesn't have the shutdown method for sockets in general; only TCP client sockets have shutdown methods; for TCP server sockets and UDP sockets, the method to shutdown a socket is the close method. For a detailed discussion of these issues, see this blog post: Socket shutdown versus socket close on cpython, jython and java. On jython, the socket method is implemented as follows, for each type of socket

  1. TCP Client sockets. When shutdown is called on TCP client sockets, the how parameter determines how the read and write streams of the socket are treated. This is standard cpython behaviour, and follows the cpython documentation.

  2. TCP Server sockets. When shutdown is called on TCP server sockets, the effect should be to close the listening socket, and discard all incoming connections in the listen queue. However, in java there is no shutdown method for server sockets (see ServerSocket and ServerSocketChannel), there is only a close method. So in jython, the shutdown method on TCP server sockets is a no-op, i.e. it does nothing. If you want to stop accepting incoming requests, then you should call the close() method on the socket.

  3. UDP sockets. When shutdown is called on an UDP socket, as with TCP server sockets, the effect should be to stop accepting incoming packets. However, in java there is no shutdown method on udp sockets (see DatagramSocket and DatagramChannel), there is only a close method. So in jython, the shutdown method on UDP sockets is a no-op, i.e. it does nothing. If you want to stop accepting incoming packets, then you should call the close() method on the socket.

Error handling

These modules have been coded so that the error handling is as close to the error handling of cpython as possible. So, ideally, cpython socket and select code run on jython should exhibit identical behaviour to cpython, in terms of exception types raised, error numbers returned, etc.

However, due to the different semantics of java and C, there will be differences in the error handling, which will be documented here as they are discovered.

Differences in the treatment of zero timeout values

On cpython, when you specify a zero (i.e. 0 or 0.0) timeout value, the socket should behave the same as if the socket had been placed in non-blocking mode. See the cpython socket object documentation for details.

However, Java interprets a zero timeout value as an infinite timeout, i.e. the socket is placed in blocking mode.

To solve this conflict, I decided that the best thing to do with zero timeouts is to adjust them to the smallest possible timeout in java, which is 1 millisecond. So if you do socket.settimeout(0) with the new jython socket module, what you will really get is equivalent to the cpython call socket.settimeout(0.001).

This means that you may get differing exception signatures when using zero timeouts under cpython and jython.

  1. Cpython: socket operations with zero timeouts that fail will raise socket.error exceptions. This is equivalent to a -1 return from the C socket.recv call, with errno set to EAGAIN, meaning "The socket is marked non-blocking and the receive operation would block, or a receive timeout had been set and the timeout expired before data was received."
  2. Jython: socket operations with zero timeouts that fail will generate socket.timeout exceptions.

Deferred socket creation on jython

Java has different objects for Client (java.net.Socket+java.nio.channels.SocketChannel) and Server (java.net.ServerSocket+java.nio.channels.ServerSocketChannel) sockets. Jython cannot know whether to create a client or server socket until some client or server specific behaviour is observed. For clients, this is a connect() call, and for servers, this is a listen() call.

When a connect() call is made, jython then knows that this is a client socket, and to create a java.net.Socket+java.nio.channels.SocketChannel to implement the socket.

Similarly, when a listen() call is made, jython then knows that this is a server socket, and to create a java.net.ServerSocket+java.nio.channels.ServerSocketChannel to implement the socket.

Any attempt to get information about a socket before either a connect() or listen() call is made, i.e. before the implementation socket is created, may fail, although attempts are made to return already set configuration values, if possible.

For example, consider the following code

>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> s.bind( ("localhost", 8888) )
>>> print s.getsockname()
(u'127.0.0.1', 8888)
>>> s.listen(5)
>>> print s.getsockname()
(u'127.0.0.1', 8888)

The value for port number, 8888, that is returned after the bind() call, is not the port number retrieved from the underlying socket itself, because the socket does not yet exist. Instead, you are being returned the requested configuration value, the value you passed in. Only after the listen() call does the socket exist, so only then is the port number retrieved from the actual underlying socket.

This may seem a trivial concern, since the correct port is always returned. But there is one situation where it does make a difference. If you request a port number of 0, then that is a request to the operating system to pick an ephemeral port number for you, one that is guaranteed to be free. Consider this code

>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
{{{
>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> s.bind( ("localhost", 0) )
>>> print s.getsockname()
(u'127.0.0.1', 0)
>>> s.listen(5)
>>> print s.getsockname()
(u'127.0.0.1', 3357)

In this scenario, if you relied on the port number returned after the bind call but before the listen call, e.g. handing it to clients, then your clients would fail, because 0 is an invalid port number.

Similarly, the actual underlying implementation socket for client sockets does not exist until you have called one of the connect() methods.

Known issues and workarounds

Null address returned from UDP socket.recvfrom() in timeout mode

This bug is reported on the jython bug tracker: UDP recvfrom fails to get the remote address.

Description

When an UDP socket is in timeout mode, the address returned from socket.recvfrom() is always (None, -1).

This only happens in timeout mode, because that code path uses java.net.DatagramSocket.receive() to receive packets. For some reason, DatagramPackets receive()d in this way either

This bug has been reported to Sun, but is not yet public on the Java bug database.

Workaround

  1. Until the java bug is fixed, only use sockets in either blocking or non-blocking mode; the problem will not occur in this case.
  2. If you need timeouts, then until the java bug is fixed, you should probably create your own DatagramSockets, directly through the java.net APIs.