本文共 5503 字,大约阅读时间需要 18 分钟。
以下程序摘自UNIX网络编程,我会对其加上一些注解,方便阅读
TCP回射服务器程序:main函数
/*怎么编译运行可以查看我的另一篇博文:
/* tcpserv01.c */ #include#include #include #include #include #include #include #include intmain(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后内核会维护两个队列,具体参见 { 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#include #include voidstr_echo(int sockfd){ ssize_t n; char buf[4096]; //创建一个应用层缓冲区,用于存放从套接字中读到的数据again: while((n = read(sockfd, buf, 4096)) > 0)//如果套接字队列没有数据可读,则read处于阻塞状态 writen(sockfd, buf, n); if(n < 0 && errno == EINTR) //read被信号中断,此时重新读 goto again; else if(n < 0) //read发生错误,打印错误信息,并终止进程 { perror("read"); exit(1); } } TCP回射客户程序:main函数
/* tcpcli01.c */#include#include #include #include #include #include #include #include #include intmain(int argc, char **argv){ int sockfd; struct sockaddr_in servaddr; if(argc != 2) { printf("usage: tcpcli "); exit(0); } if((sockfd = socket(AF_INET, SOCK_STREAM, 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, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) //调用connect会发起三路握手的连接过程 { perror("connect"); exit(1); } str_cli(stdin, sockfd); /* do it all */ //调用客户端的业务处理程序,传入标准输入流(用于得到客户端的输入)和用于网络通信的套接字描述符 exit(0);} TCP回射客户程序:str_cli函数
/* str_cli.c */#include#include #include voidstr_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, SOCK_STREAM, 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; }