Site hosted by Angelfire.com: Build your free website today!

Hands-on Projects for the Linux Graphics Subsystem

New book from Christos Karayiannis

Available in Amazon Kindle format at:

amazon.com   amazon.co.uk   amazon.de   amazon.es   amazon.fr   amazon.it

Sockets implementation in the X Window client-server system

Although Section 1 is called 'The X Client side' we make an exception and temporarily we move to the X Server's side to see how the X Server's sockets are opened and then how the C client connects to them.

As we see in Section 3, called 'The X Server side', the X Server's main() at initialization calls CreateWellKnownSockets() to create the sockets to listen on for new clients. The latter calls TRANS(MakeAllCOTSServerListeners) as:

    if ((_XSERVTransMakeAllCOTSServerListeners (port, &partial,
	&ListenTransCount, &ListenTransConns) >= 0) &&
	(ListenTransCount >= 1))
macro TRANS, is defined in Xtrans.h as:
#define TRANS(func) _XSERVTrans##func
and therefore TRANS(MakeAllCOTSServerListeners) at compile time is expanded to _XSERVTransMakeAllCOTSServerListeners. Notice that COTS stnads for Connection-Oriented Transport service in contrast to CLTS, a Connection-Less Transport service, as explained in the 'X Transport Interface'.

TRANS(MakeAllCOTSServerListeners) tries to find the transport number, NUMTRANS, which is defined as:

#define NUMTRANS	(sizeof(Xtransports)/sizeof(Xtransport_table))
where Xtransports[] is defined as:

static
Xtransport_table Xtransports[] = {
#if defined(STREAMSCONN)
    { &TRANS(TLITCPFuncs),	TRANS_TLI_TCP_INDEX },
    { &TRANS(TLIINETFuncs),	TRANS_TLI_INET_INDEX },
    { &TRANS(TLITLIFuncs),	TRANS_TLI_TLI_INDEX },
#endif /* STREAMSCONN */
#if defined(TCPCONN)
    { &TRANS(SocketTCPFuncs),	TRANS_SOCKET_TCP_INDEX },
#if defined(IPv6) && defined(AF_INET6)
    { &TRANS(SocketINET6Funcs),	TRANS_SOCKET_INET6_INDEX },
#endif /* IPv6 */
    { &TRANS(SocketINETFuncs),	TRANS_SOCKET_INET_INDEX },
#endif /* TCPCONN */
#if defined(DNETCONN)
    { &TRANS(DNETFuncs),	TRANS_DNET_INDEX },
#endif /* DNETCONN */
#if defined(UNIXCONN)
#if !defined(LOCALCONN)
    { &TRANS(SocketLocalFuncs),	TRANS_SOCKET_LOCAL_INDEX },
#endif /* !LOCALCONN */
    { &TRANS(SocketUNIXFuncs),	TRANS_SOCKET_UNIX_INDEX },
#endif /* UNIXCONN */
#if defined(OS2PIPECONN)
    { &TRANS(OS2LocalFuncs),	TRANS_LOCAL_LOCAL_INDEX },
#endif /* OS2PIPECONN */
#if defined(LOCALCONN)
    { &TRANS(LocalFuncs),	TRANS_LOCAL_LOCAL_INDEX },
#ifndef sun
    { &TRANS(PTSFuncs),		TRANS_LOCAL_PTS_INDEX },
#endif /* sun */
#ifdef SVR4
    { &TRANS(NAMEDFuncs),	TRANS_LOCAL_NAMED_INDEX },
#endif
#ifndef sun
#if !defined(__SCO__) && !defined(__UNIXWARE__)
    { &TRANS(ISCFuncs),		TRANS_LOCAL_ISC_INDEX },
#endif
    { &TRANS(SCOFuncs),		TRANS_LOCAL_SCO_INDEX },
#endif /* sun */
#endif /* LOCALCONN */
};

and Xtransport_table is defined as:

typedef struct _Xtransport_table {
    Xtransport	*transport;
    int		transport_id;
} Xtransport_table;

Xtransport is given in the white box that follows.

NUMTRANS therefore depends on the number of transports defined in the current X Server source. For instance if TCPCONN and UNIXCONN are defined, the number of transports is two. The transport macros are defined in /usr/include/xorg/xorg-server.h. For my specific Ubuntu Linux distribution I take the following results when I seek those macros in xorg-server.h:

Since the corresponding flags are set TCP and UNIX socket connections are supported.

As we previously saw in section 1.2, where the TRANS functions are discussed from the side of the X client, TRANS(SocketTCPFuncs) are used when TCPCONN is defined and TRANS(SocketUNIXFuncs) when UNIXCONN is defined.

TRANS(SocketTCPFuncs) for instance is defined as:

Xtransport	TRANS(SocketTCPFuncs) = {
	/* Socket Interface */
	"tcp",
        TRANS_ALIAS,
#ifdef TRANS_CLIENT
	TRANS(SocketOpenCOTSClient),
#endif /* TRANS_CLIENT */
#ifdef TRANS_SERVER
	tcp_nolisten,
	TRANS(SocketOpenCOTSServer),
#endif /* TRANS_SERVER */
#ifdef TRANS_CLIENT
	TRANS(SocketOpenCLTSClient),
#endif /* TRANS_CLIENT */
#ifdef TRANS_SERVER
	TRANS(SocketOpenCLTSServer),
#endif /* TRANS_SERVER */
#ifdef TRANS_REOPEN
	TRANS(SocketReopenCOTSServer),
	TRANS(SocketReopenCLTSServer),
#endif
	TRANS(SocketSetOption),
#ifdef TRANS_SERVER
	TRANS(SocketINETCreateListener),
	NULL,		       			/* ResetListener */
	TRANS(SocketINETAccept),
#endif /* TRANS_SERVER */
#ifdef TRANS_CLIENT
	TRANS(SocketINETConnect),
#endif /* TRANS_CLIENT */
	TRANS(SocketBytesReadable),
	TRANS(SocketRead),
	TRANS(SocketWrite),
	TRANS(SocketReadv),
	TRANS(SocketWritev),
	TRANS(SocketDisconnect),
	TRANS(SocketINETClose),
	TRANS(SocketINETClose),
	};

where Xtransport is defined as:

typedef struct _Xtransport {
    char	*TransName;
    int		flags;

#ifdef TRANS_CLIENT

    XtransConnInfo (*OpenCOTSClient)(
	struct _Xtransport *,	/* transport */
	char *,			/* protocol */
	char *,			/* host */
	char *			/* port */
    );

#endif /* TRANS_CLIENT */

#ifdef TRANS_SERVER
    char **	nolisten;
    XtransConnInfo (*OpenCOTSServer)(
	struct _Xtransport *,	/* transport */
	char *,			/* protocol */
	char *,			/* host */
	char *			/* port */
    );

#endif /* TRANS_SERVER */

#ifdef TRANS_CLIENT

    XtransConnInfo (*OpenCLTSClient)(
	struct _Xtransport *,	/* transport */
	char *,			/* protocol */
	char *,			/* host */
	char *			/* port */
    );

#endif /* TRANS_CLIENT */

#ifdef TRANS_SERVER

    XtransConnInfo (*OpenCLTSServer)(
	struct _Xtransport *,	/* transport */
	char *,			/* protocol */
	char *,			/* host */
	char *			/* port */
    );

#endif /* TRANS_SERVER */


#ifdef TRANS_REOPEN

    XtransConnInfo (*ReopenCOTSServer)(
	struct _Xtransport *,	/* transport */
        int,			/* fd */
        char *			/* port */
    );

    XtransConnInfo (*ReopenCLTSServer)(
	struct _Xtransport *,	/* transport */
        int,			/* fd */
        char *			/* port */
    );

#endif /* TRANS_REOPEN */


    int	(*SetOption)(
	XtransConnInfo,		/* connection */
	int,			/* option */
	int			/* arg */
    );

#ifdef TRANS_SERVER
/* Flags */
# define ADDR_IN_USE_ALLOWED	1

    int	(*CreateListener)(
	XtransConnInfo,		/* connection */
	char *,			/* port */
	unsigned int		/* flags */
    );

    int	(*ResetListener)(
	XtransConnInfo		/* connection */
    );

    XtransConnInfo (*Accept)(
	XtransConnInfo,		/* connection */
        int *			/* status */
    );

#endif /* TRANS_SERVER */

#ifdef TRANS_CLIENT

    int	(*Connect)(
	XtransConnInfo,		/* connection */
	char *,			/* host */
	char *			/* port */
    );

#endif /* TRANS_CLIENT */

    int	(*BytesReadable)(
	XtransConnInfo,		/* connection */
	BytesReadable_t *	/* pend */
    );

    int	(*Read)(
	XtransConnInfo,		/* connection */
	char *,			/* buf */
	int			/* size */
    );

    int	(*Write)(
	XtransConnInfo,		/* connection */
	char *,			/* buf */
	int			/* size */
    );

    int	(*Readv)(
	XtransConnInfo,		/* connection */
	struct iovec *,		/* buf */
	int			/* size */
    );

    int	(*Writev)(
	XtransConnInfo,		/* connection */
	struct iovec *,		/* buf */
	int			/* size */
    );

    int	(*Disconnect)(
	XtransConnInfo		/* connection */
    );

    int	(*Close)(
	XtransConnInfo		/* connection */
    );

    int	(*CloseForCloning)(
	XtransConnInfo		/* connection */
    );

} Xtransport;

where XtransConnInfo is defined as a pointer to struct _XtransConnInfo:

typedef struct _XtransConnInfo *XtransConnInfo;
and _XtransConnInfo is defined as:

struct _XtransConnInfo {
    struct _Xtransport     *transptr;
    int		index;
    char	*priv;
    int		flags;
    int		fd;
    char	*port;
    int		family;
    char	*addr;
    int		addrlen;
    char	*peeraddr;
    int		peeraddrlen;
};

Next TRANS(OpenCOTSServer) is called from TRANS(MakeAllCOTSServerListeners). This is an encapsulation of TRANS(Open):

    return TRANS(Open) (XTRANS_OPEN_COTS_SERVER, address);
As we read from TRANS(Open) comment "TRANS(Open) does all of the real work opening a connection".

TRANS(Open) calls (TRANS(ParseAddress) to parse the address and acquire the protocol, host and port:

    if (TRANS(ParseAddress) (address, &protocol, &host, &port) == 0)
From the comment of this routine we recall the address format: "the address is a string formatted as "protocol/host:port".

Then for XTRANS_OPEN_COTS_SERVER passed as first argument the TRANS(Open) code expands to:

    case XTRANS_OPEN_COTS_SERVER:
#ifdef TRANS_SERVER
	ciptr = thistrans->OpenCOTSServer(thistrans, protocol, host, port);
#endif /* TRANS_SERVER */
By comparing the Xtransport struct with TRANS(SocketTCPFuncs), that is of Xtransport type, we find that thistrans->OpenCOTSServer() for TRANS(SocketTCPFuncs) corresponds to TRANS(SocketOpenCOTSServer).

TRANS(SocketOpenCOTSServer) calls TRANS(SocketSelectFamily), which locates the address family by looking up the transport name, for instance "tcp" at TRANS(SocketTCPFuncs) with the Sockettrans2devtab[] field that also includes the "tcp" as the transport name. Sockettrans2devtab[] is defined as:

static Sockettrans2dev Sockettrans2devtab[] = {
#ifdef TCPCONN
    {"inet",AF_INET,SOCK_STREAM,SOCK_DGRAM,0},
#if !defined(IPv6) || !defined(AF_INET6)
    {"tcp",AF_INET,SOCK_STREAM,SOCK_DGRAM,0},
#else /* IPv6 */
    {"tcp",AF_INET6,SOCK_STREAM,SOCK_DGRAM,0},
    {"tcp",AF_INET,SOCK_STREAM,SOCK_DGRAM,0}, /* fallback */
    {"inet6",AF_INET6,SOCK_STREAM,SOCK_DGRAM,0},
#endif
#endif /* TCPCONN */
#ifdef UNIXCONN
    {"unix",AF_UNIX,SOCK_STREAM,SOCK_DGRAM,0},
#if !defined(LOCALCONN)
    {"local",AF_UNIX,SOCK_STREAM,SOCK_DGRAM,0},
#endif /* !LOCALCONN */
#endif /* UNIXCONN */
};

By comparing Sockettrans2devtab[] and TRANS(SocketTCPFuncs) we make the conclusion that for TCPCONN in the case that "tcp" is used as the transport name included in TRANS(SocketTCPFuncs) and in the most common case that IPv6 is not defined the following lines hold true in Sockettrans2devtab[]:

#ifdef TCPCONN
    {"inet",AF_INET,SOCK_STREAM,SOCK_DGRAM,0},
and therefore AF_INET is used as the transport family. Notice that AF stands for Address Family.

TRANS(SocketOpenCOTSServer) calls next TRANS(SocketOpen), which mainly makes a socket() call and sets some socket options if it needs to. socket() is called as:

    if ((ciptr->fd = socket(Sockettrans2devtab[i].family, type,
	Sockettrans2devtab[i].protocol)) < 0

Returning back to TRANS(MakeAllCOTSServerListeners), the next function it calls is TRANS(CreateListener), which just returns the CreateListener specific function of the transport functions that were selected previously. For instance TRANS(SocketINETCreateListener), if TRANS(SocketTCPFuncs) was selected:

    return ciptr->transptr->CreateListener (ciptr, port, flags);
TRANS(SocketINETCreateListener) mainly creates the socket address of the X Server that will accept connections from the X Clients and then calls TRANS(SocketCreateListener), which makes use of the bind() socket call to assign this address to the previously created socket. Socket options are also set, if it is needed, in TRANS(SocketCreateListener), and finally the listen() socket call is used to wait for new clients.

The X Server's socket address - TCP/IP transport

Before bind() is called the X Server's socket address must be formed. For the current thread, where "tcp" is used as the transport, the following part of code in TRANS(SocketCreateListener) could be executed (other similar part of code could run instead):


    sockname.sin_family = AF_INET;
    sockname.sin_port = htons (sport);
    sockname.sin_addr.s_addr = htonl (INADDR_ANY);
Where sockname is of type sockaddr_in, which for most UNIX-likes are generally defined as:
struct sockaddr_in {
        short int sin_family;         // set to AF_INET
        unsigned short int sin_port;  // Port number
        struct in_addr sin_addr;      // Internet address
        unsigned char sin_zero[8];    //set to all zeros
}
for instance for Linux this is defined in header file in.h as:
/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__   16              /* sizeof(struct sockaddr)      */
struct sockaddr_in {
  sa_family_t           sin_family;     /* Address family               */
  __be16                sin_port;       /* Port number                  */
  struct in_addr        sin_addr;       /* Internet address             */

  /* Pad to size of `struct sockaddr'. */
  unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -
                        sizeof(unsigned short int) - sizeof(struct in_addr)];
};
The previous code part is executed when the Address Faminly is AF_INET:
    if (Sockettrans2devtab[ciptr->index].family == AF_INET) {
which is TRUE for the current thread, as seen previously, and naturally the socket's address family is set to AF_INET:

    sockname.sin_family = AF_INET;
To the second fields of sockname it is assigned the port of the X Server listening socket:

    sockname.sin_port = htons (sport);
For htons() as we read at its man page:

The htons() function converts the unsigned short integer hostshort from host byte order to network byte order. The reason htons() is used is the difference in the byte order representation that may exist between server and client. The orders are better known as big endianess and little endianess and are discussed in other sections of site, for instance section 3.2.2.5.1.

The port passed as the second argument in TRANS(SocketINETCreateListener) was originated by the first argument of TRANS(MakeAllCOTSServerListeners), which called TRANS(SocketINETCreateListener). Looking even backwards CreateWellKnownSockets() called TRANS(SocketINETCreateLister) as:

    sprintf (port, "%d", atoi (display));

    if ((_XSERVTransMakeAllCOTSServerListeners (port, &partial,
	&ListenTransCount, &ListenTransConns) >= 0) &&
	(ListenTransCount >= 1))
where we find that port is actually an ascii-to-integer form of display. The TRANS(SocketINETCreateListener) comment explains:
* The port that is passed here is really a string containing the idisplay
* from ConnectDisplay().
In my opinion this comment is not quite right because the ConnectDisplay() as its name declares is a routine aimed for the X Client that connects to the X Server and not for the X Server itself.. What the comment probably means is that the port number of the (listening) X Server, which depends on the display number used by the X Server, is specified when the X Server starts, by an argument that reminds us of the display format of the X Client that connect to the X Server.

To make more specific we remind that the X Client in order to make a connection to the X Server could use a command line like the following:

DISPLAY=tcp/192.168.1.101:0.0 ./client_program
or:
./client_program
where display is 0, or:
DISPLAY=tcp/192.168.1.101:1.0 ./client_program
or
DISPLAY=:1.0 ./client_program
where the display is 1.

Most X Client programs, for instance xeyes, xterm, xclock, etc specify the display with a '-display' argument, without relying in the DISPLAY environment variable as previously, for instance:

xterm -display :0.0
In a similar way the X Client specifies the display (the X Server that connects to), the X Server specifies its display number it represents. For instance with the ps command I find that for my X Server (/usr/bin/X) a display '0' is specified as '/usr/bin/X :0':

Also in section 3.2.2.5.2 we start a second X Server display ':1' is assigned to start a second X Server as '/usr/bin/X :1'. It becomes clear that the X Server can start with a specific display number or as in the case of the X Client display '0' is used as the default if no other display is specified.

As we mentioned previously the port number is the numeric format of the display. This variable is defined in opaque.h

extern char *display;
In main() variable display obtains its default value with the following instruction:
    display = "0";
Other than the string 'display' variable we examine in the current text display as a variable name is used at least two more times in the X Window system code (see the 'gold' frame in section 1.5, where 'display' and 'Display' from at the X Client's code is discussed, and then there is also the idisplay, a part of display). This naturally creates some confusion about display.

As we mentioned before with the example of starting a second X Server the display of a certain X Server process is assigned by the number that follows a colon (':') in the command line. As we read from the X Server man page:

:displaynumber
         The X server runs as the given displaynumber, which by default is 0. If multiple X servers are to run simultaneously on a host, each must have a unique display number. See the DISPLAY NAMES section of the X(7) manual page to learn how to specify which display number clients should try to use.

Therefore display is set to "0" in the source code of main(), however this is altered in the command line, with the instruction that starts the X Server, and the display number is declared after the X Server processes' executable name, prepended by a colon. The main routine places the new value in display with the ProcessCommandLine().

The specific value of display "0", "1" or another value becomes the port number, when main() calls CreateWellKnownSockets() and the display value is passed to port:

    sprintf (port, "%d", atoi (display));
As we mentioned previously CreateWellKnownSockets() calls TRANS(MakeAllCOTSServerListeners) and this TRANS(CreateListener), which selects the transport specific CreateListener function. For TRANS(SocketCreateListener) this is TRANS(SocketINETCreateListener). In the source code of the latter we find the following instruction (or a similar, depending in the case):
	/* fixup the server port address */
	tmpport = X_TCP_PORT + strtol (port, (char**)NULL, 10);
where X_TCP_PORT is defined as 6000 in Xproto.h:
#define X_TCP_PORT 6000     /* add display number */
This explains why the first X Server always listens to port 6000, the second to port 6001, the third to port 6002, e.t.c.

The final part of the X Server's socket address the IP address of the X Server. With following instruction:


    sockname.sin_addr.s_addr = htonl (INADDR_ANY);
INADDR_ANY is used instead a specific IP address and this permits the server to to allow clients to connect using any one of the host's IP addresses. The function htonl() is similar to htons() for long instead of short integers.

The X Server's socket address - UNIX domain transport

A similar thread with the tcp/ip socket case, previously examined, takes place in the Unix domain sockets: main() calls CreateWellKnownSockets() and this TRANS(MakeAllCOTSServerListeners). The 'for' loop inside TRANS(MakeAllCOTSServerListeners), previously run for TCPCONN, iterates now for UNIXCONN. Recall that NUMTRANS for the Xtransports[] table we examine is 2. Inside the loop TRANS(MakeAllCOTSServerListeners) calls TRANS(OpenCOTSServer), an encapsulation of TRANS(Open), which calls (TRANS(ParseAddress) and TRANS(SocketOpen). The latter mainly makes a socket call, using AF_UNIX as the address family, because this is indicated by the index in Sockettrans2devtab[] returned by TRANS(SocketSelectFamily).

By returning to TRANS(MakeAllCOTSServerListeners) function TRANS(CreateListener) is called, which for TRANS(SocketUNIXFuncs) is resolved to TRANS(SocketUNIXCreateListener). Struct TRANS(SocketUNIXFuncs) is defined as:

Xtransport	TRANS(SocketUNIXFuncs) = {
	/* Socket Interface */
	"unix",
#if !defined(LOCALCONN)
        TRANS_ALIAS,
#else
	0,
#endif
#ifdef TRANS_CLIENT
	TRANS(SocketOpenCOTSClient),
#endif /* TRANS_CLIENT */
#ifdef TRANS_SERVER
#if !defined(LOCALCONN)
	unix_nolisten,
#else
	NULL,
#endif
	TRANS(SocketOpenCOTSServer),
#endif /* TRANS_SERVER */
#ifdef TRANS_CLIENT
	TRANS(SocketOpenCLTSClient),
#endif /* TRANS_CLIENT */
#ifdef TRANS_SERVER
	TRANS(SocketOpenCLTSServer),
#endif /* TRANS_SERVER */
#ifdef TRANS_REOPEN
	TRANS(SocketReopenCOTSServer),
	TRANS(SocketReopenCLTSServer),
#endif
	TRANS(SocketSetOption),
#ifdef TRANS_SERVER
	TRANS(SocketUNIXCreateListener),
	TRANS(SocketUNIXResetListener),
	TRANS(SocketUNIXAccept),
#endif /* TRANS_SERVER */
#ifdef TRANS_CLIENT
	TRANS(SocketUNIXConnect),
#endif /* TRANS_CLIENT */
	TRANS(SocketBytesReadable),
	TRANS(SocketRead),
	TRANS(SocketWrite),
	TRANS(SocketReadv),
	TRANS(SocketWritev),
	TRANS(SocketDisconnect),
	TRANS(SocketUNIXClose),
	TRANS(SocketUNIXCloseForCloning),
	};

#endif /* UNIXCONN */

TRANS(SocketUNIXCreateListener) calls trans_mkdir() to create the Unix domain socket's directory, UNIX_DIR, which is defined as:

#define UNIX_DIR "/tmp/.X11-unix"
Also it sets the socket address family to AF_UNIX:
sockname.sun_family = AF_UNIX;
and uses set_sun_path() as:

	if (set_sun_path(port, UNIX_PATH, sockname.sun_path) != 0) {
set_sun_path() is an encapsulation of the libc call:
sprintf(path, "%s%s", upath, port);
Also
UNIX_PATH is defined as:
#define UNIX_PATH "/tmp/.X11-unix/X"
As we mentioned previously port takes the value of display at CreateWellKnownSockets():
    sprintf (port, "%d", atoi (display));
and display either takes its default value "0" at main():
    display = "0";
or ProcessCommandLine(), called by main(), assigns a value from the X Server executable's arguments.

Therefore the sockname.sun_path, which indicates the file descriptor of the Unix domain sockets take the following values:

/tmp/.X11-unix/X0 for the first display

/tmp/.X11-unix/X1 for the second display

/tmp/.X11-unix/X2 for the third display

e.t.c.

The X client program client_program would connect to the first display as:

DISPLAY=unix/:0.0 ./client_program
or
DISPLAY=:0.0 ./client_program
or
./client_program

At section 3.2.1 we see how the X Server prepares the Unix Domain and TCP/IP sockets that listen for new clients.

At section 3.2.5.5 we see how the X Server uses TRANS(Accept) to perform the accept() socket call, in order to receive new client connections that are served with read(), readv(), write() and writev() socket calls.

Also previously at section 1.2 we followed the X Client thread in the source code for using the read(), write(), etc socket calls from the client's side.

REFERENCES:
X Transport Interface