《UNIX网络编程 》学习笔记 (五)

来源:互联网 发布:淘宝情趣用品好卖吗 编辑:程序博客网 时间:2024/05/22 17:36
第五章 TCP客户/服务器程序示例
5.1 概述
编写一个完成的echo 程序,来讲解TCP客户/服务器的编写流程。
本章的TCP客户/服务器模型:
                    标准输入 --->fgets--->TCP客户程序------>write------->read--->TCP服务器
                    标准输出 <---fputs<---TCP客户程序<-----read <------write<----TCP服务器
5.2 TCP的echo服务器程序:main函数
在使用以下内核版本的系统中编译通过:Linux version 2.6.38-8-generic (buildd@vernadsky) (gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu3) )
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <string.h>#include <unistd.h>#include <errno.h>#define SERV_PORT 5508#define LISTENQ 10int main(int argc, char **argv){intlistenfd,connfd;pid_tchildpid;socklen_t clilen;struct sockaddr_incliaddr,servaddr;memset( (struct sockaddr *)&servaddr,0,sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);if( (listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1){printf("create socket failure");exit(1);}if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) == -1){printf("call bind failure");exit(1);}if(listen(listenfd,LISTENQ) == -1){printf("call listen failure");exit(1);}for(;;){clilen = sizeof(cliaddr);if( (connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&clilen)) == -1){printf("call accept failure");}if( (childpid = fork()) == 0){close(listenfd);str_echo(connfd);/**process the request**/exit(0);}close(connfd);}}

5.3  TCP 的echo 服务器程序的 str_echo 函数
str_echo 具体处理每个客户的请求。读取客户发送的数据并echo给客户。
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <string.h>#include <unistd.h>#include <errno.h>#define SERV_PORT 5508#define LISTENQ 10#define MAXLINE 1024voidstr_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;elseif(n<0){printf("read error");exit(0);}}

5.4 TCP 的 echo 客户程序的main函数

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <string.h>#include <unistd.h>#include <errno.h>#define SERV_PORT 5508#define LISTENQ 10#define MAXLINE 1024intmain(int argc, char **argv){if(argc!=2){printf("Parameter error !!");exit(1);}int sockfd;struct sockaddr_inservaddr;memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family = AF_INET;inet_pton(AF_INET,argv[1],&servaddr.sin_addr);servaddr.sin_port = htons(SERV_PORT);sockfd = socket(AF_INET,SOCK_STREAM,0);connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));str_cli(stdin,sockfd);exit(0);}

5.5 TCP的echo客户程序的str_cli 函数

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <string.h>#include <unistd.h>#include <errno.h>#define SERV_PORT 5508#define LISTENQ 10#define MAXLINE 1024/** *没有添加 write 和 read 的错误处理***/void str_cli(FILE *fp,int sockfd){char SendLine[MAXLINE],RecvLine[MAXLINE];char *ptr;size_t retsz,wtsz,rdsz;while(fgets(SendLine,MAXLINE,fp)!=NULL){wtsz = rdsz = strlen(SendLine);ptr = SendLine;while(wtsz > 0){retsz = write(sockfd,ptr,wtsz);wtsz -= retsz;ptr += retsz;}ptr = RecvLine;while(rdsz > 0){retsz = read(sockfd,ptr,rdsz);rdsz -= retsz;ptr += retsz;}*ptr = '\0';fputs(RecvLine,stdout);}}

5.6 5.7 略过;

5.8 POSIX 信号处理

(1)信号(signal)也称为软件中断(software interrupt),就是告知某个进程发生了某个事件的通知。

(2)信号通常是异步发生的,也就是說进程预先不知道信号发生的准确时刻。

(3)信号可以由一个进程发给自身或另一个进程,也可以由内核发给某个进程。
(4)每一个信号都有一个与之关联的处置(disposition)也称为行为(action).通过调用sigaction来设定一个信号的的行为,并有三种选择。
(5)sigaction设置信号行为的三种选择:
((1))调用sigaction设置一个函数,当指定的信号发生时,就调用这个函数,这样的函数称为信号处理函数(signal handler).这种行为称为捕获(catching).有两个信号不能被捕获,分别是:SIGKILL,SIGSTOP 。
  信号处理函数(signal handler)的原型:void handler(int signo);
 大多数信号只要求我们调用sigaction并设置信号处理函数。但像 SIGIO,SIGPOLL,SIGURG等等一些信号还需要捕获它的进程做些额外的工作。
((2))可以把某个信号的处置设置为SIG_IGN(ignore)来忽略它。SIGKILL 和SIGSTOP这两个信号不能被忽略。
((3))可以把某个信号的处置设置为SIG_DFL(default)来启用它的默认处置。默认处置通常是在收到信号后终止进程。但有一些进程的默认处置是忽略。

(6)signal 函数

(1)建立信号处置的POSIX方法是调用sigaction函数。也可以调用signal函数,但是signal函数不是POSIX标准函数,并且是不可移植的。

(2)signal 函数的第一个参数是信号名,第二个参数或为指向函数的指针或为常值SIG_IGN或SIG_DFL.

(3)sigaction 函数的简化调用,自定义一个signal函数,在这个函数中调用 sigaction函数,从而达到简化调用的目的。自定义signal函数的代码如下:

#include <signal.h>typedef void Sigfunc(int);/** *自定义signal函数,简单的对sigaction函数进行封装。**/Sigfunc *signal(int signo,Sigfunc *func){struct sigaction act,oact;act.sa_handler = func;//sigaction的信号处理函数sigemptyset(&act.sa_mask);//调用信号处理函数期间将被阻塞的信号集act.sa_flags = 0;if(sigaction(signo,&act,&oact)<0)return (NULL);elsereturn (oact.sa_handler);}
(4)信号处理函数一旦安装,便一直安装着。

(5)信号处理函数运行期间,被递交的信号是阻塞的,并且sa_mask中设置的所有信号也将被阻塞。

(6)如果一个信号被阻塞期间产生了多次,那么信号解阻塞后通常只递交一次。UNIX的信号默认不排队。

(7)sigprocmask函数可以选择性的阻塞或者解阻塞一组信号。

5.9 处理SIGCHLD信号

(1)设置僵死(zombie)状态的目的是维护子进程的信息。以便父进程在以后莫个时刻获取子进程的进程ID,终止状态以及资源利用信息。

(2)如果一个进程终止,而该进程有子进程正处于僵死状态,那么所有僵死子进程的父进程ID将被重置为1(init进程),init进程将清理这些僵死进程。

(3)如果fork子进程,那么就要wait它们,以防止它们变成僵死进程。

(4)捕获SIGCHLD信号,并在信号处理函数中wait子进程,我们始终应该调用waitpid而非wait来处理子进程。

(5)我们始终应该检查慢系统调用是否返回EINTR错误。并决定是否重启这些系统调用。(一些系统会自动重启被中断的系统调用)。

(6)connect不能被重启,当connect函数被信号中断且不自动重启时,我们必须调用select来等待连接完成。

5.10 wait和waitpid函数

#include <sys/wait.h>

pid_t  wait(int *static);

pid_t  waitpid(pid_t pid,int *static ,int options);

返回值:成功返回进程ID,出错返回返回0或-1;

参数:int *static ,wait 和waitpid返回时会将子进程的终止状态(一个整数)存放在static中。

pit_t pid 想等待的进程ID号。-1表示等待第一个结束的子进程。

int options 附加选项,常用的是WNOHANG,告知内核在没有以终止子进程时不要阻塞。

wait和waitpid的区别: wait 等待第一个结束的子进程,如果没有结束的子进程,wait将阻塞。waitpid 通过参数设置,可以在没有子进程结束时waitpid不阻塞。

5.11 accept返回前连接终止

 Berkeley 的实现在内核中处理终止的连接。POSIX 规定返回一个ECONNABORTED 的 errno. 

5.12 服务器进程终止

如果向一个服务进程已终止的服务器发起连接,服务器将返回一个RST 信号。

5.13 SIGPIPE 信号

向一个接收到FIN的套接字写数据会收到RST,

向一个已接收到RST的套接字写数据将引发SIGPIPE信号。并且写操作返回EPIPE错误。

SIGPIPE信号的默认行为是终止进程。

5.14 服务器主机崩溃

如果服务器主机崩溃没有对客户做出响应,将返回ETIMEOUT错误。

如果中间路由器检测到服务器主机不可达,将响应destination unreachable的ICMP消息,内核将返回EHOSTUNREACH错误。

5.15 服务器主机崩溃重启

当服务器主机崩溃重启后,它的所有连接都已经丢失,因此服务器TCP对所收到的来自客户的数据分节响应一个RST。

5.16 服务器主机关机

unix系统关机时,init进程通常先给所有进程发送SIGTERM信号。等待5-20秒后给所有仍然在运行的进程发送SIGKILL信号,这么做的目的是给进程一小段时间来清除和终止。

5.17 TCP程序例子小结

需要通信的客户/服务器程序在通信之前都要指定套接字对。【本地IP地址,本地端口号,外地IP地址,外地端口】。

客户程序的本地IP地址和本地端口号通常是内核分配。服务程序的本地IP地址和端口号有bind函数指定。

5.18 数据格式

网络传递数据遇到的一些问题:

(1)不同的实现以不同的格式存储二进制数,最常见的是大端字节序和小端字节序。

(2)不同的实现在存储相同的C数据类型上可能存在差异,例如32位系统中的long 为32位,64位系统中的long为64位。

(3)不同的实现给结构打包的方式存在差异,取决于各种数据类型所用的位数以及机器的对齐限制,因此,穿越套接字传送二进制结构绝不明智。

解决上述问题的两个常用方法:

(1)把所有的数值数据作为文本串来传递,前提是客户和服务器机器具有相同的字符集。

(2)显式定义所支持数据类型的二进制格式(位数,大端或小端字节序),并以这样的格式在客户与服务器之间传递所有数据。

5.19 小结

这一章通过一个客户/服务器程序展示了在编写网络程序要遇到的问题,包括 信号捕获,处理僵死进程,服务器主机发生错误的几种情况。还讲解了在客户和服务器之间传送的数据的格式。

















原创粉丝点击