如何对回显服务器进行改进_1

2022-11-14 14:35:40 浏览数 (1)

文章目录

  1. 1. 增加socket函数的错误处理
  2. 2. 改写read/write函数
  3. 3. 僵死进程的处理
  4. 4. 客户服务器之间传递二进制结构
  5. 5. 其他问题
  6. 6. 目前三个文件内容如下
  7. 7. 参考

上一篇中写了一个基本的回显服务器,最基本的功能是有了,但是并不够健壮,那么如何对它进行改进呢?我们需要考虑以下几种情况。

增加socket函数的错误处理

之前的程序中,使用的socket相关的api都没有进行错误判断,一旦某个函数发生错误,程序可能就会崩溃,所以我们需要给原生api包裹一层,添加错误判断,就像下面这样:

代码语言:javascript复制
int Socket(int family, int type, int protocol){
	int		n;
	if ( (n = socket(family, type, protocol)) < 0)
		err_sys("socket error");
	return(n);
}

其他的函数可以仿照这个分别写wrap包裹函数。

改写read/write函数

当read和write用在字节流套接字上时和读写普通的文件不太一样,read或write的字节数量可能会比实际的少。原因是内核中用于套接字的缓冲区已经达到极限,所以我们可能需要多次调用read/write才能完成I/O。

代码语言:javascript复制
ssize_t readn(int fd, void *vptr, size_t n){
	size_t	nleft;
	ssize_t	nread;
	char	*ptr;

	ptr = (char *)vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nread = read(fd, ptr, nleft)) < 0) {
			if (errno == EINTR)
				nread = 0;		/* and call read() again */
			else
				return(-1);
		} else if (nread == 0)
			break;				/* EOF */

		nleft -= nread;
		ptr    = nread;
	}
	return(n - nleft);		/* return >= 0 */
}

僵死进程的处理

服务端进程在检测到一个连接后,就fork一个子进程来处理这个连接。 当客户端程序关闭后,系统就会检测到,然后会关闭该程序打开的所有描述符,然后给服务器发送一个FIN。 服务端fork的子进程接收到FIN后,以ACK响应。(此时服务器套接字处于CLOSE_WAIT状态,客户端套接字处于FIN_wait_2状态) (可以通过netstat -a 命令来查看所有套接字的状态) 服务端子进程响应完ACK后,会给父进程发送一个SIGCHLD中断,但是该信号默认的行为是忽略,所以子进程就会进入僵死状态。 如果这种僵死进程特别多,就会占用大量资源,所以我们得处理这种情况。 (处理僵死进程,涉及到UNIX的信号处理,需要先对此有个了解。) 我们需要给服务端父进程添加对SIGCHLD信号的处理,其中有wait和waitpid两个函数可以用来终止子进程。 如果使用第一个的话,还有种情况会出问题,就是如果客户端一次发起了5个连接,然后客户端进程被关闭,然后服务端的5个子进程会几乎同时收到这个消息,然后同时给父进程发送5个SIGCHLD信号。 由于wait没有排队机制,只能处理一个,其他几个子进程仍可能会变成僵死进程。所以需要使用waitpid,需要如下三步操作: 1.编写wrap函数Signal():

代码语言:javascript复制
Sigfunc * signal(int signo, Sigfunc *func){
	struct sigaction	act, oact;

	act.sa_handler = func;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	if (signo == SIGALRM) {
#ifdef	SA_INTERRUPT
		act.sa_flags |= SA_INTERRUPT;	/* SunOS 4.x */
#endif
	} else {
#ifdef	SA_RESTART
		act.sa_flags |= SA_RESTART;		/* SVR4, 44BSD */
#endif
	}
	if (sigaction(signo, &act, &oact) < 0)
		return(SIG_ERR);
	return(oact.sa_handler);
}

2.在accept前调用处理信号的函数:

代码语言:javascript复制
Signal(SIGCHLD,sig_chld);

3.编写sig_chld函数:

代码语言:javascript复制
void sig_chld(int signo){
    pid_t pid;
    int stat;
    while( (pid=waitpid(-1,&stat,WNOHANG)) > 0){
        std::cout<<"child "<<pid<<" terminated"<<std::endl;
    }
    return ;
}

客户服务器之间传递二进制结构

之前的代码都是传送字符串,不会有什么问题。但在不同的系统上,字节序可能不一样,所以传送二进制数据时(比如int型数值)可能无法得到正确的数据。 第一个常用的方法是把数值转为文本串来传递。第二种是显式地指出所支持的数据类型的二进制格式,包括位数,大端或小端。

其他问题

除了以上几个问题,以下几个问题现在还无法解决,需要学习其他知识后才能来解决。

  • 三路握手建立连接后,客户TCP发送了一个RST复位
  • 在两者正常通信时,服务器子进程被杀死,这时候客户端正阻塞在fgets函数上,无法马上作出反应
  • 服务器子进程被杀死后,服务器主机会给客户端发送FIN,然后客户端会关闭对应套接字,这时候客户端进程并不知道,如果继续向该套接字写入内容,那么会收到系统发出的SIGPIPE信号(默认操作是杀死进程)。
  • 服务器主机崩溃时(不是进程崩溃,也不是执行关机命令)。
  • 服务器主机崩溃后重启,此时再收到客户端发送的信息,会给客户端返回RST,然后导致正阻塞在redline的客户返回ECONNRESET错误。
  • 服务器主机关机,客户端应当能立马知道(跟服务器子进程被杀死时类似)

目前三个文件内容如下

wrapfun.h

代码语言:javascript复制
#ifndef WRAP_FUN
#define WRAP_FUN

#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>

typedef void Sigfunc(int);

void err_sys(const char * str){
    std::cout<<str<<std::endl;
    return ;
}

int Socket(int family, int type, int protocol)
{
	int		n;

	if ( (n = socket(family, type, protocol)) < 0)
		err_sys("socket error");
	return(n);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int		n;

again:
	if ( (n = accept(fd, sa, salenptr)) < 0) {
#ifdef	EPROTO
		if (errno == EPROTO || errno == ECONNABORTED)
#else
		if (errno == ECONNABORTED)
#endif
			goto again;
		else
			err_sys("accept error");
	}
	return(n);
}

void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
	if (bind(fd, sa, salen) < 0)
		err_sys("bind error");
}

void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
	if (connect(fd, sa, salen) < 0)
		err_sys("connect error");
}

void Listen(int fd, int backlog)
{
	char	*ptr;

		/*4can override 2nd argument with environment variable */
	if ( (ptr = getenv("LISTENQ")) != NULL)
		backlog = atoi(ptr);

	if (listen(fd, backlog) < 0)
		err_sys("listen error");
}

ssize_t						/* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)
{
	size_t	nleft;
	ssize_t	nread;
	char	*ptr;

	ptr = (char *)vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nread = read(fd, ptr, nleft)) < 0) {
			if (errno == EINTR)
				nread = 0;		/* and call read() again */
			else
				return(-1);
		} else if (nread == 0)
			break;				/* EOF */

		nleft -= nread;
		ptr    = nread;
	}
	return(n - nleft);		/* return >= 0 */
}
/* end readn */

ssize_t
Readn(int fd, void *ptr, size_t nbytes)
{
	ssize_t		n;

	if ( (n = readn(fd, ptr, nbytes)) < 0)
		err_sys("readn error");
	return(n);
}

ssize_t						/* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)
{
	size_t		nleft;
	ssize_t		nwritten;
	const char	*ptr;

	ptr = (const char *)vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;		/* and call write() again */
			else
				return(-1);			/* error */
		}

		nleft -= nwritten;
		ptr    = nwritten;
	}
	return(n);
}
/* end writen */

void
Writen(int fd, void *ptr, size_t nbytes)
{
	if (writen(fd, ptr, nbytes) != nbytes)
		err_sys("writen error");
}

ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t	n, rc;
	char	c, *ptr;

	ptr = (char*)vptr;
	for (n = 1; n < maxlen; n  ) {
again:
		if ( (rc = read(fd, &c, 1)) == 1) {
			*ptr   = c;
			if (c == 'n')
				break;	/* newline is stored, like fgets() */
		} else if (rc == 0) {
			*ptr = 0;
			return(n - 1);	/* EOF, n - 1 bytes were read */
		} else {
			if (errno == EINTR)
				goto again;
			return(-1);		/* error, errno set by read() */
		}
	}

	*ptr = 0;	/* null terminate like fgets() */
	return(n);
}
/* end readline */

ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
	ssize_t		n;

	if ( (n = readline(fd, ptr, maxlen)) < 0)
		err_sys("readline error");
	return(n);
}

Sigfunc * signal(int signo, Sigfunc *func){
	struct sigaction	act, oact;

	act.sa_handler = func;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	if (signo == SIGALRM) {
#ifdef	SA_INTERRUPT
		act.sa_flags |= SA_INTERRUPT;	/* SunOS 4.x */
#endif
	} else {
#ifdef	SA_RESTART
		act.sa_flags |= SA_RESTART;		/* SVR4, 44BSD */
#endif
	}
	if (sigaction(signo, &act, &oact) < 0)
		return(SIG_ERR);
	return(oact.sa_handler);
}
/* end signal */

Sigfunc *
Signal(int signo, Sigfunc *func)	/* for our signal() function */
{
	Sigfunc	*sigfunc;

	if ( (sigfunc = signal(signo, func)) == SIG_ERR)
		err_sys("signal error");
	return(sigfunc);
}


#endif

echoserv.cpp

代码语言:javascript复制
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include "wrapfun.h"
#include "unp.h"

using namespace std;

void str_echo(int sockfd){
    ssize_t n;
    char buf[MAXLINE];
again:
    while((n=read(sockfd,buf,MAXLINE))>0){
            Writen(sockfd,buf,n);
    }
    if(n<0 && errno==EINTR){
        goto again;
    }else if(n<0){
        cout<<"str_echo: read error"<<endl;
    }
}

void sig_chld(int signo){
    pid_t pid;
    int stat;
    while( (pid=waitpid(-1,&stat,WNOHANG)) > 0){
        std::cout<<"child "<<pid<<" terminated"<<std::endl;
    }
    return ;
}

int main(){
    int listenfd,connfd;
    pid_t childpid;
    socklen_t clilen;
    struct sockaddr_in cliaddr,servaddr;
    listenfd=Socket(AF_INET,SOCK_STREAM,0);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servaddr.sin_port=htons(SERV_PORT);
    Bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
    Listen(listenfd,LISTENQ);
    Signal(SIGCHLD,sig_chld);
    int i=0;
    for(;;){
        clilen=sizeof(cliaddr);
        connfd=Accept(listenfd,(SA*)&cliaddr,&clilen);
        if((childpid=fork())==0){
            close(listenfd);
            str_echo(connfd);
            exit(0);
        }
        close(connfd);
        i  ;
        cout<<i<<" "<<endl;
    }


    return 0;
}

echoclie.cpp

代码语言:javascript复制
#include <iostream>
#include "wrapfun.h"
#include "unp.h"

using namespace std;

int main(int argc,char **argv){
    int i,sockfd[1005];
    struct sockaddr_in servaddr;
    if(argc!=2){
        cout<<"usage:echoclie <ip addr>"<<endl;
        return 0;
    }
    for(i=0;i<10;i  ){
        sockfd[i]=Socket(AF_INET,SOCK_STREAM,0);
        bzero(&servaddr,sizeof(servaddr));
        servaddr.sin_family=AF_INET;
        servaddr.sin_port=htons(SERV_PORT);
        inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
        Connect(sockfd[i],(SA*)&servaddr,sizeof(servaddr));
    }
    str_cli(stdin,sockfd[0]);
    return 0;
}

void str_cli(FILE *fp,int sockfd){
    char sendline[MAXLINE],recvline[MAXLINE];
    while(fgets(sendline,MAXLINE,fp) != NULL){
        Writen(sockfd,sendline,strlen(sendline));
        //if(readline(sockfd,recvline,MAXLINE)==0){
        if(Readline(sockfd,recvline,MAXLINE)<0){
            cout<<"str_cli:server terminated prematurely"<<endl;
        }
        fputs(recvline,stdout);
    }
}

参考

  • 《UNIX网络编程(卷一第三版)》

欢迎与我分享你的看法。 转载请注明出处:http://taowusheng.cn/

0 人点赞