TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞
http://www.csharpwin.com/csharpspace/2248.shtml当A需要和B建立直接的TCP连接时,首先连接S的【协助打洞】端口,并发送协助连接申请。同时在该端口号上启动侦听。注意由于要在相同的网络终端上绑定到不同的套接字上,所以必须为这些套接字设置 SO_REUSEADDR 属性(即允许重用),否则侦听会失败。环境:(服务器IP:192.168.123.5,主端口:8885,打洞端口:8886,主端口已连接成功并且成功通讯)
上代码:
private TcpListener ClientListen;                IPEndPoint MyIPEnd = new IPEndPoint(IPAddress.Any, 0);//获取任意可用地址端口
                ClientListen = new TcpListener(MyIPEnd);//建立侦听
                ClientListen.ExclusiveAddressUse = false;//允许复用
                ClientListen.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);//设置复用
                ClientListen.Start();
                AddTalkMessage("连接成功");
                LPort = ((IPEndPoint)ClientListen.LocalEndpoint).Port;
                LIP = ((IPEndPoint)ClientListen.LocalEndpoint).Address;                //后面用来接受连接的代码略……
以上代码启动侦听成功,但在向服务器打洞时出错,代码如下:            IPAddress ServerIP_D = IPAddress.Parse("192.168.123.5");
            int port_D = 8886;
            IPEndPoint EP = new IPEndPoint(LIP, LPort);
            TcpClient BClient = new TcpClient(EP);
            BClient.Connect(ServerIP_D, port_D);//问题就在这里,“通常每个套接字地址(协议/网络地址/端口)只允许使用一次。”,实在不知道该如何让端口可以复用。            //获取网络流
            NetworkStream networkStream = client.GetStream();
            //将网络流作为二进制读写对象
            br = new BinaryReader(networkStream);
            bw = new BinaryWriter(networkStream);
            SendMessage("打洞洞," + ClientListen.LocalEndpoint + "," + txt_UserName.Text);//发送数据的方法
            Thread threadReceive = new Thread(new ThreadStart(ReceiveData));
            threadReceive.IsBackground = true;
            threadReceive.Start();
尝试过程如下:
                ClientListen.ExclusiveAddressUse = false;//允许复用
                ClientListen.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);//设置复用
这二句无论使用或者不使用,均在这里报错:
BClient.Connect(ServerIP_D, port_D);//问题就在这里,“通常每个套接字地址(协议/网络地址/端口)只允许使用一次。”,实在不知道该如何让端口可以复用。
如果ClientListen.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
改为:ClientListen.Server.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.ReuseAddress, true);则执行到这句的时候就直接异常"在 getsockopt 或 setsockopt 调用中指定的一个未知的、无效的或不受支持的选项或层次。
 "
虚心请教:如何才能在C#中让端口允许复用?

解决方案 »

  1.   

    我之前或多或少关注过这些东西.  也用java实现了udp穿透.但因种种原因没有继续研究这个东西. 
    ------------------------------
    别人NAT穿透都是用UDP做的.
    TCP都说无法实现. http://topic.csdn.net/t/20060609/17/4812291.html
    这个贴里多数持有TCP无法穿透NAT的观点. 其中有位获得2个CSDN勋章的牛人(xpdavis) 持观点为:TCP也可以穿透NAT,并且很简单. -- 但是你别被他勋章多, 和他话的表象迷惑. 事实并非他说的那么简单. 因为他下面一句话暴露了他其实不是深入的了解穿透这个东西, 甚至连UDP穿透NAT都不甚了解. 因为他说了:"UDP同样需要通过服务器的。"
    ---------------------------------------------
    我给你找了下TCP穿透:
    http://zhidao.baidu.com/question/51291238.html&__bd_tkn__=28a758392e30983a5a5ce174a4b737af8d1bd7fb8078338d51fed8133ea5c69d362ad36bb4bcda3b39bb3949f6bbe47087ac3af56e60b1f4e7eb60157d5dff3b9c65aaf040491fc7006f477da731bb093e7399737859b884d44d47757d2a342ab8670a463db7dddc9808eabbc9da890dc93022f14a同时也附上个人意见:
    我看了之后认为, 虽然理论上TCP也是可以实现的.
    但是发现他那个东西好像要比UDP复杂太多.  所以你得掂量一下自己是否真有必要去研究这个.或者说, 甚至你研究一下诸如安全UDP也比这个 TCP穿透靠谱呢, 比如UDT协议.
      

  2.   

    其实UDP已经实现了,理论上讲,对于UDP的安全传输方面。
    可以在传办理端把文件人为分包,并在每个包头加上序号标识,然后UDP出去,
    接收端再把包按顺序拼接起来,对于传丢的包,可以请示传输端重新发送。
    这样,理论上可以保证UDP传大文件的可靠性。其实吧,我也不是工作需要而一定要实现TCP穿透,只是想搞通这个,
    我想知道的是,如果TCP不能穿透,那QQ是怎么做的?聊天、视频、语音、传文件?这些难道都是UDP?QQ传文件肯定是点对点的(不需要QQ服务器中转数据包),传输的速度取决于传输端上行速率和接收端的下行速率之中最慢的一个,我们可以在QQ传文件的过程中,在cmd中用netstat -an看究竟是什么协议为什么QQ能实现TCP穿透,而我不能?
    QQ强是因为他有很强大的研发队伍,他的强是体现在穿后之后的。
    我弱,但我只是要实现穿透。穿透之后的功能先不考虑~
      

  3.   

    不大记得了,当时只研究了UDP打洞
    TCP打洞只是看了下,没写过代码
    我记得好像用同一个Socket,然后设置端口复用
    连接服务器
    然后断开,再监听
      

  4.   

    谢谢回复,我已经直接使用Socket实现了端口复用,抛开了封装的TcpListener和TcpClient
    目前正在实验,谢谢各位!
      

  5.   

    以前我也研究过这个,虽然能实现,但是成功率比UDP低很多
    原理很多人说了我就不说了。我只说一下我的步骤,
    大概过程是这样:
    AB各与服务器建立一个长久连接。
    当需要打洞的时候,A先用这个长久的连接给服务器一个通知。然后服务器通知B。A监听一个端口,并且重用这个端口向服务器建立一个连接。
    B监听一个端口,并且重用这个端口向服务器建立一个连接。
    服务器把双方连接服务器的端口和IP发给对方。
    然后A和B都挂断与服务器的这个连接,各自继续复用这个端口向从服务器收到的对方IP和端口建立连接
    第一次,如果有一方成功连接到对方了,另一方就取消它发出的连接请求。
    如果第一次没成功就再连接一次。第二次依然和第一次一样,只要有一方成功连接到另一方,那另一方就取消它发出的连接请求。
    但如果第二次依然没有任何一方成功连接到对方的,那就可以放弃了,说明双方的设备都不支持这种打洞方式。
    或者先由一方主动连接另一方,不成功就告诉服务器,服务器再通知另一方连接这边。一共4次。4次不成功就可以放弃了
      

  6.   

    以前我也研究过这个,虽然能实现,但是成功率比UDP低很多
    原理很多人说了我就不说了。我只说一下我的步骤,
    大概过程是这样:
    AB各与服务器建立一个长久连接。
    当需要打洞的时候,A先用这个长久的连接给服务器一个通知。然后服务器通知B。A监听一个端口,并且重用这个端口向服务器建立一个连接。
    B监听一个端口,并且重用这个端口向服务器建立一个连接。
    服务器把双方连接服务器的端口和IP发给对方。
    然后A和B都挂断与服务器的这个连接,各自继续复用这个端口向从服务器收到的对方IP和端口建立连接
    第一次,如果有一方成功连接到对方了,另一方就取消它发出的连接请求。
    如果第一次没成功就再连接一次。第二次依然和第一次一样,只要有一方成功连接到另一方,那另一方就取消它发出的连接请求。
    但如果第二次依然没有任何一方成功连接到对方的,那就可以放弃了,说明双方的设备都不支持这种打洞方式。
    或者先由一方主动连接另一方,不成功就告诉服务器,服务器再通知另一方连接这边。一共4次。4次不成功就可以放弃了我就特别纳闷了,服务器有必要使用第二个连接吗?使用一个不照样也行。
    A和B分别连接到服务器S,这时候服务器不就知道AB双方的地址了吗?