上一讲中我们通过调用fork函数实现了一个简单的并发时间获取服务器。这是一个简单的并发服务器框架,然而这里使用这个框架实现一个简单的回射服务器会出现一个问题,这个问题就是僵尸子进程。

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。

僵尸进程必须处理。下一讲来用信号处理函数处理僵尸进程。

UNIX网络编程笔记(4):简单的回射程序相关推荐

  1. UNIX网络编程笔记(3):简单的并发服务器

    上一讲中的简单时间获取服务器是一个迭代服务器,对于获取时间来说够用了.迭代服务器有这样的特点:同一时间只能给一个客户服务.也就是说,如果某一时刻服务器与某个客户正在连接,其它客户必须等到上一个客户与服 ...

  2. UNIX网络编程——使用线程的TCP回射服务器程序

    同一进程内的所有线程除了共享全局变量外还共享: (1)进程指令: (2)大多数数据: (3)  打开的文件(即描述符): (4)信号处理函数和信号处置: (5)当前工作目录: (6)用户ID和组ID. ...

  3. UNIX网络编程笔记(7):回射程序的UDP版本

    1.UDP简介 UDP是一个简单的传输层协议,应用进程往一个UDP套接字写入数据,随后被封装到一个UDP数据报,进而又被封装到一个IP数据报,然后发送到目的地.UDP不保证UDP数据报会最终到达目的地 ...

  4. 进程fork和exec ---Unix网络编程笔记

    进程fork和exec ---Unix网络编程笔记 fork 一次调用,两次返回 fork的两个典型用法 最简单的并发服务器---fork子进程 exec -Unix网络编程笔记) fork #inc ...

  5. UNIX网络编程笔记(2):一个简单的时间获取程序

    这一讲通过一个简单的时间获取程序简单介绍套接字编程. 1.套接字API 1.1.套接字地址结构 上一讲中介绍了TCP的一些内容,知道了一个套接字对唯一标识了网络中的一个TCP连接,而一个套接字标识了一 ...

  6. UNIX网络编程笔记(6):I/O复用之select函数

    上一讲中我们正确处理了僵尸子进程,使得这个简单的服务器更加健壮.不幸的是,这个程序仍然有问题.想象一下,如果一个客户正在和一个服务器子进程连接建立完毕正在通话,而服务器子进程意外终止(比如kill), ...

  7. Unix 网络编程(四)- 典型TCP客服服务器程序开发实例及基本套接字API介绍

    转载:http://blog.csdn.net/michael_kong_nju/article/details/43457393 写在开头: 在上一节中我们学习了一些基础的用来支持网络编程的API, ...

  8. UNIX网络编程笔记(1):TCP简介

    1.简介 TCP(Transmission Control Protocol),即传输控制协议,是一种面向连接的.可靠的.基于字节流的传输层通信协议.TCP协议有以下几个特点: TCP提供客户与服务器 ...

  9. 回射程序改进3——消息的群发

    前文列表: 简单的回射程序 回射程序改进1 回射程序改进2--群发消息(fork)错误的尝试 目的: 设计一个C/S程序,客户端发送/接收消息,服务端将从客户端接收到的消息群发给其它已连接套接字,产生 ...

最新文章

  1. 青龙羊毛——鸡厂签到
  2. python在线编程翻译器-【分享】python 翻译器,爬取百度翻译,并附上源码
  3. 2021-02-23 Matlab数据导入--importdata和load函数
  4. git提取和拉取的区别_git fetch和git pull的区别
  5. 天猫精灵方糖拆解报告和芯片详解
  6. transition css_Transition 过渡
  7. Android布局控件之LinearLayout详解
  8. 西安工业大学计算机专业好吗,西安工业大学(专业学位)计算机技术考研难吗
  9. puppet详解(四)——package资源详解
  10. matlab2014演示在哪,matlab2014安装时crack文件在哪里,从哪个文件下面
  11. 圈圈USB学习笔记5--关于HID协议
  12. docker部署xxl-job2.3.0
  13. 3.并列句的起源与本质
  14. 【ML】 第四章 训练模型
  15. 一个铁通员工的自白:我们的效率为何最低
  16. 深度学习中各种图像库的图片读取方式
  17. SqlException:ConnectionTimeout Expired. The timeout period elapsed during the post-login phase
  18. style=扑克牌游戏大家应该都比较熟悉了,一副牌由54张组成,含3~A、2各4张,小王1张,大王1张。 牌面从小到大用如下字符和字符串表示(其中,小写joker表示小王,大写JOKER表示大
  19. 无论创业还是做人,你都需要知道什么是MVPPMF
  20. 利用MFC库获取指定城市的天气实况

热门文章

  1. 内存泄露从入门到精通三部曲之排查方法篇
  2. MyBatis 入门到精通(二) SQL语句映射XML文件
  3. 东哥读书小记 之 《一个广告人的自白》
  4. 看了本书《答案在你心中》里面的很多问题都蛮有意思!!!
  5. C语言学习,关于fflush 和setvbuf
  6. php 修改 wordpress,wordpress怎么编辑代码修改页面
  7. 猫眼html源码,50 行代码教你爬取猫眼电影 TOP100 榜所有信息
  8. 计算机斐波那流程图,循环结构——斐波那契数列.DOC
  9. 微信多开txt_电脑版微信怎么双开、多开
  10. mvc4 html.beginform,MVC4 Html.BeginForm在Internet Explorer中提交按钮 9不工