打印
[嵌入式linux]

tcp连接标准代码(具有实时检测客户端网络故障功能)

[复制链接]
7305|20
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
sinanjj|  楼主 | 2009-8-6 18:41 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
做出个东西容易,做好难。

实时检测tcp连接客户端是否在线,是在我设计专门控制硬件的嵌入式web服务器(上边跑webdcs)这个背景下遇到的问题。

看似简单,查了查,还真没有做的很好的。

翻资料,查论坛,问高人,袖探现有实时游戏软件的包,

前前后后试了N中方案,今天,终于弄出来个比较满意的方法。

基本情况是:

web服务器端(或者是tcp服务器端)可以在3s内检测出客户端掉线(断电,路由器断电,没有RST包,没有FIN包下)
正常情况(FIN包,RST包)立即检测出。

以下先贴代码。再讲讲过程。有需要的朋友拿去。

相关帖子

沙发
sinanjj|  楼主 | 2009-8-6 18:43 | 只看该作者
/******************* 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[BUFSIZE+1]; 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[i]);
                        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 ******************/

使用特权

评论回复
板凳
sinanjj|  楼主 | 2009-8-6 18:47 | 只看该作者
以上是个演示代码。熟悉的朋友可能看出那些是关键来。

下边先讲讲基本的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 ()

使用特权

评论回复
地板
sinanjj|  楼主 | 2009-8-6 18:50 | 只看该作者
基本socket编程就是
创建socket;
bind端口
listen,
accept出针对单一客户端scoket

以上基本结构可以适应90%以上的应用。我们所见到的绝大部分网络程序是用基本的socket写成的。

(QQ, 魔兽等使用了心跳方案,这个以下令提)

使用特权

评论回复
5
sinanjj|  楼主 | 2009-8-6 18:57 | 只看该作者
那么这个基本应用的缺陷在哪呢?

结合需求说明:

我所设计的这个WEB服务器是专门跑在嵌入式系统上的,它的特点有以下几条:
1, 系统资源少,能高效就高效,没事别整一堆后台cgi,然后没事别调用很多ajax连接。能优化就优化,js能做的尽量让客户端做,所谓:胖客户端方案。
2, 控制硬件。为什么要用嵌入式的web,因为我们要控制硬件,又不想人去,而且我们需要大量布置(比如智能家居上)。控制硬件这一条决定了嵌入式web的安全性较高,而硬件的执行速度较慢又派生出网络协同的问题。

使用特权

评论回复
6
sinanjj|  楼主 | 2009-8-6 19:07 | 只看该作者
所以我已一个一般的嵌入式web服务器boa为原形进行了从写,专门适应嵌入式web服务器这种需要。

为什么选用boa?
实际上我参考了很多web服务器的代码和构架,嵌入式应用上,以多线程(进程)为构架的主流服务器(apache类)彻底歇菜了(想想跑这些玩意的大站都用了什么硬件配置就知道了),嵌入式上也来多进程???

何况根据具体需要,控制硬件的web授权很严格,我就允许某一公网IP端内10个人同时登录(关键是考虑协同性啊,你咋通知10个人这其中某一个人的操作,魔兽争霸才支持10个人)

所以多进程构架走不同。

select构架,boa和thttpd都很好,我参考了boa,对其进行了精简,添加了http401认证和内置的人数限制,
计划添加内置的聊天室。

那么这个时候我们就遇到了第一个问题,网络协作,至少要知道用户上线下线,那么现有的tcp socket只能检测正常掉线,不能检测网络中断。

就是我拔线了web服务器照样认为我在线,只是没有发送http请求而已。

使用特权

评论回复
7
sinanjj|  楼主 | 2009-8-6 19:14 | 只看该作者
首先我查了查现有的解决方案。

中文讨论大部分集中在read write的表现上,可经过我严格的测试,在客户端拔网线这种情况下,read write均表现正常的返回值,tcp协议层表现为不断的从发。

查到qq等软件使用的“心跳”即每隔一定时间发送一个包,要求客户端回显,这种方法可行,但不适用于web这种符合标准无法对客户端定制的情况。(http没有心跳包吧,查了RFC×××么有看到。 http的keepalive是一直连接的意思)

使用特权

评论回复
8
sinanjj|  楼主 | 2009-8-6 19:17 | 只看该作者
首先我根据现有的讨论情况,编写了一个检查errno的read write + select构架,很复杂。但是对于拔线这种情况同样束手无策,实测中,10s内收不到ack包是不会被判定为socket出错的,10s啊,要知道一台2g CPU的pc没s发报量在10w左右。。。。。10s这个数无论是对PC还是对人来讲都太长了。

此方案放弃。

使用特权

评论回复
9
sinanjj|  楼主 | 2009-8-6 19:25 | 只看该作者
然后我实际量了一些网络协同软件的掉线反应时间,魔兽争霸是最快的,比qq快(看来游戏的要求还真高)大概在2-3s左右,就是说:从某一客户端拔线到其他客户端知道这个客户下线只需要2-3s。

我抓取了它的部分网络包,发现也是心跳的实现方案。

于是我又在http协议上做了点**,隔一段时间写一个 “\r\n\r\n“ 浏览器收到这个信息不会动作,但如果出现问题了write回返回breaken pipe。

发现这个时间太长。找遍http协议,没有找到一个需要浏览器回应服务器的指令。。。。也就是说:http似乎没有可以当成心跳包的。。。。。

使用特权

评论回复
10
sinanjj|  楼主 | 2009-8-6 19:29 | 只看该作者
这个时候我真的感觉山穷水尽了,实在也没别的方法。

加上被要求改进界面,就暂时先把它放了放。

几天后, 用各种英文说法描述这个问题进行搜索,得到一个信息,很多系统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内反应。

使用特权

评论回复
11
sinanjj|  楼主 | 2009-8-6 19:31 | 只看该作者
然后就是一点补救:

直接查看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;
}


大功告成

使用特权

评论回复
12
AmixIce| | 2009-8-7 10:01 | 只看该作者
马克                     
                          .

使用特权

评论回复
13
john_light| | 2009-8-7 10:45 | 只看该作者
不知道谁第一个把socket翻译成套接字的,太离谱了。:L

使用特权

评论回复
14
mohanwei| | 2009-8-7 12:23 | 只看该作者
外行人纳闷一下,为什么非要用web呢?
你敲www.sinanjj.com的时候,它发一个程序给你,exe,java……反正短小精悍能马上执行的就可以
程序执行后就可以用自定义的协议通信了,怎么做都可以了……
我见过的集中监控软件都没有用web的。

使用特权

评论回复
15
mohanwei| | 2009-8-7 12:24 | 只看该作者
视频监视(不是监控)倒是挺多用web的。

使用特权

评论回复
16
jin_gc7723| | 2010-7-8 17:37 | 只看该作者
去“爱问”看看吧 应该有

使用特权

评论回复
17
dong_abc| | 2012-7-15 19:08 | 只看该作者
挖个坟,拿去调一下试试。

使用特权

评论回复
18
sinanjj|  楼主 | 2012-7-15 19:18 | 只看该作者
这个已经淘汰了。

我有空开源个新的。

有需要的可以马上联系我。

使用特权

评论回复
19
OkLove| | 2013-2-1 15:14 | 只看该作者
想要最新的!怎么联系!

使用特权

评论回复
20
yinqiyu22133812| | 2013-2-1 21:30 | 只看该作者
挖个坑,留的以后我跳下去

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

456

主题

6300

帖子

25

粉丝