最近在学习unix网络编程卷一学习,跟着书,敲了下代码。在这里做些记录与复习。
前言
之前初认识了linux下的一些API做了简单的时间获取C/S程序模型。现在进一步了解了关于并发服务器、信号处理、僵死进程等等。做了一个文本回射C/S模型。并做下笔记。
概述
文本回射服务器:
1)客户从终端输入一行文本,发送给服务器
2)服务器从网络输入读入这行文本,并回射给客户
3)客户从网络输入读取这行文本,并显示在终端上
4)服务器每接受一个客户的连接就新建一个子进程来服务这个客户
5)当客户退出需要回收子进程的资源
fork
1)、fork为每个客户派生一个服务进程,子进程需要关闭监听套接字,父进程需要关闭这个已连接的套接字。因为子进程会复制父进程的资源(资源文件描述符+1,不是真的复制文件),如果没有关闭,在客户退出后,没能关闭这些资源
2)、fork返回两个pid_t类型的数值。父进程返回子进程的pid(>0)子进程返回(0)
进程终止
1)当我们键入EOF字符时(ctrl+d)或者客户端终止,客户端发送FIN给服务端,服务端响应ACK,省略其他tcp连接释放步骤。
2)当服务器接收到客户端的FIN时,服务器子进程阻塞于readline调用,于是马上返回,子进程调用exit终止。随之子进程关闭所打开的描述符。
3)子进程终止时会给父进程发送一个SIGCHLD信号,需要对它进行捕捉,否则默认忽略,子进程进入僵死状态。
信号捕捉
1)为了给子进程收尸,父进程必须对其捕捉。(SIGKILL和SIGTOP不能捕捉)
2)当信号来了,程序实际是进入软中断状态,需要设置一个信号处理函数。
3)Sigfunc signal(int signo, Sigfuncfunc)是参照原书封装了一个unix原生API的信号函数,signo是信号编号,func是一个函数指针,信号处理函数的入口。sigfunc是一个类型定义typedef void Sigfunc(int);
函数类型
僵尸进程
定义
是指子进程退出时,父进程并未对其发出的SIGCHLD信号进行适当处理,导致子进程停留在僵死状态等待其父进程为其收尸,这个状态下的子进程就是僵死进程。
产生的原因
子进程退出时,父进程没有为子进程收尸。
危害
子进程退出时,虽然会关闭它的资源描述符(比如套接字),但是进程描述符仍然占用,需要父进程来回收,系统的进程描述符是有限的,大量的僵死进程将拖垮服务器性能,最终无法新建进程。
处理方法
父进程捕捉信号,调用wait或者waitpid来进行回收
wait和waitpid函数
#include <sys/wait.h>
pid_t wait(int *staloc);
pid_t waitpid(pid_t pid, int *staloc, int options);
//返回:若成功则返回进程ID,若出错则为0或者-1,以及由staloc返回的子进程终止状态。
区别:wait_pid就等待哪个进程以及是否阻塞给了更多的控制。pid可以指定想等待的进程,值-1表示等待第一个终止的子进程。options可指定附加选项,比如此文用的WNOHANG
告诉内核在没有已终止子进程时不要阻塞。
为什么要设置非阻塞:
因为linux的信号机制,并不排队,当你在处理其中一个信号的时候可能错过了其他信号的捕捉。因此这里的void sig_chld(int signo)
信号处理函数使用while里面调用waitpid以便捕捉所有信号。
完整代码
自己常用的函数集
mfs.cpp:
#include "mfs.h"
#include <iostream>
using namespace std;
/************iofunction***********/
ssize_t readline(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){
cout<<nread<<endl;
if(errno == EINTR)//中断重新调用read
nread = 0;
else return (-1);
}else if (!nread)//EOF
break;
nleft -= nread;
ptr += nread;
if(ptr[nread-1]='\n')break;//readline function return when meet '\n'
}
return (n-nleft);
}
ssize_t writen(int fd, void const *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;
else return(-1);
}
nleft -= nwritten;
ptr +=nwritten;
}
return n;
}
/**********Signal function***********/
Sigfunc *signal(int signo, Sigfunc*func){
struct sigaction act, oact;
/*
If SA_SIGINFO is specified in sa_flags, then sa_sigaction (instead of
sa_handler) specifies the signal-handling function for signum. This
function receives three arguments, as described below.
如果你的应用程序只需要接收信号,而不需要接收额外信息,那你需要的设置的是sa_handler,而不是sa_sigaction
*/
act.sa_handler =func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
/*SIGALRMS是计时器中断,否则应该设置自动重启*/
if(signo == SIGALRM){
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
}else{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART;//被中断的调用自动重启
#endif
}
if (sigaction(signo, &act, &oact) < 0)
return (SIG_ERR);
return (oact.sa_handler);//返回发生信号时的前一个动作
}
void sig_chld(int signo){
pid_t pid;
int stat;
while( (pid = waitpid(-1, &stat,WNOHANG)) >0 )
printf ("chaild %d terminated\n", pid);
return ;
}
server.cpp
#include "mfs.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
using namespace std;
void str_serv(int sockfd){
ssize_t n;
char buf[MAXLINE];
while((n = readline(sockfd, buf ,MAXLINE))> 0){
buf[n]='\0';
std::cout<<buf<<std::endl;
writen(sockfd, buf, strlen(buf));
}
}
int main(int argc ,char **argv){
int listenfd;
int connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr;
struct sockaddr_in servaddr;
char buff[MAXLINE];
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(2020);
bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
signal(SIGCHLD, sig_chld);
for(; ;){
clilen = sizeof(cliaddr);
if( (connfd = accept(listenfd, (SA*)&cliaddr, &clilen)) <0 ){
if(errno == EINTR)
continue; //restart
else perror("accept error");
}
if( (childpid = fork()) == 0){
printf("connection from %s, port %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff) ),ntohs(cliaddr.sin_port));
close(listenfd);
str_serv(connfd);
exit(0);
}
close(connfd);
}
}
client.cpp
#include "mfs.h"
#include <iostream>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
void str_cli(int sockfd){
std::string sendline;
char recvline[MAXLINE];
int nread;
while(std::cin>>sendline){
sendline+="\r\n";
int fuck = writen(sockfd, sendline.c_str(), sendline.size());
std::cout<<"等待信息返回"<<std::endl;
if( (nread=readline(sockfd, recvline, MAXLINE)) >0){
recvline[nread]=0;
}
if(nread < 0) {
std::cout<<"nread <0"<<std::endl;
exit(1);
}
std::cout<<recvline<<std::endl;
}
}
int main (int argc, char **argv){
std::cout<<1<<std::endl;
int sockfd;
struct sockaddr_in servaddr;
if(argc != 2){
perror ("usage: tcpcli<IPaddress>");
exit(1);
}
sockfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port =htons(2020);
inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
if(connect(sockfd, (SA *)&servaddr, sizeof(servaddr) ))
std::cout<<"connect error"<<std::endl;
str_cli(sockfd);
exit(0);
}
参考:《unix网络编程 卷1:套接字联网API》