tcp连接标准代码(具有实时检测客户端网络故障功能)
做出个东西容易,做好难。实时检测tcp连接客户端是否在线,是在我设计专门控制硬件的嵌入式web服务器(上边跑webdcs)这个背景下遇到的问题。
看似简单,查了查,还真没有做的很好的。
翻资料,查论坛,问高人,袖探现有实时游戏软件的包,
前前后后试了N中方案,今天,终于弄出来个比较满意的方法。
基本情况是:
web服务器端(或者是tcp服务器端)可以在3s内检测出客户端掉线(断电,路由器断电,没有RST包,没有FIN包下)
正常情况(FIN包,RST包)立即检测出。
以下先贴代码。再讲讲过程。有需要的朋友拿去。 /******************* tcpServer.c *********************/
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#define PORT 3000
#define BUFSIZE 1024
int tcp_state(int tcp_fd)
{
struct tcp_info info;
int optlen = sizeof(struct tcp_info);
if (getsockopt (tcp_fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&optlen) < 0) {
printf ("getsockopt() TCP_INFO error\n"); exit (0);
}
printf ("%d\n",info.tcpi_state);
if (info.tcpi_state == TCP_ESTABLISHED) return 0; /* ESTABLISHED */
else return -1;
}
int main (int argc, char **argv)
{
int listen_fd;
if ((listen_fd = socket (PF_INET, SOCK_STREAM, 0)) < 0) { printf ("socket() error\n"); exit (0); }
struct sockaddr_in acceptAddr; bzero(&acceptAddr, sizeof(acceptAddr));
acceptAddr.sin_family = PF_INET;
acceptAddr.sin_addr.s_addr = htonl (INADDR_ANY);
acceptAddr.sin_port = htons (PORT);
if (bind(listen_fd, (struct sockaddr *)&acceptAddr, sizeof(acceptAddr)) != 0) { printf("bind() error\n"); exit(0); }
if (listen (listen_fd, 1) != 0) { printf("listen() error\n"); exit(0); }
signal(SIGPIPE, SIG_IGN);
while (1) {
struct sockaddr_in clientAddr; bzero(&clientAddr, sizeof(clientAddr));
int clientSockfd, len;
len = sizeof (clientAddr);
clientSockfd = accept (listen_fd, (struct sockaddr *)&clientAddr, (socklen_t *)&len); /* accept() creates a new connected socket file descriptor. */
/* setup clientSockfd */
if (fcntl(clientSockfd, F_SETFL, O_NONBLOCK) != 0) { printf ("error: fcntl(). exit\n"); exit (0); } /* set clientSockfd nonblocking */
int keepAlive = 1;
if (setsockopt (clientSockfd,SOL_SOCKET,SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive)) == -1) { printf("setsockopt SO_KEEPALIVE error!\n"); exit(0); }
int keepIdle = 1; /* The time (in seconds) the connection needs to remain idle before TCP starts sending keepalive probes. */
if (setsockopt (clientSockfd,SOL_TCP,TCP_KEEPIDLE,(void *)&keepIdle,sizeof(keepIdle)) == -1) { printf("setsockopt TCP_KEEPIDLE error!\n"); exit(0); }
int keepInterval = 1; /* The time (in seconds) between individual keepalive probes. */
if (setsockopt (clientSockfd,SOL_TCP,TCP_KEEPINTVL,(void *)&keepInterval,sizeof(keepInterval)) == -1) { printf("setsockopt TCP_KEEPINTVL error!\n"); exit(0); }
int keepCount = 2; /* The maximum number of keepalive probes TCP should send before dropping the connection. */
if (setsockopt (clientSockfd,SOL_TCP,TCP_KEEPCNT,(void *)&keepCount,sizeof(keepCount)) == -1) { printf("setsockopt TCP_KEEPCNT error!\n"); exit(0); }
/* setup clientSockfd end */
char buf; bzero (buf, BUFSIZE+1);
sleep(5);
if ((len = read (clientSockfd, buf, BUFSIZE)) <= 0) {
if((errno == EAGAIN) || (errno == EWOULDBLOCK)) { /* Non-blocking I/O has been selected using O_NONBLOCK and no data was immediately available for reading. */
} else { }
shutdown(clientSockfd, SHUT_RDWR); close (clientSockfd); /* close file descriptor */
continue;
}
printf ("read data from client %s:\n%s\n", inet_ntoa(clientAddr.sin_addr), buf);
int i;
for (i = 0; i < len; i++) {
printf ("%2.2x ", buf);
if((i+1)%16 == 0) { printf ("\n"); }
}
printf ("\n");
sleep(5);
char *data = "123 456\n";
if (write (clientSockfd, data, strlen(data)) == -1) {
if((errno == EAGAIN) || (errno == EWOULDBLOCK)) { /* Non-blocking I/O has been selected using O_NONBLOCK and the write would block. */
} else { }
shutdown(clientSockfd, SHUT_RDWR); close (clientSockfd); /* close file descriptor */
continue;
}
printf ("send data to client %s:\n%s\n", inet_ntoa(clientAddr.sin_addr), data);
while (1) {
if (tcp_state (clientSockfd) != 0) break;
sleep (1);
}
shutdown(clientSockfd, SHUT_RDWR); close (clientSockfd); /* close file descriptor */
}
return 0;
}
/* convert address from presentation format to network format: inet_pton(PF_INET, "127.0.0.1", &(sa.sin_addr)); */
/*************** end of tcpServer.c ******************/ 以上是个演示代码。熟悉的朋友可能看出那些是关键来。
下边先讲讲基本的socket编程
基本socket编程代码:
int listen_fd;
if ((listen_fd = socket (PF_INET, SOCK_STREAM, 0)) < 0) { printf ("socket() error\n"); exit (0); }
struct sockaddr_in acceptAddr; bzero(&acceptAddr, sizeof(acceptAddr));
acceptAddr.sin_family = PF_INET;
acceptAddr.sin_addr.s_addr = htonl (INADDR_ANY);
acceptAddr.sin_port = htons (PORT);
if (bind(listen_fd, (struct sockaddr *)&acceptAddr, sizeof(acceptAddr)) != 0) { printf("bind() error\n"); exit(0); }
if (listen (listen_fd, 1) != 0) { printf("listen() error\n"); exit(0); }
struct sockaddr_in clientAddr; bzero(&clientAddr, sizeof(clientAddr));
int clientSockfd, len;
len = sizeof (clientAddr);
clientSockfd = accept (listen_fd, (struct sockaddr *)&clientAddr, (socklen_t *)&len); /* accept() creates a new connected socket file descriptor. */
read ()
write () 基本socket编程就是
创建socket;
bind端口
listen,
accept出针对单一客户端scoket
以上基本结构可以适应90%以上的应用。我们所见到的绝大部分网络程序是用基本的socket写成的。
(QQ, 魔兽等使用了心跳方案,这个以下令提) 那么这个基本应用的缺陷在哪呢?
结合需求说明:
我所设计的这个WEB服务器是专门跑在嵌入式系统上的,它的特点有以下几条:
1, 系统资源少,能高效就高效,没事别整一堆后台cgi,然后没事别调用很多ajax连接。能优化就优化,js能做的尽量让客户端做,所谓:胖客户端方案。
2, 控制硬件。为什么要用嵌入式的web,因为我们要控制硬件,又不想人去,而且我们需要大量布置(比如智能家居上)。控制硬件这一条决定了嵌入式web的安全性较高,而硬件的执行速度较慢又派生出网络协同的问题。 所以我已一个一般的嵌入式web服务器boa为原形进行了从写,专门适应嵌入式web服务器这种需要。
为什么选用boa?
实际上我参考了很多web服务器的代码和构架,嵌入式应用上,以多线程(进程)为构架的主流服务器(apache类)彻底歇菜了(想想跑这些玩意的大站都用了什么硬件配置就知道了),嵌入式上也来多进程???
何况根据具体需要,控制硬件的web授权很严格,我就允许某一公网IP端内10个人同时登录(关键是考虑协同性啊,你咋通知10个人这其中某一个人的操作,魔兽争霸才支持10个人)
所以多进程构架走不同。
select构架,boa和thttpd都很好,我参考了boa,对其进行了精简,添加了http401认证和内置的人数限制,
计划添加内置的聊天室。
那么这个时候我们就遇到了第一个问题,网络协作,至少要知道用户上线下线,那么现有的tcp socket只能检测正常掉线,不能检测网络中断。
就是我拔线了web服务器照样认为我在线,只是没有发送http请求而已。 首先我查了查现有的解决方案。
中文讨论大部分集中在read write的表现上,可经过我严格的测试,在客户端拔网线这种情况下,read write均表现正常的返回值,tcp协议层表现为不断的从发。
查到qq等软件使用的“心跳”即每隔一定时间发送一个包,要求客户端回显,这种方法可行,但不适用于web这种符合标准无法对客户端定制的情况。(http没有心跳包吧,查了RFC×××么有看到。 http的keepalive是一直连接的意思) 首先我根据现有的讨论情况,编写了一个检查errno的read write + select构架,很复杂。但是对于拔线这种情况同样束手无策,实测中,10s内收不到ack包是不会被判定为socket出错的,10s啊,要知道一台2g CPU的pc没s发报量在10w左右。。。。。10s这个数无论是对PC还是对人来讲都太长了。
此方案放弃。 然后我实际量了一些网络协同软件的掉线反应时间,魔兽争霸是最快的,比qq快(看来游戏的要求还真高)大概在2-3s左右,就是说:从某一客户端拔线到其他客户端知道这个客户下线只需要2-3s。
我抓取了它的部分网络包,发现也是心跳的实现方案。
于是我又在http协议上做了点**,隔一段时间写一个 “\r\n\r\n“ 浏览器收到这个信息不会动作,但如果出现问题了write回返回breaken pipe。
发现这个时间太长。找遍http协议,没有找到一个需要浏览器回应服务器的指令。。。。也就是说:http似乎没有可以当成心跳包的。。。。。 这个时候我真的感觉山穷水尽了,实在也没别的方法。
加上被要求改进界面,就暂时先把它放了放。
几天后, 用各种英文说法描述这个问题进行搜索,得到一个信息,很多系统tcp实现了keepalive功能,即tcp的心跳。可以直接启用。
好,经多方查找, 终于找到:
int keepAlive = 1;
if (setsockopt (clientSockfd,SOL_SOCKET,SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive)) == -1) { printf("setsockopt SO_KEEPALIVE error!\n"); exit(0); }
int keepIdle = 1; /* The time (in seconds) the connection needs to remain idle before TCP starts sending keepalive probes. */
if (setsockopt (clientSockfd,SOL_TCP,TCP_KEEPIDLE,(void *)&keepIdle,sizeof(keepIdle)) == -1) { printf("setsockopt TCP_KEEPIDLE error!\n"); exit(0); }
int keepInterval = 1; /* The time (in seconds) between individual keepalive probes. */
if (setsockopt (clientSockfd,SOL_TCP,TCP_KEEPINTVL,(void *)&keepInterval,sizeof(keepInterval)) == -1) { printf("setsockopt TCP_KEEPINTVL error!\n"); exit(0); }
int keepCount = 2; /* The maximum number of keepalive probes TCP should send before dropping the connection. */
if (setsockopt (clientSockfd,SOL_TCP,TCP_KEEPCNT,(void *)&keepCount,sizeof(keepCount)) == -1) { printf("setsockopt TCP_KEEPCNT error!\n"); exit(0); }
设置启用tcp keepalive功能。
经实测有效,有tcp心跳包,超时自动挂断。3s内反应。 然后就是一点补救:
直接查看tcp状态机状态(类似netstat 指令的效果)
int tcp_state(int tcp_fd)
{
struct tcp_info info;
int optlen = sizeof(struct tcp_info);
if (getsockopt (tcp_fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&optlen) < 0) {
printf ("getsockopt() TCP_INFO error\n"); exit (0);
}
printf ("%d\n",info.tcpi_state);
if (info.tcpi_state == TCP_ESTABLISHED) return 0; /* ESTABLISHED */
else return -1;
}
大功告成 马克
. 不知道谁第一个把socket翻译成套接字的,太离谱了。:L 外行人纳闷一下,为什么非要用web呢?
你敲www.sinanjj.com的时候,它发一个程序给你,exe,java……反正短小精悍能马上执行的就可以
程序执行后就可以用自定义的协议通信了,怎么做都可以了……
我见过的集中监控软件都没有用web的。 视频监视(不是监控)倒是挺多用web的。 去“爱问”看看吧 应该有 挖个坟,拿去调一下试试。 这个已经淘汰了。
我有空开源个新的。
有需要的可以马上联系我。 想要最新的!怎么联系! 挖个坑,留的以后我跳下去
页:
[1]
2