前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何对回显服务器进行改进_1

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

作者头像
yifei_
发布2022-11-14 14:35:40
6120
发布2022-11-14 14:35:40
举报
文章被收录于专栏:yifei的专栏

文章目录

  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/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-10-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 增加socket函数的错误处理
  • 改写read/write函数
  • 僵死进程的处理
  • 客户服务器之间传递二进制结构
  • 其他问题
  • 目前三个文件内容如下
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档