lv8 embedded development-network programming development 15I/O multiplexing and select function

Table of Contents

1 I/O multiplexing

1.1 Introduction to select function and other interfaces

1.2 Original TCP-socket example:

1.3 Example of implementing select function TCP-socket:

2 exercises


1 I/O multiplexing

Multiplexing implementation

1.1 Introduction to select function and other interfaces

int select(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);
 

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

select is a system call function used for multiplexed I/O. It can monitor multiple file descriptors for readable, writable and exception events at the same time. (Special usage: It can also be used to block microseconds, and other file descriptor functions are set to NULL and not used).

Parameter description is as follows:

  • nfds: The number of monitored file descriptors, that is, the maximum file descriptor value that needs to be checked plus one.
  • readfds: A collection of file descriptors used to check for readable events. (Commonly used, generally multi-channel monitoring is readable)
  • writefds: A collection of file descriptors used to check for writable events.
  • exceptfds: A collection of file descriptors used to check for exception events.
  • timeout: timeout period. NULL: permanent blocking, 0: non-blocking mode

The select function will monitor based on the file descriptor set and timeout period in the parameters. When an event that meets the conditions occurs, the select function will return and describe the corresponding file. character set to modify. The specific return values and collection modifications are as follows:

  • If the timeout period is reached, the return value is 0.
  • If an error occurs, the return value is -1 and the corresponding error code is set.
  • If a readable event occurs, the corresponding file descriptor in readfds will be modified, and the return value will be greater than 0.
  • If a writable event occurs, the corresponding file descriptor in writefds will be modified, and the return value will be greater than 0.
  • If an exception occurs, the corresponding file descriptor in exceptfds will be modified, and the return value will be greater than 0.

By continuously calling the select function, non-blocking I/O monitoring on multiple file descriptors can be implemented to handle readable, writable and exception events in a timely manner.

fd_set structure: Each bit represents a file descriptor, the value is 0 or 1, nfds represents the largest file descriptor + 1.

Supplement: In the standard C library header file , fd_set is implemented through a fixed-size array. The size of the array is defined by the macro FD_SETSIZE. Normally, the default value of FD_SETSIZE is 1024.

/*Remove the file descriptor from the set*/
void FD_CLR(int fd, fd_set *set);

/*Check whether the file descriptor exists in the collection*/
int FD_ISSET(int fd, fd_set *set);

/*Add file descriptor*/
void FD_SET(int fd, fd_set *set);

/*Initialize collection*/
void FD_ZERO(fd_set *set);

1.2 Original TCP-socket example:

server.c

#include "net.h"

int main(int argc, char *argv[])
{
/*Check parameters, if less than 3, exit the process directly*/
Argment(argc, argv);
/*Create a socket with listening mode set*/
int fd = CreateSocket(argv);
/*Receive client connection and generate new file descriptor*/
int newfd = accept(fd, NULL, NULL);
if(newfd < 0)
perror("accept");
/*Process client data*/
while(DataHandle(newfd) > 0);
return 0;
}

socket.c

#include "net.h"

void Argment(int argc, char *argv[]){
if(argc < 3){
fprintf(stderr, "%s<addr><port>\
", argv[0]);
exit(0);
}
}
int CreateSocket(char *argv[]){
/*Create socket*/
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
ErrExit("socket");
/*Allow addresses to be reused quickly*/
int flag = 1;
if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, & amp;flag, sizeof(flag) ) )
perror("setsockopt");
/*Set communication structure*/
Addr_in addr;
bzero( & amp;addr, sizeof(addr) );
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
/*Bind communication structure*/
if( bind(fd, (Addr *) & amp;addr, sizeof(Addr_in) ) )
ErrExit("bind");
/*Set the socket to listening mode*/
if( listen(fd, BACKLOG) )
ErrExit("listen");
return fd;
}
int DataHandle(int fd){
char buf[BUFSIZ] = {};
int ret = recv(fd, buf, BUFSIZ, 0);
if(ret < 0)
perror("recv");
if(ret > 0)
printf("data: %s\
", buf);
return ret;
}

net.h

#ifndef _NET_H_
#define _NET_H_

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>

typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)

void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);


#endif

1.3 Example of implementing select function TCP-socket:

sever.c

#include "net.h"
#include <sys/select.h>
#define MAX_SOCK_FD 1024

int main(int argc, char *argv[])
{
int i, ret, fd, newfd;
fd_set set, tmpset;
Addr_in clientaddr;
socklen_t clientlen = sizeof(Addr_in);
/*Check parameters, if less than 3, exit the process directly*/
Argment(argc, argv);
/*Create a socket with listening mode set*/
fd = CreateSocket(argv);

FD_ZERO( & amp;set);
FD_ZERO( & amp;tmpset);
FD_SET(fd, & amp;set);
while(1){
tmpset = set; //The temp file descriptor set will be modified by the select function to reflect the ready file descriptors.
if( (ret = select(MAX_SOCK_FD, & amp;tmpset, NULL, NULL, NULL)) < 0)
ErrExit("select"); //The macro defines an error handling
if(FD_ISSET(fd, & amp;tmpset) ){
/*Receive client connection and generate new file descriptor*/
if( (newfd = accept(fd, (Addr *) & amp;clientaddr, & amp;clientlen) ) < 0)
perror("accept");
printf("[%s:%d]Connection established\
",
inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
FD_SET(newfd, & amp;set); //New client file descriptor is added to set
}else{ //Process client data
for(i = fd + 1; i < MAX_SOCK_FD; i + + ){ //fd is the server, fd + 1 is the first incoming client descriptor
if(FD_ISSET(i, & amp;tmpset)){
if(DataHandle(i) <= 0){
if( getpeername(i, (Addr *) & amp;clientaddr, & amp;clientlen) )
perror("getpeername");
printf("[%s:%d] disconnected\
",
inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
FD_CLR(i, & amp;set);
}
}
}
}
}
return 0;
}

socket.c

#include "net.h"

void Argment(int argc, char *argv[]){
if(argc < 3){
fprintf(stderr, "%s<addr><port>\
", argv[0]);
exit(0);
}
}
int CreateSocket(char *argv[]){
/*Create socket*/
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
ErrExit("socket");
/*Allow addresses to be reused quickly*/
int flag = 1;
if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, & amp;flag, sizeof(flag) ) )
perror("setsockopt");
/*Set communication structure*/
Addr_in addr;
bzero( & amp;addr, sizeof(addr) );
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
/*Bind communication structure*/
if( bind(fd, (Addr *) & amp;addr, sizeof(Addr_in) ) )
ErrExit("bind");
/*Set the socket to listening mode*/
if( listen(fd, BACKLOG) )
ErrExit("listen");
return fd;
}
int DataHandle(int fd){
char buf[BUFSIZ] = {};
Addr_in peeraddr;
socklen_t peerlen = sizeof(Addr_in);
if( getpeername(fd, (Addr *) & amp;peeraddr, & amp;peerlen) )
perror("getpeername");
int ret = recv(fd, buf, BUFSIZ, 0);
if(ret < 0)
perror("recv");
if(ret>0){
printf("[%s:%d]data: %s\
",
inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
}
return ret;
}

net.h

#ifndef _NET_H_
#define _NET_H_

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>

typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)

void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);


#endif

Explanation:

This code is a simple web server program implemented using the select function. It can handle multiple client connections simultaneously.

The program mainly includes the following parts:

  1. Create and set up a socket in listening mode: fd = CreateSocket(argv);

    The CreateSocket function is called here to create a socket and make some necessary settings to put it in listening mode.

  2. Use the select function to listen for readable events on sockets and client connections:

    tmpset = set;
    if ((ret = select(MAX_SOCK_FD, & amp;tmpset, NULL, NULL, NULL)) < 0)
        ErrExit("select");

    The select function blocks the program until there is a socket or client connection to read from, or an error occurs. In this code, the select function is used to wait for the readable event of the socket fd and the client connection. MAX_SOCK_FD is the maximum file descriptor value plus 1. tmpset is a temporary file descriptor set used to store file descriptors where readable events occur.

  3. Handle socket readable events and client connection readable events:

    if (FD_ISSET(fd, & amp;tmpset)) { // Accept client connection // Add new client file descriptor to set } else { // Process client data // Disconnect and remove file descriptor from set}

    If the socket fd is readable, it means that a new client connection request has arrived. The program will call the accept function to accept the connection and generate a new file descriptor newfd, and then add the file descriptor to set for subsequent processing.

    If the socket is not readable, data has arrived from the connected client. The program will iterate through the file descriptors between fd + 1 and MAX_SOCK_FD - 1 to check whether they are readable in tmpset. If it is readable, call the DataHandle function to process the data. If the processing result is less than or equal to 0, it means the connection is disconnected. The program will output the disconnection information and remove the file descriptor from set.

  4. Loop through the above steps to continue processing client connections and data.

This code shows a simple network server program framework that uses the select function to achieve efficient event-driven concurrent processing. Specific business logic needs to be implemented based on actual needs.

Additional:

The getpeername function is used to obtain the address information of the remote connection associated with the socket. Its function prototype is as follows:

int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd is the socket file descriptor that represents the socket associated with the remote connection.
  • addr is a pointer to the struct sockaddr structure, which is used to receive the address information of the remote connection.
  • addrlen is a pointer to type socklen_t, used to pass the length of addr, and will be updated to the actual address after the function call is completed Structure length.

This function returns 0 if the call is successful, -1 if it fails, and sets the corresponding error code.

The same usage getsocketname is to get the local one.

2 Exercise

Implement the I/O multiplexing server code using the select function and communicate with it using the nc command

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define MAX_SOCK_FD 1024
#define BACKLOG 5

#define ErrExit(msg) do{perror(msg); exit(EXIT_FAILURE);} while(0)

int DataHandle(int fd)
{
char buf[BUFSIZ] = {};
int ret;
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(struct sockaddr_in);
if(getpeername(fd, (struct sockaddr *) & amp;peeraddr, & amp;peerlen) )
perror("getpeername");
\t
ret = recv(fd, buf, BUFSIZ, 0);
if(ret < 0)
{
perror("recv");
}
    if(ret>0)
{
printf("[%s:%d]data: %s\
",
inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
}
return ret;
}

int main(int argc,char *argv[])
{
int fd, new_fd, i,ret;
fd_set set, tmpset;
struct sockaddr_in addr, client_addr;
socklen_t clientlen = sizeof(client_addr);

\t
int flag = 1;

if(argc < 3)
{
printf("%s <addr> <port>\
",argv[0]);
exit(0);
}

//create socket
fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
ErrExit("socket");
}

//avoids the error of ports being occupied
if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, & amp;flag, sizeof(flag) ) )
{
perror("setsockopt");
}

//init struct sockaddr_in
memset( & amp;addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]));
if (inet_aton(argv[1], & amp;addr.sin_addr) == 0)
{
printf("Invalid address\
");
exit(EXIT_FAILURE);
}
\t
\t
\t
//bind
if(bind(fd, (struct sockaddr *) & amp;addr, sizeof(struct sockaddr_in)) == -1)
{
ErrExit("bind");
}

//listen
if(listen(fd, BACKLOG) == -1)
{
ErrExit("listen");
}

//select
FD_ZERO( & amp;set);
FD_ZERO( & amp;tmpset);
FD_SET(fd, & amp;set);
while(1)
{
tmpset = set;
if((ret = select(MAX_SOCK_FD, & amp;tmpset,NULL,NULL,NULL)) < 0)
{
ErrExit("select");
}
if(FD_ISSET(fd, & amp;tmpset) > 0)
{
new_fd = accept(fd,(struct sockaddr *) & amp;client_addr, & amp;clientlen);
if(new_fd < 0)
{
perror("accept");
}
printf("[%s:%d]connected\
",inet_ntoa(client_addr.sin_addr),\
ntohs(client_addr.sin_port));
FD_SET(new_fd, & amp;set);
}
else //if(FF_ISSET(fd, & amp;tmpset < 0) //handle client
{
for(i = fd + 1; i < MAX_SOCK_FD; i + + )
{
// can read
if(FD_ISSET(i, & amp;tmpset))
{
if(DataHandle(i) <= 0)
{
if(getpeername(i,(struct sockaddr *) & amp;client_addr, & amp;clientlen))
perror("getpeername");
printf("[%s:%d]disconnected\
",inet_ntoa(client_addr.sin_addr),\
ntohs(client_addr.sin_port));
FD_CLR(i, & amp;set);

}
}
}
}
}


return 0;
}

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. CS entry skill treeLinux introductionFirst introduction to Linux37215 people are learning the system