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
- Load all sockets (server + client) into an array
- Traversing socket arrays by select()
- Take out the corresponding sockets and put them in another array (all responding sockets)
- Centralized handling of socket arrays with responses
- Server socket response: Client link, call accept()
- 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.