1.执行客户端发来的SQL命令并把结果返回;
  2.同时有多个客户端连接;
  3.服务端在NAT之后,客户端可能在局域网内也可能在外网或不同NAT之后(只考虑 Cone NAT)我自己的思路是:
  服务端创建一个监听线程监听端口如2000,当收到客户端请求后,创建一个新的线程,其UDP的远程地址和端口为刚才监听线程收到的地址和端口,然后再和客户端进行通信做相关处理。当客户端和服务端在局域网内时一切都正常,当服务端在NAT之后,客户端直接通过ADSL拨号通信也正常,但是当客户端也在NAT之后时,服务端收到请求后直接回复没有问题,但是创建了新的线程后,新的线程发送的数据客户端无法收到。
  不知道问题在哪里,困扰了很久,请高人指点一下,谢谢!

解决方案 »

  1.   

    IdUDPServerUDPRead(Sender: TObject; AData: TStream;
          ABinding: TIdSocketHandle); 看你用的什么控件 ,如果 是INDY的,它本身就是多线程的,不用你再去创建新的线程。如果你要回复,直接用 ABinding 发回复数据就可以了。
      

  2.   

    感觉问题在NAT上,可能NAT会丢弃来源不是同一IP和端口的包,不知道QQ等软件如何实现的
      

  3.   

    没有用Indy 调用用winsock API 写的,我去用INDY的试试,估计不成
      

  4.   

    问题应该是不在你创建的新的线程里.你可以先将你的程序的逻辑关系,类层次重新组织的合理清楚一点.问题也许就解决了.服务器端在监听端口,如果有新的请求来到,相当于一个新的任务,创建一个任务对象,其中包括请求的IP和
    端口号子,然后将任务对象放到一个任务列表A中就OK系统中存在一个任务处理线程,负责从任务列表A中取出任务,处理,如果任务实在太多,可以多创建几个处理线程,
    但不要太多.任务处理结束后就将任务和任务结果从任务列表A中放到任务列表B中.系统中存在一个任务发送线程,负责从任务列表B中取出结果发送出去.可以多创建几个任务发送线程.任务列表A,任务列表B 是两个任务缓冲区,如果超出设定值,可以让用户暂缓发送.对于你的需求,接收的都是些SQL语句,任务列表A的压力应该不大.主要在于结果的返回.如果用户了*来查询的话
    会有大量的数量要返回.你可以将一些SQL的查询结果保存下来,当新的SQL语句执行时检查一下是否刚查询过,这样可以快速的返回结果.
      

  5.   

    ZyxIp谢谢,
    你的方法确实是一种办法,如果我的方法行不通只好按这种任务队列方式了。
    我主要想通过类似P2P的方式,每个客户端的每个请求都和服务端建立一个独立的UDP socket,在这个独立的UDP socket 连接中进行相应处理。
      

  6.   

    1.第一步要有个用户在线列表
    2.看看是哪个用户发过来的SQL命令
    3.执行SQL语句
    4.把结果发送给那个用户
      

  7.   

    hys_427 谢谢,
    我前面说的SQL语句,其实不止是SQL文本,还有些参数可能有图片之类的,需要分包发送,服务端也要进行组包,中间还要判断丢包超时等情况。不过这都不是问题,关键我现在的程序,在局域网内运行正常,可以开很多客户端,同一机器上也可以开多个客户端。
    我的服务端在内网,在路由上也做了监听端口的映射,远程客户端如果是ADSL拨号也运行正常,如果是远程客户端也在内网就不能通信了。估计是穿NAT问题,如果有办法解决最好不要用UDP中转,不是不能用中转的方法,只是对穿NAT没有研究过想借此学习一下,网上穿NAT的文章翻来覆去就那一篇文章,下载了一些P2P的源码也看了,那些源码跟我遇到的问题一样,哪位老牛真正的实现了P2P,请不吝赐教
      

  8.   

    full cone nat 没问题 (TPLink 402)
    Symmetric nat 过不去 (DLink DI504)
    其他两种 nat 暂时没有设备
      

  9.   

    其他Nat都可以穿过,就是Symmetric nat无法穿过,Symmetric的情况目前暂时用中转了。我简单说下我的解决办法:假设:
      处理端为:192.168.0.1:2000(内网地址) 222.10.10.10:2000(外网地址)
      中转端为:192.168.0.2:2001(内网地址) 222.10.10.10:2001(外网地址)
      客户端为:192.168.1.1:1000(内网地址) 117.101.10.10:12345(外网地址)1.服务端开一个UDP的监听线程和中转线程,如监听端口 2000中转端口2001,在路由上已做映射;
    2.服务端收到客户端请求后,创建子线程,子线程回复客户端(除Symmetric nat客户端以外,都可以收到回复并可与之通信)
      注:包头不是中转包标志
    3.客户端收到回复后与服务端子线程进行通信(上传命令包、等待执行、接收结果等)4.如果客户端收不到服务端子线程的回复,说明在 Symmetric nat之后
      a.客户端发送包到中转端口,包头添加处理服务器的地址和端口和中转标志
        如:
        TTranPack = record
         TranMark : integer;
         IP : LongInt;
         Port : word;
         end;
      b.中转服务端收到客户端包后,将包中的IP和Port改成客户端外网的IP和Port,然后按包中的地址将包发送出去。
         如:中转端收到客户端117.101.10.10:12345的包,包内IP为222.10.10.10 Port:2000
            中转端修改包内IP由222.10.10.10改为117.101.10.10端口由2000改为123456
            然后将包发送到:222.10.10.10:2000(如中转和处理端都在内网,路由要支持回环,否则建立一个对应表)  c.处理服务器判断为中转包,处理完后包里添加中转标志,地址和端口与收到包里的地址端口相同,然后发给中转服务器。
         如:收到中转端 222.10.10.10:2001的包,包内IP为:117.101.10.10端口为123456
            处理完后,包里的地址仍然为IP为:117.101.10.10端口为123456,
             然后将包发送到:222.10.10.10:2001
      d.中转服务端收到处理端的包后,将包里的IP和Port换成收包的IP和Port,然后按包里的地址发送出去。
        处理过程同B  中转端没有做队列处理,因为我的中转端和处理端在一个内网,中转速度应该很快,而且通信过程采用一问一答的方式,到底会不会丢包只有通过一定量客户端测试才行。
      ZyxIp 的方法也不错,服务端只需要一个端口,而且不需要中转,没有Nat的限制,但是那个队列方式编程复杂度太高,没有一问一答来的简单(个人认为)
      
      
      不知有没有对Symmetric nat的处理的好方法呢?
      再等两天看看,就结贴散分了~
      

  10.   

    按照你的需求你的服务器端可以使用INDY9的IDUDPSERVER来做
    INDY9使用了IOCP,数据到达通知,灰常的方便!
    所以,基本上我可以认为你有了一个UDP端口的服务器(端口映射也是一样),SO你没有必要创建一个子线程去应答。
    INDY为你做好了一个应答机制。基本上如果你有能力使用API完成到现在这个程度,了解和使用INDY也就是10分钟的事情。另外你需要注意的一点就是:数据包的大小,我注意到了你的测试环境是局域网,局域网或者宽带环境MTU是相对宽松的,你发一个N大的包都可以顺利接受
    在一个ADSL拨号的情况下,是有MTU值的限制的一般ADSL为1500
    所以建议你的每个数据包不要大于1458(UDP传输的时候42个IP包头也算)
    另外要考虑的就是UDP的不稳定性,相信你都做到了。
      

  11.   

    不需要创建子线程,就不存在要穿过Symmetric nat的说法了,不过我一直在想,为什么你不用2000端口直接应答呢?
      

  12.   

    yuyuhaso 谢谢,能将一个端口实现的流程简单描述一下吗?我没有测试过INDY的UDP控件,有空去测试一下,正想学习一下IOCP...我估计用一个端口代码太复杂了,我的应答过程如下:
    1.客户端发送请求
    2.服务端回应并要求客户端发送数据包头(指要提交的包的总大小,需要分多少次发送)
    3.客户端发送包头
    4.服务端收到包头,分配缓冲区,并要求客户端发送数据包(N个包)
    5.服务端收取数据包,并检查缺少的包,要求重发(一直重复 4,5步骤直到收取所有数据包)
    6.分解数据包,将SQL语句和参数分解开,调用SQL执行过程(SQL执行的时间可能比较长,因此用线程方式,能够让主线程继续相应客户端命令)
    7.客户端等待服务端执行SQL查询,每隔几秒询问一次是否查询执行完成(所以增加此询问过程,避免客户端因特殊情况无限等待,每次执行的SQL时间可能不一致不能设置固定等待时间)
    8.服务端执行完SQL查询后,将结果放到缓冲区里,将结果大小和需要多少次发送数据发给客户端
    9.客户端收到包头后分配缓冲区,请求服务端发送数据包
    10.服务端发送数据包
    11.客户端收取数据包,并要求重发丢失的包,重复10,11步骤,直到数据包收取完整。
    12.结束在以上过程中,每次的应答都要有超时判断和按指定次数重试,超过指定重试次数后返回错误。当有多个客户端时,服务端相应代码就会变的很复杂,估计处理调试都是麻烦。而且,我准备将文件传输等其他功能也添加进去,增加功能时只需要每次按照相应流程写个相应的线程就OK了,如同写单个客户端和服务器通讯一样,不影响到其他功能。
      个人愚见,可能哪位大侠有更好的方法,我就当抛砖引玉了,共同学习,共同进步!
      

  13.   

    感觉lz有可能的话试试看最新的DataSnap 2009,因为利用JSON封装过,应该比自己根据Socket设计来的简单好用。
      

  14.   

    那样也太难做了吧, udp不支持长数据包的。一般的通信还可以。太复杂的还是用TCP
      

  15.   

    在UDP加控制,实现TCP的数据重发检验。不管什么数据(文本,图像,控制命令),都以某种固定的数据长度和特定的报文头发送。我的做法是:建一个结构,里面包括:
    1、报文类型 
    2、报文参数1 
    3、报文参数2 
    4、后续数据长度 
    5、总数据长度 
    6、文本信息1(固定长度,用于放置文件名之类)
    7、文本信息2(固定长度,用于放置验证码之类)建一个固定长度的buffer,把报文头和数据内容放入这个buffer,sendto....如果在指定时间没有收到“应答”报文,则继续sendtorecvfrom之后根据
    1、报文类型 
    2、报文参数1 
    3、报文参数2 做不同处理。用那两个参数实现多帧数据。处理完该报文之后还要回发“应答”报文,否则另一端会一直发。
    我一般都是直接用winapi socket做的,控件不会用。
    不知对楼主有没有帮助,惭愧说一声,我还不知道NAT是什么东西