《UNIX网络编程 》学习笔记 (五)
来源:互联网 发布:淘宝情趣用品好卖吗 编辑:程序博客网 时间:2024/05/22 17:36
标准输入 --->fgets--->TCP客户程序------>write------->read--->TCP服务器编写一个完成的echo 程序,来讲解TCP客户/服务器的编写流程。本章的TCP客户/服务器模型:
标准输出 <---fputs<---TCP客户程序<-----read <------write<----TCP服务器
在使用以下内核版本的系统中编译通过: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 小结
这一章通过一个客户/服务器程序展示了在编写网络程序要遇到的问题,包括 信号捕获,处理僵死进程,服务器主机发生错误的几种情况。还讲解了在客户和服务器之间传送的数据的格式。
- 《UNIX网络编程 》学习笔记 (五)
- Unix网络编程学习笔记
- UNIX网络编程学习笔记
- Unix网络编程学习笔记(1)
- 《UNIX网络编程 》学习笔记 (一)
- 《UNIX网络编程 》学习笔记 (二)
- 《UNIX网络编程 》学习笔记 (三)
- 《UNIX网络编程 》学习笔记 (四)
- unix网络编程 学习笔记(精华)
- Unix 网络编程卷一- 学习笔记
- unix网络编程学习笔记1
- unix网络编程学习笔记2
- UNIX网络编程学习笔记(Racoon)
- 学习《UNIX网络编程卷一》笔记
- unix网络编程 学习笔记(精华)
- 《Unix 网络编程》学习笔记 第一章:简介
- unix网络编程笔记
- unix网络编程笔记
- 在vs2005中为listbox加上水平滚动条
- 【收集】易语言界面学习推荐地址(以后继续更新)
- sqlite 自增 table
- WinCE下添加新窗体
- Android大事记(不断更新中)
- 《UNIX网络编程 》学习笔记 (五)
- stm32 smartcard调试--不用st8024
- Hibernate性能优化
- Intrinsics头文件与SIMD指令集、Visual Studio版本对应表
- dom总结
- C# 16进制转换10进制相关函数详解
- 拍照必备-POCO相机
- Ruby 之简介
- CentOS安装中文汉字输入法ibus