UNIX网络编程笔记(4):简单的回射程序
2015-12-02 15:50
417 查看
上一讲中我们通过调用fork函数实现了一个简单的并发时间获取服务器。这是一个简单的并发服务器框架,然而这里使用这个框架实现一个简单的回射服务器会出现一个问题,这个问题就是僵尸子进程。
1、回射程序
下图是这个回射程序的主要结构:
客户向服务器发送一行文字,服务器回射回来,然后客户再输出到屏幕上。这里的fgets、fputs、read、write函数都是标准库函数或系统调用,唯一要说明的就是这个readline函数,这个函数读取文件中的一行,调用read系统调用:
(1)启动服务器:
(2)客户发起请求并输入文字:
服务器成功返回并输出到标准输出上。
(3)服务器端:
2、程序执行时发生了什么?
1、启动时
(1)服务器启动后阻塞在accept函数上,等待客户连接。客户发起连接后accept函数返回,服务器进入子进程调用str_echo函数,函数调用read系统调用,并阻塞在这里,因为客户还没有输入文字;
(2)客户建立连接后,调用str_cli函数,函数调用fgets并阻塞,等待用户输入;
(3)与此同时,服务器父进程关闭连接描述符connfd,重新阻塞在accept函数上,等待下一个客户连接;
以上就是连接建立后发生的事情。
2、执行时
(1)执行时用户在客户端输入文字,fgets函数返回,str_cli函数将输入写入套接字,发送给服务器子进程,然后调用readline,readline函数调用read,并阻塞于read,因为客户等待服务器的应答;
(2)服务器子进程调用read读取客户输入,写入套接字发送给客户作为应答并继续阻塞在read函数上;
(3)客户调用readline函数读取服务器子进程应答并将数据写入标准输出;
(4)循环进行直到客户得到用户的EOF输入;
3、终止时
(1)用户在客户输入EOF字符,fgets返回NULL指针,于是str_cli函数返回;
(2)str_cli函数返回到main函数,main然后通过return终止;
(3)进程终止后关闭所有打开的描述符,因此客户打开的套接字由内核关闭,导致客户TCP发送一个FIN给服务器,服务器以ACK响应,这就是TCP终止的前半部分;
(4)当服务器接收到FIN时,服务器子进程阻塞于readline函数,readline函数返回0,导致str_echo函数返回到main;
(5)服务器子进程通过return终止;
(6)服务器子进程打开的所有描述符关闭。由子进程来关闭已连接套接字会引发TCP连接终止序列的最后两个分节:一个从服务器到客户的FIN和客户到服务器的ACK。至此,连接完全终止;
3、有什么问题?
上面的进程终止还有一部分内容,就是服务器子进程终止时,会给父进程发送一个SIGCHLD信号。这里我们没有捕获信号,而该信号的默认行为是被忽略。由于父进程中没有处理该信号,子进程于是进入僵死状态,即僵尸进程。可以使用ps命令查看:
可以看到,进程号是4228的进程是僵尸进程,而在我们的服务器运行时,客户连接的子进程ID正好是4228。
僵尸进程必须处理。下一讲来用信号处理函数处理僵尸进程。
1、回射程序
下图是这个回射程序的主要结构:
客户向服务器发送一行文字,服务器回射回来,然后客户再输出到屏幕上。这里的fgets、fputs、read、write函数都是标准库函数或系统调用,唯一要说明的就是这个readline函数,这个函数读取文件中的一行,调用read系统调用:
int readline(int fd,void *vptr,size_t maxlen) { ssize_t n,rc; char c,*ptr; ptr=vptr; for(n=1;n<maxlen;n++) { again: if((rc=read(fd,&c,1))==1) { *ptr++=c; if(c=='\n') break; } else if(rc==0) { *ptr=0; return (n-1); } else { if(errno==EINTR) goto again; return (-1); } } *ptr=0; return(n); }这里的服务器程序还是遵循上一讲中的流程,不同的是处理请求的过程,服务器处理请求时调用一个函数str_echo,返回客户写入的一行,代码如下:
#include <sys/socket.h> #include <errno.h> #include <string.h> #include <strings.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <time.h> #include <unistd.h> #define MAXLINE 1024 void str_echo(int sockfd); int main(int argc,char *argv[]) { int listenfd; struct sockaddr_in servaddr; char buff[MAXLINE]; time_t ticks; if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0) { printf("socket error\n"); return 0; } bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5000); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { printf("bind error\n"); return 0; } if(listen(listenfd,5)<0) { printf("listen error\n"); return 0; } int connfd; socklen_t len; struct sockaddr_in cliaddr; pid_t pid; for(;;) { len=sizeof(cliaddr); if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&len))<0) { printf("accept error\n"); return 0; } if((pid=fork())==0) { if(close(listenfd)<0) { printf("close listenfd error\n"); return 0; } printf("[PID]%ld Receive a connection from:%s.%d\n",(long)getpid(),inet_ntop(AF_INET,&cliaddr.sin_addr,buff,sizeof(buff)),ntohs(cliaddr.sin_port)); str_echo(connfd); if(close(connfd)<0) { printf("close child connfd error\n"); return 0; } return 0; } if(close(connfd)<0) { printf("close parent connfd error\n"); return 0; } } } void str_echo(int sockfd) { ssize_t n; char buf[MAXLINE]; again: while((n=read(sockfd,buf,MAXLINE))>0) write(sockfd,buf,n); if(n<0&&errno==EINTR) goto again; else if(n<0) { printf("read error\n"); return; } }客户端程序也大体相同,首先从标准输入中读取一行,写入套接字发送给服务器,接收到套接字后将套接字的内容输出到标准输出上,代码如下:
#include <sys/socket.h>下面是运行结果:
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 1024
void str_cli(FILE *fp,int sockfd);
int readline(int fd,void *vptr,size_t maxlen);
int main(int argc,char *argv[])
{
int sockfd;
char recvline[MAXLINE];
if(argc!=2||strcmp(argv[1],"--help")==0)
{
printf("Usage:%s <IPaddress>\n",argv[0]);
return 0;
}
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("socket error\n");
return 0;
}
struct sockaddr_in servaddr;
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5000);
if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr)<=0)
{
printf("inet_pton error for %s\n",argv[1]);
return 0;
}
if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
{
printf("connect error\n");
return 0;
}
str_cli(stdin,sockfd);
return 0;
}
void str_cli(FILE *fp,int sockfd)
{
char sendline[MAXLINE],recvline[MAXLINE];
printf("puts:");
while(fgets(sendline,MAXLINE,fp))
{
write(sockfd,sendline,strlen(sendline));
if(readline(sockfd,recvline,MAXLINE)==0)
{
printf("str_cli:server terminated prematurely\n");
return;
}
printf("gets:");
fputs(recvline,stdout);
printf("puts:");
}
}
int readline(int fd,void *vptr,size_t maxlen) { ssize_t n,rc; char c,*ptr; ptr=vptr; for(n=1;n<maxlen;n++) { again: if((rc=read(fd,&c,1))==1) { *ptr++=c; if(c=='\n') break; } else if(rc==0) { *ptr=0; return (n-1); } else { if(errno==EINTR) goto again; return (-1); } } *ptr=0; return(n); }
(1)启动服务器:
(2)客户发起请求并输入文字:
服务器成功返回并输出到标准输出上。
(3)服务器端:
2、程序执行时发生了什么?
1、启动时
(1)服务器启动后阻塞在accept函数上,等待客户连接。客户发起连接后accept函数返回,服务器进入子进程调用str_echo函数,函数调用read系统调用,并阻塞在这里,因为客户还没有输入文字;
(2)客户建立连接后,调用str_cli函数,函数调用fgets并阻塞,等待用户输入;
(3)与此同时,服务器父进程关闭连接描述符connfd,重新阻塞在accept函数上,等待下一个客户连接;
以上就是连接建立后发生的事情。
2、执行时
(1)执行时用户在客户端输入文字,fgets函数返回,str_cli函数将输入写入套接字,发送给服务器子进程,然后调用readline,readline函数调用read,并阻塞于read,因为客户等待服务器的应答;
(2)服务器子进程调用read读取客户输入,写入套接字发送给客户作为应答并继续阻塞在read函数上;
(3)客户调用readline函数读取服务器子进程应答并将数据写入标准输出;
(4)循环进行直到客户得到用户的EOF输入;
3、终止时
(1)用户在客户输入EOF字符,fgets返回NULL指针,于是str_cli函数返回;
(2)str_cli函数返回到main函数,main然后通过return终止;
(3)进程终止后关闭所有打开的描述符,因此客户打开的套接字由内核关闭,导致客户TCP发送一个FIN给服务器,服务器以ACK响应,这就是TCP终止的前半部分;
(4)当服务器接收到FIN时,服务器子进程阻塞于readline函数,readline函数返回0,导致str_echo函数返回到main;
(5)服务器子进程通过return终止;
(6)服务器子进程打开的所有描述符关闭。由子进程来关闭已连接套接字会引发TCP连接终止序列的最后两个分节:一个从服务器到客户的FIN和客户到服务器的ACK。至此,连接完全终止;
3、有什么问题?
上面的进程终止还有一部分内容,就是服务器子进程终止时,会给父进程发送一个SIGCHLD信号。这里我们没有捕获信号,而该信号的默认行为是被忽略。由于父进程中没有处理该信号,子进程于是进入僵死状态,即僵尸进程。可以使用ps命令查看:
可以看到,进程号是4228的进程是僵尸进程,而在我们的服务器运行时,客户连接的子进程ID正好是4228。
僵尸进程必须处理。下一讲来用信号处理函数处理僵尸进程。
相关文章推荐
- http 协议
- iOS数据库离线缓存思路和网络层封装
- AngularJS $http
- 了解OkHttp了
- 使用OkHttp访问ssl(https)网络
- OSI七层与TCP/IP五层网络架构详解
- tcp/ip三次握手及四次挥手
- http协议
- http状态码对应表
- 深入浅出 http 请求
- 深入分析JavaWeb Item4 -- Http协议
- Spring Http Invoker
- 【Python】Python的urllib模、urllib2模块的网络下载文件
- VMWare下CentOS的网络设置
- 30、OSPF网络类型
- HTTP Live Streaming直播
- HTTP协议与状态链接解决方案cookie,session的关系
- 神经网络激励函数的形象解释
- 【智能路由器】轻量级web服务器lighttpd架设——打造家庭影院
- rtp rtcp