1. 简单回射客户/服务器图解
2.代码实例
以下程序摘自UNIX网络编程,我会对其加上一些注解,方便阅读
TCP回射服务器程序:main函数
/*怎么编译运行可以查看我的另一篇博文:unpv13e编译和运行
/* tcpserv01.c */ #include <sys/socket.h> #include <strings.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> int main(int argc,char **argv) { int listenfd,connfd; //用来存储监听套接字fd和连接套接字fd pid_t childpid; // 用来存储子进程fd socklen_t clilen; //用来存储套接字地址大小 struct sockaddr_in cliaddr,servaddr; //用来存储服务端和客户端套接字地址 if((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0) //创建使用ipv4,tcp的流套接字 { perror("socket"); exit(1); } bzero(&servaddr,sizeof(servaddr)); //初始化服务端套接字地址为0,bzero已经过时,此处最好使用memset servaddr.sin_family = AF_INET; //使用ipv4协议族 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //将主机字节序转换为网络字节序(默认大端字节序)并赋予相应值,INADDR_ANY表示接受任意地址的请求 servaddr.sin_port = htons(9877); //赋予服务端套接字地址结构中的端口值 if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0) //将创建的套接字listenfd和服务端套接字地址结构绑定在一起 { perror("bind"); exit(1); } if(listen(listenfd,5) < 0) //监听端口收到的请求,最大连接数设置为5,调用listen后内核会维护两个队列,具体参见tcp套接字编程 { perror("listen"); exit(1); } for(;;) //最常见的服务进程是被动的等待请求,除非手动关闭或者遇到异常,否则不会主动关闭 { clilen = sizeof(cliaddr); if((connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&clilen)) < 0) //若已连接队列为空,则服务进程阻塞于此,否则取出队列头,内核生成一个全新的描述符,cliaddr是值-结果参数 { perror("accept"); exit(1); } if((childpid = fork()) < 0) //毫无悬念,创建子进程。注意,创建子进程后,对父进程现有套接字的引用都增1,表示子进程也可使用父进程拥有的套接字,此时注意close一些不是用的 { perror("fork"); exit(1); } else if(childpid == 0) /* child process */ fork返回两次,返回0的表示处于子进程 { if(close(listenfd) < 0) /* close listening socket */ /*关掉子进程对监听套接字的可使用权,监听套接字引用计数减1,直至计数为0,才回真正关闭套接字*/ { perror("child close"); exit(1); } str_echo(connfd); /* process the request */ //传递连接套接字给服务器程序的业务处理函数,从而进行通信操作 exit(0); /*此处别忘了哟,子进程用完了要退出,要不然站着进程资源不释放,又没用,你猜会怎么样 } if(close(connfd) < 0) /* parent close connected socket */ //父进程也要关闭连接套接字,(以为accept成功返回的时候,父进程也拥有了对连接套接字的使用权) { perror("parent close"); exit(1); } } }
TCP回射服务器程序:str_echo函数
/* str_echo.c */ #include <stdio.h> #include <stdlib.h> #include <errno.h> void str_echo(int sockfd) { ssize_t n; char buf[4096]; //创建一个应用层缓冲区,用于存放从套接字中读到的数据 again: while((n = read(sockfd,buf,4096)) > 0)//如果套接字队列没有数据可读,则read处于阻塞状态 writen(sockfd,n); if(n < 0 && errno == EINTR) //read被信号中断,此时重新读 goto again; else if(n < 0) //read发生错误,打印错误信息,并终止进程 { perror("read"); exit(1); } }
TCP回射客户程序:main函数
/* tcpcli01.c */ #include <stdio.h> #include <strings.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> int main(int argc,char **argv) { int sockfd; struct sockaddr_in servaddr; if(argc != 2) { printf("usage: tcpcli <IPaddress> "); exit(0); } if((sockfd = socket(AF_INET,0)) < 0) //不管是服务程序还是客户程序,想要网络通信,都得先创建通信用的网络套接字 { perror("socket"); //创建套接字失败的下场 exit(1); } bzero(&servaddr,sizeof(servaddr)); //同样的,初始化套接字地址结构为0 servaddr.sin_family = AF_INET; servaddr.sin_port = htons(9877); if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) < 0) { perror("inet_pton"); exit(1); } if(connect(sockfd,sizeof(servaddr)) < 0) //调用connect会发起三路握手的连接过程 { perror("connect"); exit(1); } str_cli(stdin,sockfd); /* do it all */ //调用客户端的业务处理程序,传入标准输入流(用于得到客户端的输入)和用于网络通信的套接字描述符 exit(0); }
TCP回射客户程序:str_cli函数
/* str_cli.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> void str_cli(FILE *fp,int sockfd) { char sendline[4096],recvline[4096]; //应用程序创建一个发送缓冲区和接收缓冲区 while(fgets(sendline,4096,fp) != NULL) // { writen(sockfd,sendline,strlen(sendline)); if(readline(sockfd,recvline,4096) == 0) { printf("str_cli: server terminated prematurely"); exit(0); } fputs(recvline,stdout); } }
当然,上述程序还有诸多问题,我们只是以一个简单示例程序来展示tcp客户端和服务通信
改进的服务器程序:(增加了清除僵尸进程的机制和accept被中断重新调用的机制)
#include "unp.h" int main(int argc,char **argv) { int listenfd,connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr,servaddr; void sig_chld(int); listenfd = Socket(AF_INET,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd,(SA *) &servaddr,sizeof(servaddr)); Listen(listenfd,LISTENQ); Signal(SIGCHLD,sig_chld); /* must call waitpid() */ for ( ; ; ) { clilen = sizeof(cliaddr); if ( (connfd = accept(listenfd,(SA *) &cliaddr,&clilen)) < 0) { if (errno == EINTR) continue; /* back to for() */ else err_sys("accept error"); } if ( (childpid = Fork()) == 0) { /* child process */ Close(listenfd); /* close listening socket */ str_echo(connfd); /* process the request */ exit(0); } Close(connfd); /* parent closes connected socket */ } }
void sig_chld(int signo) { pid_t pid; int stat; pid = wait(&stat); // printf("child %d terminated\n",pid); return; }