Network Programming - select Model (Summary)

Keywords: C socket

Why use the select model?

Answer: Solve the blocking problem of accept(), recv(), send() in the basic C/S model

Differences between select model and C/S model

  • accept() in the C/S model blocks socket s that have been silly to link to
  • The select model only solves the problem of accept() being silly, not the problem of recv(),send() execution blocking.

In fact, the select model solves the problem of implementing multiple client links and communicating with multiple clients separately.
Both models have a recv(),send() execution blocking problem

  • Because of the server side, the client does not need (the client has only one socket and can recv and send simultaneously by adding threads)

select model logic

  1. Load all sockets (server + client) into an array
  2. Traversing socket arrays by select()
  3. Take out the corresponding sockets and put them in another array (all responding sockets)
  4. Centralized handling of socket arrays with responses
  5. Server socket response: Client link, call accept()
  6. Client socket response: Client communication, call send() or recv()
graph TD
A(Loaded with all socket array)==>|foreach|B(Responsive socket)
B(Responsive socket)==>C[Server Side socket]
B(Responsive socket)==>D[Client socket]
C[Server Side socket]==>E{"accept()"}
D[Client socket]==>F{"recv()or send()"}

select()

fd_set

Role: Define a structure for socket s

#ifndef FD_SETSIZE
#Define FD_SETSIZE 64 /* Default 64 */
#endif /* FD_SETSIZE */

typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

The default socket size is 64 and you can give a larger value by declaring the macro in front of the winsock2.h header file

#define FD_SETSIZE 128
#include <WinSock2.h>

Because the principle is to keep iterating through the detection, the more efficient the lower the delay, the better the size.
Selectect model application is small user access.

Four action macros for fd_set
Operational Macros Effect Code
FD_ZERO Zero Client socket Collection FD_ZERO(&clientSockets);
FD_SET Add a socket (no longer processed beyond default size) FD_SET(socketListen,&clientSockets);
FD_CLR Delete the specified socket from the collection, be sure to close and release it manually FD_CLR(socketListen, &clientSockets);closesocket(socketListen);
FD_ISSET Query whether the socket is in the collection, there is no return 0, there is a return non-0 int a = FD_ISSET(socketListen, &clientSockets);

select() function

Role: Monitor the socket collection, and if a socket responds (links or sends or receives data), tell us which socket responds by return values and parameters

int WSAAPI select(
  int           nfds,   /*Fill in 0*/
  fd_set        *readfds, /*Check for readable socket s*/
  fd_set        *writefds, /*Check for writable socket s*/
  fd_set        *exceptfds, /*Check for exceptional errors on socket s*/
  const timeval *timeout
);
Parameter 1: Ignore fill-in 0

To be compatible with Berkeley sockets

Parameter 2: Points to a set of socket arrays that hold the responding array
  • Only for recv(), accept()
  • This array to hold the response is initialized to contain all sockets, which are dropped to the system through the select function. After the system traverses the array, only the responding sockets are assigned back. After the call, only the requested sockets are left with this parameter
Parameter 3: Points to a set of socket arrays that hold the sent array
  • Which client socket s can you send messages to, for send
  • This array to hold the sent sockets is initialized to contain all sockets, which are dropped to the system through the select function. After the system traverses the array, only the sent sockets are assigned back. After calling, only the sent sockets are left with this parameter
Parameter 4: Check for exceptional errors on socket s

Like parameters 2 and 3, socket s with exceptional errors are loaded and fed back to us

/*Get the specific error code on the exception socket*/
getsockopt(socket, SOL_SOCKET, SO_ERROR, buf, buflen);

If there is no error calling this function (for this getsockopt function), return 0, otherwise return SOCKET_ERROR, and you can call WSAGetLastError to get the error code.

Parameter 5: Maximum wait time

A structure

struct timeval {
        long    tv_sec;         /* seconds */
        long    tv_usec;        /* and microseconds */
};

When the client does not respond, select can choose to wait for a period of time, not wait until there is a socket response, in three ways

tv_sec tv_usec Effect
0 0 Return without waiting
3 4 Wait 3 seconds 4 microseconds for no message to return

NULL: Dead wait until socket responds

Return value
  • 0: continue waits the next time there is no client socket response during the wait time
  • >0: There is a client socket response '
  • SOCKET_ERROR: Send error

select model code

    fd_set allsockets;
    //Zero
    FD_ZERO(&allSockets);
    //Server Load In
    FD_SET(socketServer, &allSockets);
    while (1)
    {
        fd_set readSockets = allSockets;
        fd_set writeSockets = allSockets;
        fd_set errorSockets = allSockets;

        //Time slot
        struct timeval st;
        st.tv_sec = 3;
         st.tv_usec = 0;

        //select
        int nRes = select(0, &readSockets, &writeSockets, &errorSockets, &st);
        if (0 == nRes) //Unresponsive socket
        {
            continue;
        }
        else if (nRes > 0)
        {
            //Handling errors
            for (u_int i = 0; i < errorSockets.fd_count; i++)
            {
                char str[100] = { 0 };
                int len = 99;
                if (SOCKET_ERROR == getsockopt(errorSockets.fd_array[i], SOL_SOCKET, SO_ERROR, str, &len))
                {
                    printf("Unable to get error information\n");
                }
                printf("%s\n", str);    
            }

            for (u_int i = 0; i < writeSockets.fd_count; i++)
            {
                //printf("server%d,%d: writable\n", socketServer, writeSockets.fd_array[i]);
                if (SOCKET_ERROR == send(writeSockets.fd_array[i], "ok", 2, 0))
                {
                    int a = WSAGetLastError();
                }
            }

            //Responsive
            for (u_int i = 0; i < readSockets.fd_count; i++)
            {
                if (readSockets.fd_array[i] == socketServer)
                {
                    //accept
                    SOCKET socketClient = accept(socketServer, NULL, NULL);
                    if (INVALID_SOCKET == socketClient)
                    {
                        //Link error
                        continue;
                    }
                    
                    FD_SET(socketClient, &allSockets);
                    //send
                }
                else
                {
                    char strBuf[1500] = { 0 };
                    //Client
                    int nRecv = recv(readSockets.fd_array[i], strBuf, 1500, 0);
                    //send
                    if (0 == nRecv)
                    {
                        //Client is offline
                        //Remove from collection
                        SOCKET socketTemp = readSockets.fd_array[i];
                        FD_CLR(readSockets.fd_array[i], &allSockets);
                        //release
                        closesocket(socketTemp);
                    }
                    else if (0 < nRecv)
                    {
                        //Message received
                        printf(strBuf);
                    }
                    else //SOCK_ERROR
                    {
                        //Forced offline also called error 10054
                        int a = WSAGetLastError();
                        switch (a)
                        {
                        case 10054:
                            {
                                SOCKET socketTemp = readSockets.fd_array[i];
                                FD_CLR(readSockets.fd_array[i], &allSockets);
                                //release
                                closesocket(socketTemp);
                            }    
                        }
                    }    
                }
            }
        }

summary

select model

Deliver an array of sockets to the system, and then query the system for a signal from the socket. The procedure is performed in the select function, and then returns the set of sockets with operations.

select() function essence

The select() function performs traversal and returns a responsive socket, which is also blocked.

waiting time block
Don't wait Execution Blocking
Half wait Execution Block+Soft Block
Wait All Execution Block+Hard Block

Compare with CS model

When using the CS model, when a client is linked, recv is executed and the while loop returns to accept(), waiting foolishly for the client to link, no multi-client link communication is possible.
When using the select model, it is the select that traverses the socket array, takes out the responsive socket, and does not always traverse, although the execution of the select() function is blocked.It can be understood that each time a response-only socket is processed, multiclient link communication is possible.

When the first client socket comes to link, the select() function takes the server socket out of the allsocket, adds the newly created containing client socket to the allsockets array, and then iterates through the allsocket to see that it is responsive at times, so it won't block and wait like the CS model in the accept() function.

The select() function mainly solves the accept() function blocking problem, but not the recv() and send() function blocking problem.

Posted by jwagner on Sun, 19 May 2019 05:36:53 -0700