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

Please notice that the subjects of the current section are discussed in detail in section 3, "The X Server Side", especially section 3.2.5.5.

X Client - X Server connection establishment

As we saw in section 1 the X client sends a connection request to the server via XOpenDisplay() and waits for the reply. As seen in section 1.2 the request has the format of xConnClientPrefix. We temporarily move to the X Server side to find how this request is processed.

The X Server accepts new client connections in WaitForSomething() , which is called by the Dispatch() server loop, which by its turn is called by X server's main() routine.

WaitForSomething() calls EstablishNewConnections(), where readyconnections, the 'ready' connections from clients, are calculated in the X Server code as:

    XFD_ANDSET (&tmask, (fd_set*)closure, &WellKnownConnections);
    XFD_COPYSET(&tmask, &readyconnections);

Then for each active connection:

    for (i = 0; i < howmany(XFD_SETSIZE, NFDBITS); i++)
    {
      while (readyconnections.fds_bits[i])

the specific connection is marked in the 'for' loop as the current connection curconn:

	curconn = XFD_FD(&readyconnections, i);

where XFD_FD is defined in Xpoll.h as:

#define XFD_FD(p,i) (((fd_set FAR *)(p))->fd_array[i])

EstablishNewConnections() uses next lookup_trans_conn() is used, which returns for a given fd to trans_conn the XtransConnInfo info connection struct (see section 3.2.5.5 ).

	if ((trans_conn = lookup_trans_conn (curconn)) == NULL

Next trans_conn is used by _XSERVTransAccept():

new_trans_conn = _XSERVTransAccept (trans_conn, &status)

_XSERVTransAccept() is implemented as TRANS(Accept), since the latter according to Xtrans.h is resolved to _XSERVTransAccept():

#ifdef XSERV_t
#if !defined(UNIXCPP) || defined(ANSICPP)
#define TRANS(func) _XSERVTrans##func

The implementation of TRANS(Accept) is found at Xtrans.c:

XtransConnInfo
TRANS(Accept) (XtransConnInfo ciptr, int *status)

{
    XtransConnInfo	newciptr;

    PRMSG (2,"Accept(%d)\n", ciptr->fd, 0, 0);

    newciptr = ciptr->transptr->Accept (ciptr, status);

    if (newciptr)
	newciptr->transptr = ciptr->transptr;

    return newciptr;
}

In the case of TRANS(SocketTCPFuncs) (see section 1.4.2) ciptr->transptr->Accept is TRANS(SocketINETAccept), which is resolved to the socket API's accept() system call.

EstablishNewConnections() calls next _XSERVTransGetConnectionNumber() as:

newconn = _XSERVTransGetConnectionNumber (new_trans_conn);

This routine is implemented as TRANS(GetConnectionNumber) and returns the file descriptor of the connection:

int
TRANS(GetConnectionNumber) (XtransConnInfo ciptr)

{
    return ciptr->fd;
}

EstablishNewConnections() calls next AllocNewConnection as:

	if (!AllocNewConnection (new_trans_conn, newconn, connect_time
#ifdef LBX
				 , StandardFlushClient,
				 CloseDownFileDescriptor, (LbxProxyPtr)NULL
#endif
				))

AllocNewConnection() calls NextAvailableClient in order to create a new client. NextAvailableClient() allocates a new client, of type _client and returns a pointer to this struct (the pointer is of type ClientPtr) to the next available slot of clients[]. NextAvailableClient() calls also InitClient() to fill some of client's fields especially requestVector to InitialVector:

    client->requestVector = InitialVector;

requestVector is the table the server holds for the given client to translate the client's request opcode to a specific request. The opcode is used as an index to the table. For instance as seen in the first section request X_MapWindow has the opcode 8. The table at the connection phase is InitialVector and then is changed to either ProcVector[] or SwappedProcVector[]:

int (* InitialVector[3]) (
	ClientPtr /* client */
    ) =
{
    0,
    ProcInitialConnection,
    ProcEstablishConnection
}

InitialVector[] has three elements (0, 1 and 2) and NextAvailableClient() sets the request type (reqType) to 1, which means that the second routine of InitialVector[], ProcInitialConnection(), opcode 1, will be used to dispatch the client's request. In the fake request except the request header, the xConnClientPrefix is also included:

    data.reqType = 1;
    data.length = (sz_xReq + sz_xConnClientPrefix) >> 2;

The request has a request header of type xReq:

/* Request structure */

typedef struct _xReq {
	CARD8 reqType;
	CARD8 data;            /* meaning depends on request type */
	CARD16 length B16;         /* length in 4 bytes quantities 
				  of whole request, including this header */
} xReq;

The data as previously noted have the form of xConnClientPrefix:

typedef struct {
    CARD8	byteOrder;
    BYTE	pad;
    CARD16	majorVersion B16, minorVersion B16;
    CARD16	nbytesAuthProto B16;	/* Authorization protocol */
    CARD16	nbytesAuthString B16;	/* Authorization string */
    CARD16	pad2 B16;
} xConnClientPrefix;

At the following figure of section 1.5 we see that the the xConnClientPrefix, prepared by the client as described in section 1 is now handled to the X Server:

NextAvailableClient() prepares then a "Fake Request", that is a request prepared by the server in behalf of the client and not by the client itself, and uses InsertFakeRequest() in order to place the 'InitialConnection' request to the input(1) buffer for the client (that is client->osPrivate->input, if client is the pointer to the _Client struct):

    if (!InsertFakeRequest(client, (char *)&data, sz_xReq))

When WaitForSomething() finally returns to Dispatch() clients may have ready requests (See section 3) and the following instruction uses ReadRequestFromClient() to return one request in client->requestBuffer (the request it moved there from client->osPrivate->input->buffer, see figure at the start of io.c):

	        result = ReadRequestFromClient(client);

Then the request type is resolved with the MAJOROP macro, which is defined in dispatch.c as:

#define MAJOROP ((xReq *)client->requestBuffer)->reqType

The request type is used next in Dispatch() as an index in the requestVector table (InitialVector[]) to find the routine that serves a specific request:

		    result = (* client->requestVector[MAJOROP])(client);

As we saw previously for the given client we examine the function that is executing is ProcInitialConnection(). This routine examines field 'byteOrder' from the X client's request and if this is different from the server's byte order (different endianess - see section 3.2.5.5) it sets the value of the field 'swapped' at the specific client's struct into the server to TRUE. Now that the X server and the X client can speak with understanding the initial request changes the request type of the client from 1 to 2:

    stuff->reqType = 2;

stuff is defined in dix.h as:

#define REQUEST(type) \
	register type *stuff = (type *)client->requestBuffer

The role of ProcInitialConnection(), which was to determine the endianess of the client stops here, however it places another 'Fake' request which on behalf of the client. Looking at InitialVector we find at index 2 that the new request is served by ProcEstablishConnection().

ProcInitialConnection() uses ResetCurrentRequest() to reexecute the current request. This routine uses AppendFakeRequest() to append a fake request as the last request from the current client, this time with opcode 2, which as found in InitialVector[] corresponds to ProcEstablishConnection(). Therefore the next time Dispatch() has clients to serve, for the current client it executes ProcEstablishConnection(). This routine checks for possible authentication required by the server and if no problem there calls SendConnSetup().

SendConnSetup() updates the client's requestvector, which is the table with the opcodes that correspond to a client's request to either SwappedProcVector or ProcVector:

    client->requestVector = client->swapped ? SwappedProcVector : ProcVector;

Both tables are found in tables.c. The difference between the two are that the 'swapped' table includes functions that swap their arguments before used because the clients come from machines with different endianness, that is different way to represent words. If the arguments need to be swapped to be in the way the server waits them the routine that is executes next comes from the non-swaped table. For instance see SProcCreateWindow() found in the swapped vector which calls ProcCreateWindow() from the vector table when its arguments are swapped.

SendConnSetup() sends its reply to the client using WriteToClient(). The reply has the form xConnSetupPrefix:

typedef struct {
    CARD8          success;
    BYTE           lengthReason; /*num bytes in string following if failure */
    CARD16         majorVersion B16, 
                   minorVersion B16;
    CARD16         length B16;  /* 1/4 additional bytes in setup info */
} xConnSetupPrefix;

At this point the X Client is connected to the X server.

The next figure highlights the xConnSetupPrefix, sent by the X Server to the client at this stage. This is part of the X client-server handshake as described in sections 1.5, 1.6 and 1.7.


(1) Note that osPrivate is of type OsCommPtr and it points to a struct _osComm. The input and output fields in this struct are related to the X Server's input and output and not to the client's. This may cause a little misunderstanding about the input's direction (from client or from server), since the fields are in a 'client' struct, which is inside the server's program.


References

Definition of the Porting Layer for the X v11 Sample Server
X Window System core protocol