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 ...
随机推荐
- Angular5学习笔记 http请求
在anular4更新到angular5后,有些模块也发生了有些变化,例如http模块. 首先在app.module.ts里面引入HttpClientModule import { HttpClient ...
- JAVA 面试基础
经典的Java基础面试题________________________________________________________________________________________ ...
- truncated、delete和drop的异同点
相同点 truncate和不带where子句的delete, 以及drop都会删除表内的数据. 不同点: 1.truncate和 delete只删除数据不删除表的结构(定义) drop语句将删除表的结 ...
- JavaScript基础知识从浅入深理解(一)
JavaScript的简介 javascript是一门动态弱类型的解释型编程语言,增强页面动态效果,实现页面与用户之间的实时动态的交互. javascript是由三部分组成:ECMAScript.DO ...
- 剑指架构师系列-spring boot的logback日志记录
Spring Boot集成了Logback日志系统. Logback的核心对象主要有3个:Logger.Appender.Layout 1.Logback Logger:日志的记录器 主要用于存放日志 ...
- Node.js 调试器
稳定性: 3 - 稳定 V8 提供了强大的调试工具,可以通过 TCP protocol 从外部访问.Node 内置这个调试工具客户端.要使用这个调试器,以debug参数启动 Node,出现提示: % ...
- ngx.ctx
https://github.com/openresty/lua-nginx-module#ngxctx 要点 生命周期和请求一致 每个请求的ngx.ctx是相互独立的,包括ngx.location. ...
- SpringBatch配置数据库
Spring Batch提供一个任务的仓库实现将任务元数据保存在数据库中,以便监控你的批量处理进程以及其结果. Spring Batch 数据库引擎支持的有:DB2,Derby, H2, HSQLDB ...
- 序列化战争:主流序列化框架Benchmark
序列化战争:主流序列化框架Benchmark GitHub上有这样一个关于序列化的Benchmark,被好多文章引用.但这个项目考虑到完整性,代码有些复杂.为了个人学习,自己实现了个简单的Benchm ...
- Python 3 re模块3个括号相关的语法
(?aiLmsux) (One or more letters from the set 'a', 'i', 'L', 'm', 's', 'u', 'x'.) The group matches t ...