Improvement of Reflect Client-Server Model with select

Keywords: socket network Unix Programming

This section focuses on how to use select functions to improve our previous client-server model.

When we were dealing with the multi-client model, every time a client is connected, the server needs to open up a new process to handle the new client, which will consume a lot of memory resources.

The select function allows the process to instruct the kernel to wait for any one of multiple events to occur and to wake it up only after one or more events occur or after a specified period of time. In other words, select has the ability to manage multiple I/O. For multiple socket interfaces, once a socket interface has an event of interest to us, the select function returns, and the return value is the number of events monitored. Moreover, since the parameters of the select function are "value-result" type, we also know which sets of interfaces have events, and then traverse these sets of interfaces and deal with related events.

The specific usage of the select parameter is not explained here, you can refer to the online resources or "UNIX Network Programming: Volume 1" on the relevant content.

The code of the improved echo client-server model is given below.

Server side: echosrv

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

#define ERR_EXIT(m)	\
	do	\
	{	\
		perror(m);	\
		exit(EXIT_FAILURE);	\
	}while(0)


ssize_t readn(int fd, void* buf, size_t count)
{
	//Because there is no guarantee that count s can be read at a time
	//So we need to read in a loop.
	//Until the number of bytes read is count
	size_t nleft = count;
	ssize_t nread;
	char* bufp = (char*)buf;
	while(nleft > 0)
	{
		if((nread = read(fd, bufp, nleft)) < 0)
		{
			if(errno == EINTR)//If the signal is interrupted
				continue;
			return -1;
		}
		if(nread == 0)
			//Represents peer closure, returned directly here
			return count-nleft;
		nleft -= nread;//The number of bytes left after each read
		bufp += nread;
	}
	return count;
}

ssize_t writen(int fd, void* buf, size_t count)
{
	//The number of bytes we want to write each time is count
	size_t nleft = count;
	ssize_t nwritten;
	char* bufp = (char*)buf;
 	while(nleft > 0)
	{
		if((nwritten = write(fd, bufp, nleft)) < 0)
		{
			if(errno == EINTR)
				continue;
			return -1;
		}
		if(nwritten == 0)
			//Nothing happened.
			continue;
		nleft -= nwritten;//The number of bytes left to write after each write
		bufp += nwritten;
	}
	return count;
}

ssize_t recv_peek(int sockfd, void* buf, size_t len)
{
	//This function can receive data from a socket interface
	//But data is not removed from the buffer
	while(1)
	{
		int ret = recv(sockfd, buf, len, MSG_PEEK);
		//Read the data and return, otherwise return
		if(ret == -1 && errno == EINTR)
			continue;
		return ret;
	}
}

ssize_t readline(int sockfd, void*buf, size_t maxline)
{
	//The read process does not necessarily read maxline bytes
	//Just encounter n to return
	int ret;
	int nread;
	char* bufp = buf;
	int nleft = maxline;
	while(1)
	{
		ret = recv_peek(sockfd, bufp, nleft);
		if(ret < 0)
			return ret;
		else if(ret == 0)
			return ret;
		nread = ret;
		int i;
		for(i = 0; i < nread; ++i)
		{
			if(bufp[i] == '\n')
			{
				//Our recv_peek is just peeping at the data.
				//There is no data left.
				//So here we use readn to remove peeped data from the buffer
				ret = readn(sockfd, bufp, i+1);
				if(ret != i+1)
					exit(EXIT_FAILURE);
				return ret;
			}
		}
		//No encounter \n
		if(nread > nleft)
			exit(EXIT_FAILURE);
		//Remove read data nread bytes from the buffer
		nleft -= nread;
		ret = readn(sockfd, bufp, nread);
		if(ret != nread)
			exit(EXIT_FAILURE);
		//The next peep needs to be offset.
		bufp += nread;
	}
	return -1;
}

void echo_srv(int conn)
{
	char recvbuf[1024];
	while(1)
	{
		memset(&recvbuf, 0, sizeof(recvbuf));
		int ret = readline(conn,  recvbuf, 1024);
		if(ret == -1)
			ERR_EXIT("read failure");
		if(ret == 0)
		{
			printf("client close\n");
			break;
		}
		fputs(recvbuf, stdout);
		writen(conn, recvbuf, strlen(recvbuf));
	}
}

void handle_sigchld(int sig)
{
/*	wait(NULL);*/
	while(waitpid(-1, NULL, WNOHANG) > 0)
		;
}

int main(void)
{
	//Avoiding zombie processes
/*	signal(SIGCHLD, SIG_IGN);//ignore
 */
	signal(SIGCHLD, handle_sigchld);

	//Create a socket
	int listenfd;
	if((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) //Both AF_INET and PF_INET can be used. The first two parameters can be determined to be TCP, so the third parameter can be set to 0.
//	If ((listenfd = socket (AF_INET, SOCK_STREAM, 0)) < 0// / Both AF_INET and PF_INET can be used. The first two parameters can be determined to be TCP, so the third parameter can be set to 0.
		ERR_EXIT("socket_failure");
		//Initialization address
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;//Address family
	servaddr.sin_port = htons(5188);

	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//Represents the arbitrary address of the machine, recommended for use (converted into network byte order)
	//You can also specify explicitly by yourself
//	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	//perhaps
//	inet_aton("127.0.0.1", &servaddr.sin_addr);
	
	//Open address reuse before binding
	int on = 1;
	if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
		ERR_EXIT("setsockopt_failure");

	//Next, bind the socket to a local address
	//The IPv4 address structure needs to be mandatory to be converted to a universal address structure
	if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
		ERR_EXIT("bind_failure");//Binding failed
	
	//The next step is listening, which changes the socket from close state to listen state to accept the connection.
	if(listen(listenfd, SOMAXCONN) < 0)
		ERR_EXIT("listen_failure"); 

	//Define the address of the other party
	struct sockaddr_in peeraddr;
	socklen_t peerlen;
	int conn; //A new socket, called a connected socket (active socket)
/*
	pid_t pid;
	while(1)
	{
		if((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)				
			ERR_EXIT("accept_failure");
		//The address and port of the output client		
		printf("IP=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
		//Once you have a connection, create a process
		//Processing multiple clients to achieve concurrency
		pid = fork();
		if(pid == -1)
			ERR_EXIT("fork_failure");
		if(pid == 0)
		{
			//Let subprocesses handle existing communication processes
			//No longer need to listen socket interface
			close(listenfd);
			echo_srv(conn);
			//Once the do_service function returns, the process has no value in existence.
			exit(EXIT_SUCCESS);//At this point, the process of opening up for clients is also destroyed.
		}
		else
			//The parent process accept s
			//There is no need to connect socket interfaces anymore, namely conn (parent-child process shared file descriptor)
			close(conn);
			
	}

	//Implementing a Reflex Client/Server Model
	//That is, the client obtains data from standard input, sends it to the server, and then echoes back to the server.
*/

	//Change to implement with select
	int client[FD_SETSIZE];//The maximum number of events that select can handle
	int i;
	for(i = 0; i < FD_SETSIZE; ++i)
		client[i] = -1;//Initialization, -1 indicates idle state
	int maxi = 0; //Maximum non-idle position

	int nready;
	int maxfd = listenfd;//3
	fd_set rset;//Define a read set
	fd_set allset;
	FD_ZERO(&rset);
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);//First add the monitor socket interface
	while(1)
	{
		rset = allset;
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);
		if(nready == -1)
		{
			if(errno == EINTR)
				continue;
			ERR_EXIT("select");
		}
		if(nready == 0)
			continue;
		if(FD_ISSET(listenfd, &rset))
		{
			peerlen = sizeof(peeraddr);
			//Monitor socket interface generates events
			conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);
			if(conn == -1)
				ERR_EXIT("accept error");
			for(i = 0; i < FD_SETSIZE; ++i)
			{
				if(client[i] < 0)
				{
					//Find a free place to put the interface in.
					client[i] = conn;
					if(maxi < i)
						maxi = i;
					break;
				}
			}
			if(i == FD_SETSIZE)
			{
				fprintf(stderr, "too many clients\n");
				exit(EXIT_FAILURE);
			}
			printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); 

			FD_SET(conn, &allset);
			if(conn > maxfd)
				maxfd = conn;

			if(--nready <= 0)
				continue;
		}
		//Connected socket interfaces may also generate events
		for(i = 0; i < FD_SETSIZE; ++i)
		{
			conn = client[i];
			if(conn == -1)
				continue;
			if(FD_ISSET(conn, &rset))
			{
				//It means that there are readable events.
				char recvbuf[1024] = {0};
				int ret = readline(conn, recvbuf, 1024);
				if(ret == -1)
					ERR_EXIT("readline error");
				if(ret == 0)
				{
					//Peer closure
					printf("client close\n");
					FD_CLR(conn, &allset);//Clear this set of interfaces
					client[i] = -1;
				}
				fputs(recvbuf, stdout);
				writen(conn, recvbuf, strlen(recvbuf));

				if(--nready <= 0)
					break;
			}
		}
	}
	return 0;
}
Client: echocli.c

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

#define ERR_EXIT(m)	\
	do	\
	{	\
		perror(m);	\
		exit(EXIT_FAILURE);	\
	}while(0)

ssize_t readn(int fd, void* buf, size_t count)
{
	size_t nleft = count; //The number of bytes remaining to read
	ssize_t nread; //Number of bytes read at a time
	char* bufp = (char*)buf;
	while(nleft > 0)
	{
		if((nread = read(fd, bufp, nleft)) < 0)
		{
			if(errno == EINTR)
				continue;
			return -1;
		}
		if(nread == 0)//Peer closure
			return count-nleft;
		nleft -= nread;
		bufp += nread;
	}
	return count;
}

ssize_t writen(int fd, void* buf, size_t count)
{
	size_t nleft = count; //The number of bytes remaining to read
	ssize_t nwritten; //Number of bytes read at a time
	char* bufp = (char*)buf;
	while(nleft > 0)
	{
		if((nwritten = write(fd, bufp, nleft)) < 0)
		{
			if(errno == EINTR)
				continue;
			return -1;
		}
		if(nwritten == 0)
			continue;
		nleft -= nwritten;
		bufp += nwritten;
	}
	return count;	
}

ssize_t recv_peek(int sockfd, void* buf, size_t len)
{
	while(1)
	{
		int ret = recv(sockfd, buf, len, MSG_PEEK);
		if(ret == -1 && errno == EINTR)
			continue;
		//Sneak at the data and return it directly
		return ret;
	}
}

ssize_t readline(int sockfd, void*buf, size_t maxline)
{
	char* bufp = buf;
	int nleft = maxline;
	int nread;
	int ret;
	while(1)
	{
		ret = recv_peek(sockfd, bufp, nleft);
		if(ret < 0)
			return ret;
		if(ret == 0)
			return ret;
		nread = ret;
		int i;
		for(i = 0; i < nread; ++i)
		{
			if(bufp[i] == '\n')
			{
				ret = readn(sockfd, bufp, i+1);
				if(ret != i+1)
					exit(EXIT_FAILURE);
				return ret;
			}
		}
		//No encounter \n
		if(nread > nleft)
			exit(EXIT_FAILURE);
		nleft -= nread;
		ret = readn(sockfd, bufp, nread);
		if(ret != nread)
			exit(EXIT_FAILURE);
		//Keep on peeping
		bufp += nread;
	}
	return -1;
}

void echo_cli(int sock)
{
/*
	//If the connection is successful, you can communicate
	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
	{
		writen(sock, sendbuf, strlen(sendbuf));
		
		int ret = readline(sock, recvbuf, 1024);
		if(ret == -1)
			ERR_EXIT("read error");
		else if(ret == 0)
		{
			printf("peer close\n");
			break;
		}
		//Show up
		fputs(recvbuf, stdout);
		//Here we need to clear the buffer
		memset(sendbuf, 0, sizeof(sendbuf));
		memset(recvbuf, 0, sizeof(recvbuf));			
	}
	close(sock);
*/
	//This is the use of select
	fd_set rset;
	FD_ZERO(&rset);

	//Detect whether the standard input produces readable events
	int nready;
	int maxfd;
	int fd_stdin = fileno(stdin);//Standard input
	char recvbuf[1024] = {0};
	char sendbuf[1024] = {0};

	//There are two file descriptors, fd_stdin and sock
	if(fd_stdin > sock)
		maxfd = fd_stdin;
	else
		maxfd = sock;
	while(1)
	{
		FD_SET(fd_stdin, &rset);
		FD_SET(sock, &rset);
		//There's only a collection of readings.
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);
		if(nready == -1)
			ERR_EXIT("select error");
		if(nready == 0)
			continue;
		//If an event is detected, rset changes
		//Which sets of interfaces will be included in this package?
		if(FD_ISSET(sock, &rset))
		{
			//Socket interface produces readability
			//Read by line
			int ret = readline(sock, recvbuf, sizeof(recvbuf));
			if(ret == -1)
				ERR_EXIT("readline error");
			else if(ret == 0)
			{
				printf("server close\n");
				break;
			}
			//Show up
			fputs(recvbuf, stdout);
			memset(recvbuf, 0, sizeof(recvbuf));
		}
		if(FD_ISSET(fd_stdin, &rset))
		{
			//Standard input generates events, input buffer has contents, and fgets is used to clear them
			if(fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
				break;
			write(sock, sendbuf, strlen(sendbuf));
			memset(sendbuf, 0, sizeof(sendbuf));
		}
	}
	close(sock);
}

void handle_sigpipe(int sig)
{
	printf("recv a sig=%d\n", sig);
}

int main(void)
{
	signal(SIGPIPE, handle_sigpipe);
	int sock;//Create a socket
	if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) //Both AF_INET and PF_INET can be used. The first two parameters can be determined to be TCP, so the third parameter can be set to 0.
//	If ((sock = socket (AF_INET, SOCK_STREAM, 0)< 0)// / Both AF_INET and PF_INET can be used. The first two parameters can be determined to be TCP, so the third parameter can be set to 0.
		ERR_EXIT("socket_failure");
		
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;//Address family
	servaddr.sin_port = htons(5188);

	//Explicitly specify the server-side address yourself
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	//Clients do not need bind or listen.
	//Just connect directly to the past.
	if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
		ERR_EXIT("connect_failure");

	//Connect successfully, view the local port and address
	struct sockaddr_in localaddr;
	socklen_t addrlen = sizeof(localaddr);
	if(getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0)
		ERR_EXIT("getsockname error");
	
	printf("IP=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));

	echo_cli(sock);
	
	return 0;
}

Description: FD_SETSIZE is the maximum number of events that a system-specified select can monitor, usually 1024.


Posted by neiltaylormade on Thu, 11 Apr 2019 13:57:31 -0700