《UNIX网络编程》TCP网络编程基础(2)
来源:互联网 发布:润桐数据 编辑:程序博客网 时间:2024/05/22 03:37
在上一篇中,我们编写了一个简单的TCP服务器/客户端程序,初步探讨了一些问题,本文将进一步优化该程序,使我们的程序更加健壮。
问题提出
我们的服务器阻塞于accept时,如果被信号中断了,将会返回一个错误,有些内核会自动重启被中断的系统调用,但为了可移植性,我们必须对慢系统调用返回EINTR有所准备。
服务器关闭后,客户端由于阻塞在read上,不能及时收到服务器关闭的通知.
close存在两点限制(代码注释中将详细说明)
改进后的版本
/* * tcp_server.c */#include <stdio.h>#include <stdlib.h>#include <strings.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <linux/in.h>#include <string.h>#include <signal.h>#include <errno.h>#define PORT 8888#define LISTENQ 2#define BUFSIZE 1024typedef void (*sighandler_t)(int);/*向信号signum注册一个void (*sighandler_t)(int)类型 *的函数,函数句柄为handler *使用typedef简化 */sighandler_t signal(int signum, sighandler_t handler);/*处理子进程的终止信号,防止僵死进程*/void sig_chld(int sign);int main (int argc, char **argv){ struct sockaddr_in cliaddr, servaddr; int listenfd, connfd; pid_t childpid; socklen_t clilen; signal(SIGCHLD, sig_chld); if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) //监听套接字 { printf("socket error\n"); return -1; } bzero(&servaddr, sizeof(servaddr)); //清零 servaddr.sin_family = AF_INET; /*地址和端口都要转成网络字节序*/ servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址 servaddr.sin_port = htons(PORT); /*绑定地址结构到套接字描述符*/ if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) { printf("bind error\n"); return -1; } /*监听, LISTENQ为监听队列的长度*/ if (listen(listenfd, LISTENQ) < 0) { printf("listen error\n"); return -1; } /*主循环*/ for( ; ; ) { clilen = sizeof(cliaddr); /*接收客户端连接*/ if ( (connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &clilen)) < 0 ) { /*accept属于慢系统调用,即它可能永远阻塞; *比如,如果没有客户连接到服务器上,那accept将不会返回。 *当阻塞于某个慢系统调用的进程捕捉了某个信号, *且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。 *在这里,我们重启被中断的系统调用。 */ if (errno == EINTR) continue; else { printf("accept error\n"); return -1; } } /*fork子进程来处理请求*/ if ( (childpid = fork()) == 0 ) { close(listenfd); //子进程关闭监听套接字 process_conn_server(connfd); exit(0); } close(connfd); //父进程关闭连接套接字 }}void sig_chld(int sign){ pid_t pid; int stat; /*注意信号是不排队的,使用wait的话,同时多个信号只处理一个. *使用waitpid来取得所有已终止子进程的状态; *指定WNOHANG,告诉waitpid在有未终止的子进程运行时不要阻塞; *不能在循环中调用wait,因为它会阻塞. */ while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) printf("child %d terminated\n", pid); return;}
/* * tcp_client.c */#include <stdio.h>#include <stdlib.h>#include <strings.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <linux/in.h>#include <string.h>#include <signal.h>#define PORT 8888#define BUFSIZE 1024typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);void sig_pipe(int sign);int main (int argc, char **argv){ struct sockaddr_in servaddr; int clifd; signal(SIGPIPE, sig_pipe); if (argc != 2) { printf("usage: client server_addr\n"); exit(1); } /*设置服务器地址*/ bzero(&servaddr, sizeof(servaddr)); //清零 servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址 servaddr.sin_port = htons(PORT); if ((clifd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("socket error\n"); return -1; } /* 将用户输入的字符串类型的IP地址转为整型, * p: presentation 表达格式 ASCII串 * n: numeric 数值格式 二进制 * 第二个参数为字符串指针, 第三个参数为指向结构struct in_addr的指针 */ inet_pton(AF_INET, argv[1], &servaddr.sin_addr); if (connect(clifd, (struct sockaddr*) &servaddr, sizeof(struct sockaddr)) < 0) { printf("connect error\n"); return -1; } process_conn_client(clifd); close(clifd);}/*当服务器一端关闭时,客户端如果继续向服务器发送数据,将会 *收到一个SIGPIPE信号,我们在这里捕捉该信号. */void sig_pipe(int sign){ printf("Catch a SIGPIPE signal\n");}
/* * tcp_process.c */#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#define max(a,b) a>b?a:bvoid process_conn_server(int fd){ ssize_t size = 0; char buffer[1024]; for (;;) { size = read(fd, buffer, 1024); if (size == 0) return ; sprintf(buffer, "%d bytes altogether\n", size); write(fd, buffer, strlen(buffer)+1); }}/*上一版的问题:服务器关闭后,客户端由于阻塞在read上, *不能及时收到服务器关闭的通知. *本版本改为阻塞于select调用,等待标准输入可读或者套接字可读. *套接字上的三个处理条件: *1.对端发送数据,套接字可读,read返回大于0的值; *2.对端发送FIN(对端进程终止),套接字可读,read返回0(EOF); *3.对端发送RST(对端主机崩溃并重启),套接字可读,read返回-1, * errno中含有确切的错误码. * *本版本使用shutdown主要原因如下: *1.close在描述符引用计数为0时才关闭套接字, * 而shutdown立即激发TCP的正常连接终止序列. *2.close终止读写两个方向的数据传送。有时我们需要告知对端 * 我们已经完成了数据发送,即使对端仍有数据要发送给我们. * 这时可以使用shutdown实现半关闭. */void process_conn_client(int fd){ ssize_t size = 0; char buffer[1024]; int maxfdp, stdineof; fd_set rset; stdineof = 0; //用来标识标准输入的结束 FD_ZERO(&rset); for (;;) { if (stdineof == 0) //标识为0才select标准输入的可读性 FD_SET(0, &rset); FD_SET(fd, &rset); //套接字 maxfdp = max(0, fd) + 1; select(maxfdp, &rset, NULL, NULL, NULL); if (FD_ISSET(fd, &rset)) //套接字可读 { if ( (size = read(fd, buffer, 1024)) == 0 ) //从服务器读数据 { if (stdineof == 1) return; //正常结束 else { printf("server terminated prematurely\n"); return; } } write(1, buffer, size); //输出到标准输出 } if (FD_ISSET(0, &rset)) //标准输入可读 { /*从标准输入读数据*/ if ( (size = read(0, buffer, 1024)) == 0 ) { //读到EOF, 调用shutdown来发送FIN stdineof = 1; shutdown(fd, SHUT_WR); FD_CLR(0, &rset); continue; //继续循环,此时可以接收数据,不可发送数据 } write(fd, buffer, size); //写数据给服务器 } }}
一些其他需要考虑的问题
服务器主机崩溃,此时如果客户端向主机发送数据,将得不到响应,在不断重传到最后不得不放弃之后才得到错误消息(比如9分钟)。
解决方案:设置超时, or 使用SO_KEEPALIVE套接字选项。accept返回前连接中止。假设已经完成三次握手,客户TCP却发送了一个RST,在服务器端看了,就在该连接已由TCP排队,等着服务器进程调用accept时RST到达。稍后,服务器进程调用accept。
服务器主机崩溃后重启。
0 0
- 《UNIX网络编程》TCP网络编程基础(2)
- UNIX网络编程:TCP
- 《UNIX网络编程》TCP网络编程基础(1)
- 【Unix 网络编程】TCP Socket 编程基础(0)
- unix网络编程之tcp
- Unix c 网络编程 TCP
- 《UNIX网络编程》UDP网络编程基础
- Unix网络编程基础概念
- Unix 网络编程 基础接口
- unix网络编程基础接口
- UNIX网络编程:基础套接字编程
- 网络编程TCP基础一
- 网络编程基础--TCP/IP
- java基础----网络编程 tcp
- UNIX网络编程--TCP网络编程中的listen
- 唯快不破:UNIX网络编程--TCP网络编程中的listen
- unix网络编程之一TCP/UDP
- UNIX网络编程----TCP客户端和服务器端
- (转) sqlserver,mysql,db2,oracle中判断字段的值不为空
- Unity3D 开发优秀技术资源汇总
- windows下用sendmail配置php的mail函数
- 数据挖掘浅尝
- protocol类型限制
- 《UNIX网络编程》TCP网络编程基础(2)
- pig JOIN 的replicated后标
- Objective-c语言_计算机网络(UI)同步get,post和异步get,post
- FireWorks based on NeHe Particle System
- 微信公众号开发之创建自定义菜单
- protocol buffer 字段过滤
- 转换坐标系
- freemarker 为空会报错,该怎么解决?
- MariaDB 通过命令行的方式导出指定数据库和还原指定数据库