这一章主要是完成一个完整的tcp客户/服务器程序.通过一很简单的例子.弄清客户和服务器如何启动,如何终止,发生了某些错误会发生什么.这些事很重要的


 客户端代码

  1. #include "unp.h"
  2. //static void str_cli1(FILE*fp,int sockfd);
  3. int main(int argc,char *argv[])
  4. {
  5. int sockfd;
  6. struct sockaddr_in servaddr;
  7. sockfd=Socket(AF_INET,SOCK_STREAM,0);
  8. bzero(&servaddr,sizeof(servaddr));
  9. servaddr.sin_family=AF_INET;
  10. servaddr.sin_port=htons(SERV_PORT);
  11. Inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
  12. Connect(sockfd,(SA*)&servaddr,sizeof(servaddr));
  13. str_cli(stdin,sockfd);//逻辑处理
  14. exit(0);
  15. }
 str_cli函数代码
  1. #include "unp.h"
  2. void str_cli(FILE*fp,int sockfd)
  3. {
  4. char sendline[MAXLINE],recvline[MAXLINE];
  5. while(Fgets(sendline,MAXLINE,fp)!=NULL){//从键盘接收保存到sendline
  6. Writen(sockfd,sendline,strlen(sendline)); //写给服务端
  7. if(Readline(sockfd,recvline,MAXLINE)==0)//接收从服务端返回的数据
  8. err_quit("str_cli:服务端提前关闭");
  9. Fputs(recvline,stdout);//打印到屏幕
  10. }
  11. }
(1)三次握手状态
当客户端调用connect(阻塞状态)的时候.将发送一个syn.此时状态为syn_sent.
服务端此刻在监听.创建了一个syn未连接队列.一个已连接队列.接收到客户端的syn包,将放在syn未连接队列.调用accept(阻塞)将发送一个syn+ack.服务端状态为syn_recv
客户端接收到服务端的syn+ack后.connect返回.将发送一个ack给服务端.服务端接收到ack后.syn未连接将会被转移到已连接队列.然后Accept从中拿一个已连接socket返回...双方状态为ESTABLISH
此时客户端阻塞于str_cli函数中的fget调用.因为我们木有从键盘输入.
当服务端的accept返回.服务端调用fork.再有子进程调用str_echo.该函数调用了readline,readline调用了read.而read在等待客户数据的到来而阻塞
另外.服务端的父进程再次调用accept并且阻塞(未完成连接队列跟已完成的链接队列都为空).此时监听套接字为Listen状态
以上.我们将有3个进程都在睡眠.客户进程,服务器父进程,服务器子进程

  1. #include "unp.h"
  2. int main(int argc,char *argv[])
  3. {
  4. int listenfd=Socket(AF_INET,SOCK_STREAM,0);
  5. struct sockaddr_in servaddr;
  6. servaddr.sin_family=AF_INET;
  7. servaddr.sin_port=htons(SERV_PORT);
  8. servaddr.sin_addr.s_addr=0;
  9. Bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
  10. Listen(listenfd,LISTENQ);
  11. for(;;){
  12. pid_t childpid;
  13. int connect=Accept(listenfd,NULL,NULL);
  14. if((childpid=Fork())==0){
  15. Close(listenfd);
  16. str_echo(connect);
  17. exit(0);
  18. }
  19. close(connect);
  20. }
  21. }

str_echo套接字回射数据
  1. #include "unp.h"
  2. void str_echo(int sockfd)
  3. {
  4. ssize_t n;
  5. char buf[MAXLINE];
  6. again:
  7. while((n=read(sockfd,buf,MAXLINE))>0)//读取
  8. Writen(sockfd,buf,n);//返回
  9. if(n<0&&errno==EINTR)
  10. goto again;
  11. else if(n<0)
  12. err_sys("str_echo :read error");
  13. }
客户端关闭状态
关闭客户端键入cltr+d使得客户端终止
过程分析
(1)我们键入eof.fgets返回一个空指针.str_cli返回
(2)str_cli返回到客户端main函数后.main通过exit终止
(3)进程一旦终止将关闭所有打开的描述符,内核将关闭客户打开的套接字.这将会使得客户端的tcp发送一个FIN(进入FIN_WAIT_1).
服务端TCP受到FIN.将返回ACK响应(服务端进入CLOSE_WAIT)状态.客户端收到服务端的ACK.将进入FIN_WAIT_2
(4)服务端受到由客户端发送的FIN.服务器子进程还阻塞到readline调用.接收了fin.将返回0.使得Str_echo函数返回到服务器子进程的main函数中
(5)服务器子进程通过调用exit来终止.这将导致所有描述符都关闭..子进程关闭已连接套接字回使得服务端发送FIN(LAST_ACK).
客户端一旦接收到服务端发来的FIN将进入time_wait状态.并发送一个ACK给服务端.服务端接收到ACK后.将处于close.
以上是tcp 4次断手的过程

(7)这里有一个bug.服务器子进程终止时.给父进程发送一个SIGCHLD信号.但以上代码没捕捉这个信号,默认忽略.
这样会使得子进程进入僵尸状态.占用系统资源.

POSIX信号处理
信号也称为软件中断.信号通常异步发生的
信号来源:一个进程发给其他进程,由内核发给某个进程.
信号发生对应的动作:
(1)我们可以提供一个函数,用来执行对应的动作,这样的函数表示信号处理函数,这种行为表示捕获信号.但有2个信号无法捕捉(SIGKILL跟SIGSTOP)
(2)把信号的处置设定为SIG_IGN来忽略它.
(3)把信号的处置设置为SIG_DFL启动默认处置(绝大多数是终止进程)
这是实现一个signal函数
  1. Sigfunc * signal(int signo,Sigfunc *func)
  2. {
  3. struct sigaction act,oact;
  4. act.sa_handler=func;
  5. sigemptyset(&act.sa_mask);// empty the block setmask
  6. act.sa_flags=0;
  7. if(sigaction(signo,&act,&oact)<0)
  8. return (SIG_ERR);
  9. return oact.sa_handler;
  10. }
1.当信号处理函数运行时,突然发生一个新的信号.这个信号是会被阻塞的,而且安装处理函数时在sa_mask信号集任何额外的信号也会被阻塞
2.一个信号被阻塞期间产生一次或多次,该信号被解除阻塞后,只会提交一次,可以用sigprocmask函数选择性的阻塞跟解除阻塞

处理僵尸进程
设置僵尸状态是为了维护子进程的信息,父进程可能在某个时刻需要获取子进程相关信息,这些信息包括子进程的进程ID,终止原因,资源利用信息,
如果父进程挂了,子进程处于僵尸状态.init 进程将成为这些子进程的父进程.一般会清理她们,去除僵尸状态.
用以下函数来捕捉信号
  1. void sig_chld(int signo)
  2. {
  3. pid_t pid;
  4. int stat;
  5. pid=wait(&stat);
  6. printf("child %d stoped\n",pid);
  7. }

(书上用的是solaris9特定的例子.以下是该环境说明.被信号打断的系统调用不会重启)
当子进程挂了.发送一个SIG_CHLD信号给父进程,此时父进程阻塞于accept,因为信号原因,accept被打断,将返回一个EInTR错误(被中断的系统调用),父进程不处理该错误,父进程将终止

我用的是centos.这个环境下,被信号打断的系统调用将会自动重启不会出现书上的错误
[root@localhost five]#
child 13099 stoped

处理被中断的系统调用
慢系统调用:表示可能永远阻塞的系统调用
适用于慢系统调用的基本规则:当阻塞于某个慢系统调用的一个进程捕获某个信号且处理函数返回时,该系统调用可能返回一个EINtR错误,有些内核会自动重启某些被中断的系统调用,不过为了方便移植,我们编写捕获信号函数时,必须被返回EINTR有准备,一般处理规则如下
  1. if((connfd=accept(listenfd,NULL,NULL)<0){
  2. if(errno==EINTR)
  3. continue;
  4. else
  5. err_sys("accept error");
  6. }
注意:这段代码表示的就是自己重启被中断的系统调用,不过有一个函数我们不能重启:connect.该函数返回EINTR.不能再次重启,否则立刻返回一个该套接字已被使用的错误,因为connect发送syn.一直没收到服务端的ack,一旦被打断,重启可能又发送syn.这样会又用该套接字会被认为是不对的,比如打断后,服务端恰好已发送了ack只是没接受到而已.

wait跟waitpid函数
  1. pid_t wait(int *statloc);
  2. pid_t waitpid(pid_t pid,int *statloc,int options);


服务器进程终止
服务器子进程杀死.将发送一个fin给客户端,客户端回一个ack给服务端.SIGCHLD信号.然而客户端此时依旧阻塞在fgets调用等待从中断接收一行文本
如果我们依旧客户端write发送数据给服务端,服务端接收数据时.发现之前打开那个套接字进程已经挂了,于是响应一个RST.
这里要分情况 1.如果客户端调用readline在收到RST包之前.那客户端readline返回0表示服务端已经关闭,否则如果客户端在收到RST后,才调用readline.那readline将会返回一个ECONNRESET(connection reset by peer)
  这种情况一般发生在服务进程较客户进程提前终止。当服务进程终止时会向客户 TCP 发送 FIN 分节,客户 TCP
回应 ACK,服务 TCP 将转入 FIN_WAIT2 状态。此时如果客户进程没有处理该 FIN (如阻塞在其它调用上而没有关闭 Socket
时),则客户 TCP 将处于 CLOSE_WAIT 状态。当客户进程再次向 FIN_WAIT2 状态的服务 TCP 发送数据时,则服务 TCP
将立刻响应 RST。一般来说,这种情况还可以会引发另外的应用程序异常,客户进程在发送完数据后,往往会等待从网络IO接收数据,很典型的如
read 或 readline 调用,此时由于执行时序的原因,如果该调用发生在 RST 分节收到前执行的话,那么结果是客户进程会得到一个非预期的
EOF 错误。此时一般会输出“server terminated prematurely”-“服务器过早终止”错误

这里的问题在于:当fin到达套接字时,客户阻塞在fgets.但客户实际在对应2个描述符--套接字跟用户输入,它也不能单纯阻塞到这个2个之中一个.而应该阻塞在其中任何一个.(select poll可以解决)

SIGPIPE信号
如果一个进程向已收到的RST的套接字继续执行写操作的话,内核将向该进程发送一个SIGPIPE信号,该信号默认行为是终止.进程必须捕获该信号以免不情愿的终止

服务器主机奔溃
(1)当服务器主机奔溃时,已有的网络连接发不出任何东西(不是正常关机)
(2)当客户write发送数据候,阻塞到readline中,等待回射的应答.
(3)用tcpdump观察,可以看出客户TCP一直在持续重传数据分节,想要重服务端接收一个Ack,一共等待9分钟放弃重传,一旦客户TCP放弃重传.将给客户端返回一个ETIMEDOUT错误..如果一个路由已经判断服务器主机不可达,将响应一个 "destination unreachable (目的不可达)ICMP消息",返回的错误将是 EHOSTUNREACH

服务器主机奔溃后重启
假设客户端已跟服务器建立连接,服务器突然断网挂了,然后又重启了(客户端不知道)
当客户端发送数据时,客户TCP将收到RST,客户正阻塞readline.这将导致readline返回ECONNRESET错误


服务器正常关机.
init进程通常先给所有进程发送SIGTERM信号(这样给所有运行的进程一小段时间来清除和终止).然后再给仍然在运行的进程发送SIGKILL,服务器进程终止过程跟之前一样,






















第5章-unix网络编程 TCP/服务端程序示例的更多相关文章

  1. UNIX网络编程---TCP客户/服务器程序示例(五)

    一.概述 客户从标准输入读入一行文本,并写给服务器 服务器从网络输入读入这行文本,并回射给客户 客户从网络输入读入这行回射文本,并显示在标准输出上 二.TCP回射服务器程序:main函数 这里给了函数 ...

  2. Java网络编程(TCP服务端)

    /* * TCP服务端: * 1.创建服务端socket服务,并监听一个端口 * 2.服务端为了给客户端提供服务,获取客户端的内容,可以通过accept方法获取连接过来的客户端对象 * 3.可以通过获 ...

  3. C#网络编程TCP通信实例程序简单设计

    C#网络编程TCP通信实例程序简单设计 采用自带 TcpClient和TcpListener设计一个Tcp通信的例子 只实现了TCP通信 通信程序截图: 压力测试服务端截图: 俩个客户端链接服务端测试 ...

  4. 03-案例——多任务版TCP服务端程序开发

    案例——多任务版TCP服务端程序开发   1. 需求     目前我们开发的TCP服务端程序只能服务于一个客户端,如何开发一个多任务版的TCP服务端程序能够服务于多个客户端呢?完成多任务,可以使用线程 ...

  5. python网络编程TCP服务多客户端的服务端开发

    #服务多客户端TCP服务端开发 2 #方法说明 3 """ 4 bind(host,port)表示绑定端口号,host是ip地址,ip地址一般不进 行绑定,表示本机的任何 ...

  6. 【网络编程】服务端产生大量的close_wait状态的进程分析

    首先要明白close_wait状态是在tcp通信四次握手时的一个中间状态: 即当被动关闭方发送完ACK后进入的状态.这个状态的结束,即要达到下一个状态LASK_ACK需要在发无端发送完剩余的数据后(s ...

  7. Python中的Tcp协议的应用之Tcp服务端程序开发

    TCP通信协议是面向连接的可靠的网络通信协议. 网络间想要进行数据传输必须要用到socket,socket翻译过来叫做套接字,其主要作用是不同设备或同一台设备之间的进程通信工具. Python中的Tc ...

  8. Mina TCP服务端客户端 示例

    服务端代码: package com.xd.nms.example; import java.io.IOException; import java.net.InetSocketAddress; im ...

  9. unix网络编程——TCP套接字编程

    TCP客户端和服务端所需的基本套接字.服务器先启动,之后的某个时刻客户端启动并试图连接到服务器.之后客户端向服务器发送请求,服务器处理请求,并给客户端一个响应.该过程一直持续下去,直到客户端关闭,给服 ...

随机推荐

  1. win8/10 bcdboot引导修复命令的原理和使用方法

    win8/10 bcdboot引导修复命令的原理和使用方法 [迅维网原创文章禁止转载] (本文所述已用UEFI+GPT.BIOS+MBR,WIN10 64位企业版和专业版测试过) 在win8/10系统 ...

  2. rem和em的区别

    原文链接:http://caibaojian.com/rem-vs-em.html rem 单位如何转换为像素值 当使用 rem 单位,他们转化为像素大小取决于页根元素的字体大小,即 html 元素的 ...

  3. Mac配置gdb的一些问题

    1.Unable to find Mach task port for process-id 1527: (os/kern) failure (0x5).   (please check gdb is ...

  4. LeetCode935

    问题:935. 骑士拨号器 国际象棋中的骑士可以按下图所示进行移动:  .            这一次,我们将 “骑士” 放在电话拨号盘的任意数字键(如上图所示)上,接下来,骑士将会跳 N-1 步. ...

  5. python爬虫基础14-selenium大全8/8-常见问题

    Selenium笔记(8)常见的坑 本文集链接:https://www.jianshu.com/nb/25338984 用Xpath查找数据时无法直接获取节点属性 通常在我们使用xpath时,可以使用 ...

  6. Beyond Compare 4 30天试用期后,破解方法

    Beyond Compare 4 30天试用期后,破解方法. 方法一:在安装目录下找到文件BCUnrar.dll,比如:D:\software\Beyond Compare 4,重命名该文件即可. 重 ...

  7. 让Python带你看一场唯美的横飘雪!

    “北国风光,千里冰封,万里雪飘”,这句诗描写了一句美丽肃静的风光图,恰逢昨天笔者这边也下了一场比较大的雪,要不今天就用Python带大家也来领略一次美丽的雪景? 开发环境 版本:Python3.6 系 ...

  8. selenium2中的TestNg注解和数据驱动的简介及使用

    TestNg常用注解介绍,先来张图: 先看一下,以上各个注释的运行次序: @Test 表示的意义:    1.表示示该方法是一个测试方法,在运行时,会自动的运行有@Test注脚的方法. 示例: @Be ...

  9. App架构经验总结

    作者:李纪钢,网名 Keegan小钢,博客地址:http://keeganlee.me.目前在广州日报新媒体有限公司,负责移动产品的研发工作. 关于:本文整理自CSDN架构主题月子活动金牌架构师微课堂 ...

  10. 80x86保护模式下IDT和中断调用过程分析

    80x86保护模式下IDT和中断调用过程分析 1.中断描述符表(IDT),将每个异常或中断向量分别与它们的处理过程联系起来.与GDT和LDT类似,IDT也是由8字节长度的描述符组成.IDT空描述符的存 ...