Linux TCP server系列(2)-简单优化服务器和客户端程序
2011-09-15 16:48
671 查看
目标:
在上个server中考虑更多细节问题,完善server。
思路:
(1)服务器
父进程使用fork派生子进程后,如果子进程运行结束,那么该进程不会立刻被销毁,而会进入“僵尸状态”,仍然维护着自身的信息,这时候如果服务器父进程不加以处理,那么很快就会消耗完系统的内存空间,所以父进程需要监听子进程SIGCHLD信号,并做出处理以销毁残留信息,这里可以使用wait或者waitpid来实现。
我们在父进程调用listen之后,注册监听信号和信号处理函数signal(SIGCHLD, sig_child);
信号处理函数实现如下:
void sig_child(int signo) //父进程对子进程结束的信号处理
{
pid_t pid;
int stat;
while( (pid=waitpid(-1,&stat,WNOHANG))>0)
printf("child %d terminated\n",pid);
return;
}
这样一来我们就可以应付子进程退出后的残留信息问题。注意这里使用waitpid,其
中使用WNOHANG参数来防止父进程阻塞在wait上。
(2)客户端
客户端需要考虑下面几种边界情况,根据自身需求做出调整:
a)客户端连接成功后,某个时刻向服务器发送信息,接着再调用read从服务器接收信息,如果在发送信息之前,服务器子进程关闭了,那么它会发送FIN给客户端TCP,客户端TCP则以ACK响应,然后客户端向服务器发送消息时,服务器就会以RST响应,如果这时候客户端继续调用read,由于客户端TCP已经从服务器接收FIN,所以read会返回0,这时候客户根本不知道之前所发送的东西没有到达服务器,所以在read返回0时要做恰当提示和处理。
b)如果客户端不理会read的错误,继续发送信息给服务器,那么当一个进程向接收了RST的套接口进行写时,内核会给它发送一个SIGPIPE信号,该信号会终止进程,所以我们也需要自己捕获该信号并做处理。
c)在read之前,服务器主机崩溃,它没有回传FIN,这样read会阻塞很长时间才放弃连接,这也是需要考虑的。
考虑到客户端既需要从用户接收输入,又需要从服务器接收输入,其中一方在使用时会阻塞另一方,影响客户体验,所以这里使用select来做这两个描述符的监听。后文会将select使用到服务器上,提高服务器的性能。
select的主要用法:
1 在fd_set中设置被监听的描述符(输入描述符集,输出描述符集,异常输出描述符集)
2 调用select函数开始监听(可以自行配置超时时间)
3 如果监听到有数据流动,则使用FD_ISSET判断发生在哪个描述符,并做处理。
代码如下:
FD_ZERO(&rset);
for(;;)
{
FD_SET(fileno(fp),&rset);
FD_SET(sockfd,&rset);
maxfdp1=std::max( fileno(fp),sockfd) +1;
select(maxfdp1,&rset,NULL,NULL,NULL); //使用select使得客户端不需要阻塞在标准IO 或者 TCP read的其中一个上
if( FD_ISSET(sockfd,&rset))
{
if(read(sockfd,recvline,sizeof(recvline))==0)
//那边发来需要有换行符表示串尾,不然这边要求接受的字节数还不到,或者还没到尾部,所以没有输出
{
printf("read error\n");
//handle error;
return;
}
// printf("readed:%d,str:%d",cr,strlen(recvline));
// 如果tcp缓冲区内有多于预定义取的字节,则会自动调用read再取,直到结尾或者遇到换行符
fputs(recvline,stdout); //标准库函数fputs向标准IO上写一行
bzero(&recvline,strlen(recvline));
}
if( FD_ISSET(fileno(fp),&rset))
{
if(fgets(sendline,sizeof(sendline),fp)==NULL) //标准库函数fgets从标准IO上获取一行
//fgets会加上换行符,如果有
return;
write(sockfd,sendline,strlen(sendline)-1);
//减一去除换行符
bzero(&sendline,strlen(sendline));
}
}
实现:
client.cpp
分析:
服务器增加了对子进程退出时的信号处理,防止子进程数增多后带来的资源消耗,客户端使用select避免阻塞在标准IO和socket IO上。
在上个server中考虑更多细节问题,完善server。
思路:
(1)服务器
父进程使用fork派生子进程后,如果子进程运行结束,那么该进程不会立刻被销毁,而会进入“僵尸状态”,仍然维护着自身的信息,这时候如果服务器父进程不加以处理,那么很快就会消耗完系统的内存空间,所以父进程需要监听子进程SIGCHLD信号,并做出处理以销毁残留信息,这里可以使用wait或者waitpid来实现。
我们在父进程调用listen之后,注册监听信号和信号处理函数signal(SIGCHLD, sig_child);
信号处理函数实现如下:
void sig_child(int signo) //父进程对子进程结束的信号处理
{
pid_t pid;
int stat;
while( (pid=waitpid(-1,&stat,WNOHANG))>0)
printf("child %d terminated\n",pid);
return;
}
这样一来我们就可以应付子进程退出后的残留信息问题。注意这里使用waitpid,其
中使用WNOHANG参数来防止父进程阻塞在wait上。
(2)客户端
客户端需要考虑下面几种边界情况,根据自身需求做出调整:
a)客户端连接成功后,某个时刻向服务器发送信息,接着再调用read从服务器接收信息,如果在发送信息之前,服务器子进程关闭了,那么它会发送FIN给客户端TCP,客户端TCP则以ACK响应,然后客户端向服务器发送消息时,服务器就会以RST响应,如果这时候客户端继续调用read,由于客户端TCP已经从服务器接收FIN,所以read会返回0,这时候客户根本不知道之前所发送的东西没有到达服务器,所以在read返回0时要做恰当提示和处理。
b)如果客户端不理会read的错误,继续发送信息给服务器,那么当一个进程向接收了RST的套接口进行写时,内核会给它发送一个SIGPIPE信号,该信号会终止进程,所以我们也需要自己捕获该信号并做处理。
c)在read之前,服务器主机崩溃,它没有回传FIN,这样read会阻塞很长时间才放弃连接,这也是需要考虑的。
考虑到客户端既需要从用户接收输入,又需要从服务器接收输入,其中一方在使用时会阻塞另一方,影响客户体验,所以这里使用select来做这两个描述符的监听。后文会将select使用到服务器上,提高服务器的性能。
select的主要用法:
1 在fd_set中设置被监听的描述符(输入描述符集,输出描述符集,异常输出描述符集)
2 调用select函数开始监听(可以自行配置超时时间)
3 如果监听到有数据流动,则使用FD_ISSET判断发生在哪个描述符,并做处理。
代码如下:
FD_ZERO(&rset);
for(;;)
{
FD_SET(fileno(fp),&rset);
FD_SET(sockfd,&rset);
maxfdp1=std::max( fileno(fp),sockfd) +1;
select(maxfdp1,&rset,NULL,NULL,NULL); //使用select使得客户端不需要阻塞在标准IO 或者 TCP read的其中一个上
if( FD_ISSET(sockfd,&rset))
{
if(read(sockfd,recvline,sizeof(recvline))==0)
//那边发来需要有换行符表示串尾,不然这边要求接受的字节数还不到,或者还没到尾部,所以没有输出
{
printf("read error\n");
//handle error;
return;
}
// printf("readed:%d,str:%d",cr,strlen(recvline));
// 如果tcp缓冲区内有多于预定义取的字节,则会自动调用read再取,直到结尾或者遇到换行符
fputs(recvline,stdout); //标准库函数fputs向标准IO上写一行
bzero(&recvline,strlen(recvline));
}
if( FD_ISSET(fileno(fp),&rset))
{
if(fgets(sendline,sizeof(sendline),fp)==NULL) //标准库函数fgets从标准IO上获取一行
//fgets会加上换行符,如果有
return;
write(sockfd,sendline,strlen(sendline)-1);
//减一去除换行符
bzero(&sendline,strlen(sendline));
}
}
实现:
client.cpp
#include<sys/types.h> #include<stdlib.h> #include<stdio.h> #include<unistd.h> #include<sys/socket.h> #include<strings.h> #include<string.h> #include<arpa/inet.h> #include<errno.h> #include<stdio.h> #include<algorithm> #define SERVER_PORT 84 void str_cli(char *data,int sockfd); int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; //tcpcli <ipaddress> <data> if(argc!=3) return -1; sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd==-1) { printf("socket established error: %s\n",(char*)strerror(errno)); } bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(SERVER_PORT); inet_pton(AF_INET,argv[1],&servaddr.sin_addr); printf("client try to connect\n"); int conRes=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); if(conRes==-1) { printf("connect error: %s\n",strerror(errno)); } str_cli(argv[2],sockfd); exit(0); } void str_cli(char *data,int sockfd) { int n=0; char recv[512]; FILE* fp=stdin; int maxfdp1; fd_set rset; char sendline[512],recvline[512]; FD_ZERO(&rset); for(;;) { FD_SET(fileno(fp),&rset); FD_SET(sockfd,&rset); maxfdp1=std::max( fileno(fp),sockfd) +1; select(maxfdp1,&rset,NULL,NULL,NULL); //使用select使得客户端不需要注释在标准IO 或者 TCP read的其中一个上 if( FD_ISSET(sockfd,&rset)) { if(read(sockfd,recvline,sizeof(recvline))==0) //那边发来需要有换行符表示串尾,不然这边要求接受的字节数还不到,或者还没到尾部,所以没有输出 { printf("read error\n"); //handle error; return; } // printf("readed:%d,str:%d",cr,strlen(recvline)); // 如果tcp缓冲区内有多于预定义取的字节,则会自动调用read再取,直到结尾或者遇到换行符 fputs(recvline,stdout); //标准库函数fputs向标准IO上写一行 bzero(&recvline,strlen(recvline)); } if( FD_ISSET(fileno(fp),&rset)) { if(fgets(sendline,sizeof(sendline),fp)==NULL) //标准库函数fgets从标准IO上获取一行 //fgets会加上换行符,如果有 return; write(sockfd,sendline,strlen(sendline)-1); //减一去除换行符 bzero(&sendline,strlen(sendline)); } } }
分析:
服务器增加了对子进程退出时的信号处理,防止子进程数增多后带来的资源消耗,客户端使用select避免阻塞在标准IO和socket IO上。
相关文章推荐
- Linux TCP server系列(1)-简单TCP服务器+多进程处理客户请求
- Linux基于TCP/IP简单的客户端、服务器通信程序实例
- Linux TCP server系列(1)-简单TCP服务器+多进程处理客户请求
- TCP/TP编程 - 一个简单的Linux下C写的socket服务器客户端程序
- Linux基于TCP/IP简单的客户端、服务器通信程序实例
- 服务器开发之简单的TCP回射服务器(二):客户端程序
- Linux网络编程-简单的客户端和服务器通讯程序开发入门(2)
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- Linux网络编程 3 - 简单的Tcp服务器和客户端编程
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- linux 服务器/客户端 tcp通信的简单例子
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- 简单的网络通信程序,客户端发送hello,server 服务器反馈 ok UDP
- Linux下的C语言编程——简单实现tcp客户端和服务器
- linux下多线程tcp服务器、客户端程序
- 非阻塞式服务器和客户端程序(TCP)【简单的原理例子】
- linux下的简单文件服务器和客户端程序
- C++基于TCP/IP简单的客户端、服务器通信程序实例