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 ...
随机推荐
- windows系统和centos双系统安装引导项修改
在CentOS下修改Linux引导文件: (1)找到win10的引导 1.首先我们点击第一个系统进入centos 2.运行终端,敲入命令su,为了获取管理员权限,然后终端提 ...
- 分布式服务框架Dubbo
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. 单一应用架构 当网站流量很小时,只需一个应用, ...
- Gradle 1.12用户指南翻译——第四十七章. Build Init 插件
本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- 项目部署、配置、查错常用到的Linux命令
一.常用命令 ls 显示文件或目录 -l 列出文件详细信息l(list) -a 列出当前目录下所有文件及目录,包括隐藏的a(all) ll 会列出该文件下的所有文件信息,包括隐藏的文件的文件详细信息, ...
- 对 /dev/shm 认识
一./dev/shm理论 /dev/shm/是linux下一个非常有用的目录,因为这个目录不在硬盘上,而是在内存里.因此在linux下,就不需要大费周折去建 ramdisk,直接使用/dev/shm/ ...
- android launcher 之踩到的坑
需求: 1. 用android系统launcher 隐藏主菜单 所有应用显示在桌面 即workspace上: 2.隐藏launcher上方默认的google search: 3.切换一套launche ...
- post插件
分享牛系列,分享牛专栏,分享牛.在项目开发中,http请求方式是最常见的了.怎么模拟http请求呢?方法有很多种,可以使用httpclient直接模拟请求,也可以使用火狐post插件方式,这个章节主要 ...
- Android支付——支付宝支付总结
摘要:分享牛系列.分享牛转载.第三方支付,java第三方支付.android第三方支付. 原文地址:http://blog.csdn.net/zwl5670/article/details/51219 ...
- ORACLE数据库管理常用查询语句
/*查看表空间的名称及大小*/ SELECT t.tablespace_name, round(SUM(bytes / (1024 * 1024)), 0) ts_size FROM dba_tabl ...
- Android更新UI的几种方法
在Android开发过程中,常需要更新界面的UI.比如网络请求操作.一些耗时操作都不能放在UI线程中运行的,需要放在子线程,而子线程又不能更新UI界面,这是我们需要引入一个Handler,消息处理机制 ...