Socket编程实践(6) --TCP服务端注意事项
僵尸进程处理
1)通过忽略SIGCHLD信号,避免僵尸进程
在server端代码中添加
signal(SIGCHLD, SIG_IGN);
2)通过wait/waitpid方法,解决僵尸进程
signal(SIGCHLD,onSignalCatch); void onSignalCatch(int signalNumber) { wait(NULL); }
3) 如果多个客户端同时关闭, 问题描述如下面两幅图所示:
/** client端实现的测试代码**/ int main() { int sockfd[50]; for (int i = 0; i < 50; ++i) { if ((sockfd[i] = socket(AF_INET, SOCK_STREAM, 0)) == -1) err_exit("socket error"); struct sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8001); serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sockfd[i], (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1) err_exit("connect error"); } sleep(20); }
在客户运行过程中按下Ctrl+C,则可以看到在server端启动50个子进程,并且所有的客户端全部一起断开的情况下,产生的僵尸进程数是惊人的(此时也证明了SIGCHLD信号是不可靠的)!
解决方法-将server端信号捕捉函数改造如下:
void sigHandler(int signo) { while (waitpid(-1, NULL, WNOHANG) > 0) ; }
waitpid返回值解释:
on success, returns the process ID of the child whose state has changed(返回已经结束运行
的子进程的PID); if WNOHANG was specified and one or more child(ren) specified by pid exist,
but have not yet changed state, then 0 is returned(如果此时尚有好多被pid参数标识的子进程存在, 并
且没有结束的迹象, 返回0). On error, -1 is returned.
地址查询API
#include <sys/socket.h> int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //获取本地addr结构 int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //获取对方addr结构 int gethostname(char *name, size_t len); int sethostname(const char *name, size_t len); #include <netdb.h> extern int h_errno; struct hostent *gethostbyname(const char *name); #include <sys/socket.h> /* for AF_INET */ struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type); struct hostent *gethostent(void);
//hostent结构体 struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ } #define h_addr h_addr_list[0] /* for backward compatibility */
/**获取本机IP列表**/ int gethostip(char *ip) { struct hostent *hp = gethostent(); if (hp == NULL) return -1; strcpy(ip, inet_ntoa(*(struct in_addr*)hp->h_addr)); return 0; } int main() { char host[128] = {0}; if (gethostname(host, sizeof(host)) == -1) err_exit("gethostname error"); cout << "host-name: " << host << endl; struct hostent *hp = gethostbyname(host); if (hp == NULL) err_exit("gethostbyname error"); cout << "ip list: " << endl; for (int i = 0; hp->h_addr_list[i] != NULL; ++i) { cout << '\t' << inet_ntoa(*(struct in_addr*)hp->h_addr_list[i]) << endl; } char ip[33] = {0}; gethostip(ip); cout << "local-ip: " << ip << endl; }
TCP协议的11种状态
1.如下图(客户端与服务器都在本机:双方(server的子进程,与client)链接已经建立(ESTABLISHED),等待通信)
2.最先close的一端,会进入TIME_WAIT状态; 而被动关闭的一端可以进入CLOSE_WAIT状态 (下图,server端首先关闭)
3.TIME_WAIT 时间是2MSL(报文的最长存活周期的2倍)
原因:(ACK y+1)如果发送失败可以重发, 因此如果server端不设置地址重复利用的话, 服务器在短时间内就无法重启;
服务器端处于closed状态,不等于客户端也处于closed状态。
(下图, client先close, client出现TIME_WAIT状态)
4.TCP/IP协议的第1种状态:图上只包含10种状态,还有一种CLOSING状态
产生CLOSING状态的原因:
Server端与Client端同时关闭(同时调用close,此时两端同时给对端发送FIN包),将产生closing状态,最后双方都进入TIME_WAIT状态(如下图)。
SIGPIPE信号
往一个已经接收FIN的套接中写是允许的,接收到FIN仅仅代表对方不再发送数据;但是在收到RST段之后,如果还继续写,调用write就会产生SIGPIPE信号,对于这个信号的处理我们通常忽略即可。
signal(SIGPIPE, SIG_IGN);
/** 测试: 在Client发送每条信息都发送两次 当Server端关闭之后Server端会发送一个FIN分节给Client端, 第一次消息发送之后, Server端会发送一个RST分节给Client端, 第二次消息发送(调用write)时, 会产生SIGPIPE信号; 注意: Client端测试代码使用的是下节将要介绍的Socket库 **/ void sigHandler(int signo) { if (SIGPIPE == signo) { cout << "receive SIGPIPE = " << SIGPIPE << endl; exit(EXIT_FAILURE); } } int main() { signal(SIGPIPE, sigHandler); TCPClient client(8001, "127.0.0.1"); try { std::string msg; while (getline(cin, msg)) { client.send(msg); client.send(msg); //第二次发送 msg.clear(); client.receive(msg); client.receive(msg); cout << msg << endl; msg.clear(); } } catch (const SocketException &e) { cerr << e.what() << endl; } }
close与shutdown的区别
#include <unistd.h> int close(int fd); #include <sys/socket.h> int shutdown(int sockfd, int how);
shutdown的how参数 |
|
SHUT_RD |
关闭读端 |
SHUT_WR |
关闭写端 |
SHUT_RDWR |
读写均关闭 |
1.close终止了数据传送的两个方向;
而shutdown可以有选择的终止某个方向的数据传送或者终止数据传送的两个方向。
2.shutdown how=SHUT_WR(关闭写端)可以保证对等方接收到一个EOF字符(FIN段),而不管是否有其他进程已经打开了套接字(shutdown并没采用引用计数)。
而close需要等待套接字引用计数减为0时才发送FIN段。也就是说直到所有的进程都关闭了该套接字。
示例分析:
客户端向服务器按照顺序发送:FIN E D C B A, 如果FIN是当client尚未接收到ABCDE之前就调用close发送的, 那么client端将永远接收不到ABCDE了, 而通过shutdown函数, 则可以有选择的只关闭client的发送端而不关闭接收端, 则client端还可以接收到ABCDE的信息;
/**测试: 实现与上面类似的代码(使用close/shutdown)两种方式实现 **/
完整源代码请参照:
http://download.csdn.net/detail/hanqing280441589/8486517
注意: 最好读者需要有select的基础, 没有select基础的读者可以参考我的博客<Socket编程实践(8)>相关部分
Socket编程实践(6) --TCP服务端注意事项的更多相关文章
- socket编程,简单多线程服务端测试程序
socket编程,简单多线程服务端测试程序 前些天重温了MSDN关于socket编程的WSAStartup.WSACleanup.socket.closesocket.bind.listen.acce ...
- Socket编程实践(5) --TCP粘包问题与解决
TCP粘包问题 由于TCP协议是基于字节流且无边界的传输协议, 因此很有可能产生粘包问题, 问题描述如下 对于Host A 发送的M1与M2两个各10K的数据块, Host B 接收数据的方式不确定, ...
- Socket编程--TCP服务端注意事项
僵尸进程处理 僵尸进程和孤儿进程: 基本概念:我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程.子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预 ...
- Socket编程实践(1) --TCP/IP简述
ISO的OSI OSI(open system interconnection)开放系统互联模型是由ISO国际标准化组织定义的网络分层模型,共七层, 从下往上为: OSI七层参考模型 物理层(Phys ...
- Java网络编程(TCP服务端)
/* * TCP服务端: * 1.创建服务端socket服务,并监听一个端口 * 2.服务端为了给客户端提供服务,获取客户端的内容,可以通过accept方法获取连接过来的客户端对象 * 3.可以通过获 ...
- tcp服务端socket
import socket if __name__ == '__main__': # 创建tcp服务端socket tcp_server_socket = socket.socket(socket.A ...
- python编程系列---tcp服务端的简单实现
流程如下: """tcp服务端创建流程1. 创建服务端的tcp socket : server_socket 用于监听客户端的请求2. 绑定端口3. server_soc ...
- python网络编程-TCP服务端的开发
#TCP服务端开发 2 #方法说明 3 """ 4 bind(host,port)表示绑定端口号,host是ip地址,ip地址一般不进 行绑定,表示本机的任何一个ip地址 ...
- [javaSE] 网络编程(TCP服务端客户端互访阻塞)
客户端给服务端发送数据,服务端收到数据后,给客户端反馈数据 客户端: 获取Socket对象,new出来,构造参数:String的ip地址,int的端口号 调用Socket对象的getOutputStr ...
随机推荐
- Python小代码_11_生成小于 n 的裴波那契数列
def fib(n): a, b = 1, 1 while a < n: print(a, end=' ') a, b = b, a + b fib(100000) #输出结果 #1 1 2 3 ...
- 未能加载 global.asax的类的解决方案
“/suitecallback”应用程序中的服务器错误. 分析器错误 说明: 在分析向此请求提供服务所需资源时出错.请检查下列特定分析错误详细信息并适当地修改源文件. 分析器错误消息: 未能加载类型“ ...
- SQL部分常用指令整理
dual 伪表 用来测试函数和表达式 1.查询EMP表中所有人的信息,结果格式样例为"某人的月薪是1000$" SELECT ENAME||'的月薪是'||SAL||'$' FRO ...
- textarea不能使用maxlength
知道文本框有个maxlength属性,有次开发项目中使用了textarea标签,没去看文档,直接加了maxlength属性,且有效果没有报错,喜滋滋的用了,结果没两天就测试出了bug 问题描述:文本域 ...
- Luogu P2756 [网络流24题]飞行员配对方案问题_二分图匹配
二分图模板题 我用的是匈牙利 其实最大流也可以做 #include<iostream> #include<cstdio> #include<cstdlib> #in ...
- 异常处理&RandomAccessFile&节奏感
异常处理 异常处理方面的知识,下面是学习中记的笔记: try尝试捕获异常 catch对捕获的异常进行处理 多个catch要注意的问题: 一.顺序问题,先小后大,也就是先子类后父类.因为当异常出现的时候 ...
- PHP 字符串变量
PHP 字符串变量 字符串变量用于存储并处理文本. PHP 中的字符串变量 字符串变量用于包含有字符的值. 在创建字符串之后,我们就可以对它进行操作了.您可以直接在函数中使用字符串,或者把它存储在变量 ...
- docker环境 快速使用elasticsearch-head插件
docker环境 快速使用elasticsearch-head插件 #elasticsearch配置 #进入elk容器 docker exec -it elk /bin/bash #head插件访问配 ...
- MYSQL 索引类型、什么情况下用不上索引、什么情况下不推荐使用索引
mysql explain的使用: http://blog.csdn.net/kaka1121/article/details/53394426 索引类型 在数据库表中,对字段建立索引可以大大提高查询 ...
- javap反编译命令详解&Eclipse中配置javap命令
javap命令所有参数如下图所示: javap 命令用于解析类文件.其输出取决于所用的选项.若没有使用选项,javap 将输出传递给它的类的 public 域及方法.javap 将其输出到标准输出设备 ...