Network socket programming under Linux -- realizing the communication between server (select) and multiple clients

Posted by ramkumar_unknow on Wed, 15 Dec 2021 04:17:38 +0100

1, About socket communication

 

Server side workflow:

Call the socket() function to create a socket, and bind the created socket with the server IP address with the bind() function
Call the listen() function to listen to the socket created by the socket() function and wait for the client to connect. When the client request arrives
Call the accept() function to accept the connection request and return a new socket corresponding to this connection to prepare for communication
Call the write()/read() function and send()/recv() function to read and write data, communicate with the client through the socket returned by accept(), and close the socket (close)
Client workflow:

Call the socket() function to create a socket
Call the connect() function to connect the server
Call write()/read() function or send()/recv() function to read and write data
Close socket(close)

2, Server side programming with select:

The select function is described in the previous article( select function usage )It has been mentioned, so I won't do more elaboration. Next, paste the server-side code servce c

#include <stdio.h>
#include <netinet/in.h>   //for souockaddr_in
#include <sys/types.h>      
#include <sys/socket.h>
#include <errno.h>
#include <stdlib.h>

#include <arpa/inet.h>

//for select
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>

#include <strings.h>   //for bzero
#include <string.h>

#define BUFF_SIZE 1024
#define backlog 7
#define ser_port 11277
#define CLI_NUM 3


int client_fds[CLI_NUM];

int main(int agrc,char **argv)
{
    int ser_souck_fd;
    int i;   
    char input_message[BUFF_SIZE];
    char resv_message[BUFF_SIZE];


    struct sockaddr_in ser_addr;
    ser_addr.sin_family= AF_INET;    //IPV4
    ser_addr.sin_port = htons(ser_port); 
    ser_addr.sin_addr.s_addr = INADDR_ANY;  //All addresses are specified

    //creat socket
    if( (ser_souck_fd = socket(AF_INET,SOCK_STREAM,0)) < 0 )
    {
        perror("creat failure");
        return -1;
    } 

    //bind soucket
    if(bind(ser_souck_fd, (const struct sockaddr *)&ser_addr,sizeof(ser_addr)) < 0)
    {
        perror("bind failure");
        return -1;
    }

    //listen
    if(listen(ser_souck_fd, backlog) < 0) 
    {
        perror("listen failure"); 
        return -1;
    }


    //fd_set
    fd_set ser_fdset;
    int max_fd=1;
    struct timeval mytime;
    printf("wait for client connnect!\n");

    while(1)
    {
        mytime.tv_sec=27;
        mytime.tv_usec=0;

        FD_ZERO(&ser_fdset);

        //add standard input
        FD_SET(0,&ser_fdset);
        if(max_fd < 0)
        {
            max_fd=0; 
        }

        //add serverce
        FD_SET(ser_souck_fd,&ser_fdset);
        if(max_fd < ser_souck_fd)
        {
            max_fd = ser_souck_fd;
        }

        //add client 
        for(i=0;i<CLI_NUM;i++)  //Define multiple clients with an array fd
        {
            if(client_fds[i]!=0) 
            {
                FD_SET(client_fds[i],&ser_fdset);
                if(max_fd < client_fds[i])
                {
                    max_fd = client_fds[i]; 
                }
            }
        }

        //select multiplexing
        int ret = select(max_fd + 1, &ser_fdset, NULL, NULL, &mytime);

        if(ret < 0)    
        {    
            perror("select failure\n");    
            continue;    
        }    

        else if(ret == 0)
        {
            printf("time out!");
            continue;
        }

        else
        {
            if(FD_ISSET(0,&ser_fdset)) //Does the standard input exist in the ser_ In the fdset set (that is, when an input is detected, do the following)
            {
                printf("send message to");
                bzero(input_message,BUFF_SIZE);
                fgets(input_message,BUFF_SIZE,stdin);

                for(i=0;i<CLI_NUM;i++)
                {
                    if(client_fds[i] != 0)
                    {
                        printf("client_fds[%d]=%d\n", i, client_fds[i]);
                        send(client_fds[i], input_message, BUFF_SIZE, 0);
                    }
                }

            }

            if(FD_ISSET(ser_souck_fd, &ser_fdset)) 
            {
                struct sockaddr_in client_address;
                socklen_t address_len;
                int client_sock_fd = accept(ser_souck_fd,(struct sockaddr *)&client_address, &address_len);
                if(client_sock_fd > 0)
                {
                    int flags=-1;
                    //Assign a fd, CLI to a client_ Num = 3, there can only be three clients at most. After 4, the for loop will jump out, and flags will be re assigned to - 1
                    for(i=0;i<CLI_NUM;i++)
                    {
                        if(client_fds[i] == 0)
                        {
                            flags=i; 
                            client_fds[i] = client_sock_fd;
                            break;
                        }
                    }


                    if (flags >= 0)
                    {
                        printf("new user client[%d] add sucessfully!\n",flags);

                    }

                    else //flags=-1
                    {   
                        char full_message[]="the client is full!can't join!\n";
                        bzero(input_message,BUFF_SIZE);
                        strncpy(input_message, full_message,100);
                        send(client_sock_fd, input_message, BUFF_SIZE, 0);

                    }
                }    
            }

        }

        //deal with the message

        for(i=0; i<CLI_NUM; i++)
        {
            if(client_fds[i] != 0)
            {
                if(FD_ISSET(client_fds[i],&ser_fdset))
                {
                    bzero(resv_message,BUFF_SIZE);
                    int byte_num=read(client_fds[i],resv_message,BUFF_SIZE);
                    if(byte_num > 0)
                    {
                        printf("message form client[%d]:%s\n", i, resv_message);
                    }
                    else if(byte_num < 0)
                    {
                        printf("rescessed error!");
                    }

                    //A client exited
                    else  //cancel fdset and set fd=0
                    {
                        printf("clien[%d] exit!\n",i);
                        FD_CLR(client_fds[i], &ser_fdset);
                        client_fds[i] = 0;
                       // printf("clien[%d] exit!\n",i);
                        continue;  //If break is used here, a client exiting will cause the server to exit.  
                    }
                }
            }
        }    
    }
    return 0;
}

Select implements multiplexing. Multiplexing, as the name suggests, means that each does its own thing. When the standard input event comes, there are related functions to handle it. The server handles the server's events. When the client arrives, there are relevant functions to process them. You can traverse the reading and writing of each fd through select, so you don't have to worry about blocking.

3, Client programming with epoll:

1. Client program (epoll_client.c):

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

#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>

#define BUFFER_SIZE 1024    

int main(int argc, const char * argv[])    
{   
    int i,n;
    int connfd,sockfd;
    struct epoll_event ev,events[20]; //ev is used to register events, and the array is used to return events to be processed
    int epfd=epoll_create(256);//Create an epoll handle, where 256 is the maximum number of handles supported by your epoll

    struct sockaddr_in client_addr;
    struct sockaddr_in server_addr;    

    server_addr.sin_family = AF_INET;    
    server_addr.sin_port = htons(11277);    
    server_addr.sin_addr.s_addr =INADDR_ANY;    
    bzero(&(server_addr.sin_zero), 8);    

    int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  

    ev.data.fd=server_sock_fd;//Sets the file descriptor associated with the event to be processed
    ev.events=EPOLLIN|EPOLLET;//Set the type of event to process
    epoll_ctl(epfd,EPOLL_CTL_ADD,server_sock_fd,&ev);//Register epoll events

    if(server_sock_fd == -1)    
    {    
        perror("socket error");    
        return 1;    
    }    

    char recv_msg[BUFFER_SIZE];    
    char input_msg[BUFFER_SIZE];    

    if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)    
    {    
        for(;;)
        {
            int nfds=epoll_wait(epfd,events,20,500);//Wait for the epoll event to occur
            for(i=0;i<nfds;++i)
            {    
                if(events[i].events&EPOLLOUT) //Send data and write socket
                {
                    bzero(input_msg, BUFFER_SIZE);    
                    fgets(input_msg, BUFFER_SIZE, stdin);    

                    sockfd = events[i].data.fd;
                    write(sockfd, recv_msg, n);

                    ev.data.fd=sockfd;
                    ev.events=EPOLLIN|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);
                }   

                else if(events[i].events&EPOLLIN)//When data arrives, read the socket
                {
                    bzero(recv_msg, BUFFER_SIZE);
                    if((n = read(server_sock_fd, recv_msg, BUFFER_SIZE)) <0 )
                    {
                        printf("read error!");
                    }

                    ev.data.fd=server_sock_fd;
                    ev.events=EPOLLOUT|EPOLLET;
                    printf("%s\n",recv_msg);
                }

            }        
        }
    }    
    return 0;    
}   

 

2. About epoll functions:
Compared with select, epoll's biggest advantage is that it will not reduce efficiency as the number of listening fd increases. Because in the implementation of select in the kernel, it is processed by polling. The more fd polling, the more time it takes. And, in Linux / POSIX_ types. The H header file has the following statement:
#define __FD_SETSIZE 1024 
Indicates that select can listen to 1024 fd at most at the same time

There are three functions:

1, int epoll_create (int size); 
Create a handle to epoll

*size is used to tell the kernel the total number of listeners. This parameter is different from the first parameter in select(). It gives the maximum listening fd+1 value. It should be noted that when the epoll handle is created, it will occupy an fd value. Under linux, if you view / proc / process id/fd /, you can see this fd. Therefore, after using epoll, you must call close() to close it, otherwise fd may be exhausted.

2, int epoll_ctl (int epfd , int op, int fd, struct epoll_event *event);

Epoll's event registration function is different from select(). It tells the kernel what type of event to listen for when listening for events, but registers the event type to listen for here. The first parameter is epoll_ The return value of create(). The second parameter represents the action, which is represented by three macros:

  • EPOLL_CTL_ADD: register a new fd into epfd;
  • EPOLL_CTL_MOD: modify the listening event of the registered fd;
  • EPOLL_CTL_DEL: delete an fd from epfd;

The third parameter is the fd to be monitored

The fourth parameter tells the kernel what to listen for, struct epoll_ The event structure is as follows:

struct epoll_event {
    __uint32_t events;    /* Epoll events */
    epoll_data_t data;     /* User data variable */
};
typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

 

events can be a collection of the following macros:

Epolin: indicates that the corresponding file descriptor can be read (including the normal shutdown of the opposite SOCKET);
EPOLLOUT: indicates that the corresponding file descriptor can be written;
EPOLLPRI: indicates that the corresponding file descriptor has urgent data readability (here it should indicate the arrival of out of band data);
EPOLLERR: indicates that an error occurred in the corresponding file descriptor;
EPOLLHUP: indicates that the corresponding file descriptor is hung up;
EPOLLET: set EPOLL to edge triggered mode, which is relative to level triggered.
EPOLLONESHOT: only listen to the event once. After listening to the event, if you need to continue listening to the socket, you need to add the socket to the EPOLL queue again

3, int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

Wait for the event to occur, similar to the select() call.
The parameter events is used to get the collection of events from the kernel,
Maxevents tells the kernel how big the events are, and the value of maxevents cannot be greater than the epoll created_ size at create(),
The parameter timeout is the timeout (in milliseconds, 0 will be returned immediately, - 1 will be uncertain, and it is also said that it is permanently blocked). This function returns the number of events to be processed. For example, returning 0 means that it has timed out.

Use steps:
< 1 > first, create_epoll(int maxfds) to create an epoll handle, where maxfds is the maximum number of handles supported by your epoll. This function will return a new epoll handle, which will be used for all subsequent operations. After using up, remember to close the epoll handle created with close().  

< 2 > then call epoll for each frame_ Wait (int EPFD, epoll_event, int max events, int timeout) to query all network interfaces.

< 3 > kdpfd is epoll_create handle after creation. Events is an epoll_ Pointer to event *, when epoll_ After the wait function is operated successfully, epoll_events will store all read and write events. max_events is the number of all socket handles that currently need to listen. The last timeout is epoll_ The timeout of wait. When it is 0, it means to return immediately. When it is - 1, it means to wait until there is an event range. When it is any positive integer, it means to wait for such a long time. If there is no event, it will return. Generally, if the network main loop is a separate thread, you can use - 1 to ensure some efficiency. If it is in the same thread as the main logic, you can use 0 to ensure the efficiency of the main loop.
  epoll_ After the return of wait, there should be a loop that traverses all events.

Basically, they are the following frameworks:

for( ; ; )
    {
        nfds = epoll_wait(epfd,events,20,500);
        for(i=0;i<nfds;++i)
        {
            if(events[i].data.fd==listenfd) //There are new connections
            {
                     connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);    //accept this connection
                     ev.data.fd=connfd;
                     ev.events=EPOLLIN|EPOLLET;
                     epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //Add the new fd to the listening queue of epoll
            }

           else if( events[i].events&EPOLLIN ) //After receiving the data, read the socket
            {
                     n = read(sockfd, line, MAXLINE)) < 0    //read
                     ev.data.ptr = md;     //md is a custom type, adding data
                     ev.events=EPOLLOUT|EPOLLET;
                     epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//Modify the identifier and send data when waiting for the next cycle, which is the essence of asynchronous processing
            }
            else if(events[i].events&EPOLLOUT) //Data to be sent, write socket
            {
                     struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //Fetch data
                     sockfd = md->fd;
                     send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //send data
                     ev.data.fd=sockfd;
                     ev.events=EPOLLIN|EPOLLET;
                     epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //Modify the identifier and wait for the next cycle to receive data
            }
            else
            {
                     //Other treatment
            }
        }
    }

--------
Copyright notice: This article is the original article of CSDN blogger "qicheng777", which follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this notice for reprint.
Original link: https://blog.csdn.net/qicheng777/article/details/73642622

Topics: socket