SIGPIPE
往一個已經接收到FIN的套接中寫是容許的,接收到的FIN僅僅表明對方再也不發送數據。並不能表明我不能發送數據給對方。
往一個FIN結束的進程中寫(write),對方會發送一個RST字段過來,TCP重置。若是再調用write就會產生SIGPIPE信號服務器
一般,咱們只須要忽略這個信號便可:signal(SIGPIPE,ISG_IGN);socket
模擬捕捉SIGPIPE信號。首先是客戶端程序:spa
#include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<netinet/in.h> #include<arpa/inet.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; ssize_t nread; char *bufp=(char*)buf; while(nleft>0) { if((nread=read(fd,bufp,nleft))<0) { if(errno==EINTR) continue; else return -1; } else if(nread==0) return (count-nleft); bufp+=nread; nleft-=nread; } return count; } ssize_t writen(int fd, const void *buf, size_t 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; }else if(nwritten==0) continue; bufp+=nwritten; nleft-=nwritten; } return count; } ssize_t recv_peek(int sockfd,void *buf,size_t len) { while(1) { int ret=recv(sockfd,buf,len,MSG_PEEK);//從sockfd讀取內容到buf,但不去清空sockfd,偷窺 if(ret==-1&&errno==EINTR) continue; return ret; } } //偷窺方案實現readline避免一次讀取一個字符 ssize_t readline(int sockfd,void * buf,size_t maxline) { int ret; int nread; size_t nleft=maxline; char *bufp=(char*)buf; while(1) { ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窺看 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') { ret=readn(sockfd,bufp,i+1);//讀出sockfd中的一行而且清空 if(ret!=i+1) exit(EXIT_FAILURE); return ret; } } if(nread>nleft) exit(EXIT_FAILURE); nleft-=nread; ret=readn(sockfd,bufp,nread); if(ret!=nread) exit(EXIT_FAILURE); bufp+=nread;//移動指針繼續窺看 } return -1; } void echo_cli(int sock) { char sendbuf[1024]={0}; char recvbuf[1024]={0}; while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)//默認有換行符 { //writen(sock,sendbuf,strlen(sendbuf)); //在服務器已經終止以後(kill -9 通訊進程),第一次發送一個字節,引起RST write(sock,sendbuf,1); //第二次write 引起內核發送SIGPIPE信號,默認動做終止進程. write(sock,sendbuf+1,strlen(sendbuf)-1); int ret=readline(sock,recvbuf,1024); if(ret==-1) ERR_EXIT("readline");
//SIGPIPE信號默認終止 else if(ret==0) { printf("service closed\n"); break; } fputs(recvbuf,stdout); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } close(sock); } void handle_sigpipe(int sig) { printf("recive a signal=%d\n",sig); } int main(void) { signal(SIGPIPE,handle_sigpipe);//捕捉第二次write的SIGPIPE信號,默認終止進程 int sock; if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) ERR_EXIT("socket error"); struct sockaddr_in servaddr;//本地協議地址賦給一個套接字 memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//服務器段地址 //inet_aton("127.0.0.1",&servaddr.sin_addr); if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("connect"); //利用getsockname獲取客戶端自己地址和端口,即爲對方accept中的對方套接口 struct sockaddr_in localaddr; socklen_t addrlen=sizeof(localaddr); if(getsockname(sock,(struct sockaddr *)&localaddr,&addrlen)<0) ERR_EXIT("getsockname error"); printf("local IP=%s, local port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port)); //使用getpeername獲取對方地址 echo_cli(sock); return 0; }
服務器端程序沒有變化,只要先用kill -9 退出服務器客戶端已鏈接進程便可實現模擬指針
#include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<netinet/in.h> #include<arpa/inet.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) { size_t nleft=count; ssize_t nread; char *bufp=(char*)buf; while(nleft>0) { if((nread=read(fd,bufp,nleft))<0) { if(errno==EINTR) continue; else return -1; } else if(nread==0) return (count-nleft); bufp+=nread; nleft-=nread; } return count; } ssize_t writen(int fd, const void *buf, size_t 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; }else if(nwritten==0) continue; bufp+=nwritten; nleft-=nwritten; } return count; } ssize_t recv_peek(int sockfd,void *buf,size_t len) { while(1) { int ret=recv(sockfd,buf,len,MSG_PEEK);//從sockfd讀取內容到buf,但不去清空sockfd,偷窺 if(ret==-1&&errno==EINTR) continue; return ret; } } //偷窺方案實現readline避免一次讀取一個字符 ssize_t readline(int sockfd,void * buf,size_t maxline) { int ret; int nread; size_t nleft=maxline; char *bufp=(char*)buf; while(1) { ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窺看 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') { ret=readn(sockfd,bufp,i+1);//讀出sockfd中的一行而且清空 if(ret!=i+1) exit(EXIT_FAILURE); return ret; } } if(nread>nleft) exit(EXIT_FAILURE); nleft-=nread; ret=readn(sockfd,bufp,nread); if(ret!=nread) exit(EXIT_FAILURE); bufp+=nread;//移動指針繼續窺看 } return -1; } void echo_srv(int conn) { int ret; char recvbuf[1024]; while(1) { memset(&recvbuf,0,sizeof(recvbuf)); //使用readn以後客戶端發送的數據不足1024會阻塞 //在客戶端程序中肯定消息的邊界,發送定長包 ret=readline(conn,recvbuf,1024); //客戶端關閉 if(ret==-1) ERR_EXIT("readline"); else if(ret==0) { printf("client close\n"); break;//不用繼續循環等待客戶端數據 } fputs(recvbuf,stdout); writen(conn,recvbuf,strlen(recvbuf)); } } void handle_sigchld(int sig) { while(waitpid(-1,NULL, WNOHANG)>0) ; } int main(void) { signal(SIGCHLD,handle_sigchld); int listenfd; if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) ERR_EXIT("socket error"); //if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0) //本地協議地址賦給一個套接字 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本機地址 //開啓地址重複使用,關閉服務器再打開不用等待TIME_WAIT int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) ERR_EXIT("setsockopt error"); //綁定本地套接字 if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("bind error"); if(listen(listenfd,SOMAXCONN)<0)//設置監聽套接字(被動套接字) ERR_EXIT("listen error"); struct sockaddr_in peeraddr;//對方套接字地址 socklen_t peerlen=sizeof(peeraddr); int conn;//已鏈接套接字(主動套接字) pid_t pid; while(1){ if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0) ERR_EXIT("accept error"); //鏈接好以後就構成鏈接,端口是客戶端的。peeraddr是對端 printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); pid=fork(); if(pid==-1) ERR_EXIT("fork"); if(pid==0){ close(listenfd); echo_srv(conn); //某個客戶端關閉,結束該子進程,不然子進程也去接受鏈接 //雖然結束了exit退出,可是內核還保留了其信息,父進程並未爲其收屍。 exit(EXIT_SUCCESS); }else close(conn); } return 0; }