文章目录
上一篇中写了一个基本的回显服务器,最基本的功能是有了,但是并不够健壮,那么如何对它进行改进呢?我们需要考虑以下几种情况。
之前的程序中,使用的socket相关的api都没有进行错误判断,一旦某个函数发生错误,程序可能就会崩溃,所以我们需要给原生api包裹一层,添加错误判断,就像下面这样:
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才能完成I/O。
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():
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前调用处理信号的函数:
Signal(SIGCHLD,sig_chld);
3.编写sig_chld函数:
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型数值)可能无法得到正确的数据。 第一个常用的方法是把数值转为文本串来传递。第二种是显式地指出所支持的数据类型的二进制格式,包括位数,大端或小端。
除了以上几个问题,以下几个问题现在还无法解决,需要学习其他知识后才能来解决。
wrapfun.h
#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
#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
#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);
}
}
欢迎与我分享你的看法。 转载请注明出处:http://taowusheng.cn/