C#与python UDP打洞通信

2 篇文章 0 订阅

本标题的应用场景是C#系统服务端和基于linux的python设备在不同的局域网下通信,通常C#系统端在办公室内部wifi下,设备在室外利用4G上网。

打洞原理网上蛮多的,随便一搜就是好多,实际将如何打洞的确很少。这里需要理论的推荐一篇博客,个人觉得写的很好。

https://blog.csdn.net/sqqyq/article/details/51841579

本人阅读了两遍这篇博客,画了三张草图。花了一星期(中间大部分事件都在代码调试)完成了这个实验。

打洞通信其实很简单

UDP打洞前准备,需要一台公网服务器(辅助打洞),并且已知可用端口。本实验的环境是腾讯云的ubantu.

第一,进入服务器,修改或者查看IPv4端口可用情况

修改

1.vi /etc/sysctl .config 

可以看到这个:

net.ipv4 ip_local_port_range = 10000 65000

后面就是端口的区间可以改成你需要的端口所在的区间。

当然通常我们都不改,直接查看可用端口

2.sysctl -a|grep port_range

第二,服务器上建立python server,主要用来辅助打洞。host IP为:0.0.0.0 注意不是给你的那个公网/私网IP,也不是127.0.0.1.个人大量尝试出来的,端口选用上面的端口区间内的。(选择后的端口要在控制台配置到安全组,让其可以和外界UDP通信)

接着写代码,语言上选择python,服务器上自带python环境,无需安装,主要区分版本是2.7还是3.2.

这里是个人测试的粗略代码,若需使用还需要看我具体如何传值,主要依靠json。

import socket
import json

byte = 1024
#两个端口要保持一致
port = 32769
host = ""
addr = (host, port)

#创建套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#绑定
sock.bind(addr)
print("waiting to receive messages...")

#创建空字典保存数据
addr_dict = {}

while True:
    (data, clientAddr) = sock.recvfrom(byte)
    if json_data['info'] == "sendAddr":
        # 判断服务器是否已经有了该地址
        print("New addr connect,add connection pool")
        name = json_data['name']
        addr_dict[name] = clientAddr
        print("connection pool:{}".format(addr_dict))

        text = 'Server got Addr'
        data = text.encode('utf-8')
        sock.sendto(data, clientAddr)

    if json_data['info'] == "getAddr":
        name = json_data['name']
        print("Client want to {} client addr in the pool".format(name))
        otherAddr = addr_dict[name]

        data = otherAddr.encode('utf-8')
        sock.sendto(otherAddr, clientAddr)

#关闭套接字
sock.close()

第三步,设备端和系统端的编程。我这里的需求是python和c#,需要的按需更改为相应的语言UDP代码。

设备端python代码:选择1连接服务器,等待系统端连接服务器后选2开始发打洞包,打印error后表示可以接收系统端的消息,选择4开启消息监听

import socket
import json
import threading

host = '云服务器公网ip'
port = 32769
addr = (host, port)
otherAddr = ()
byte = 1024
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)


def myrevc(sock):
    while True:
        data ,newaddr= sock.recvfrom(byte)
        text = data.decode("utf-8")
        if text == "getStr":
            sock.sendto(bytes("hello,I was Device", "utf-8"), newaddr)
        print(text)


while True:
    print("1.sendAddr 2.getAddr 4.listen")
    data = input('Please choose your choose: ')
    if data == "1":
        info = {
            'Name': 'Device1',
            'Info': 'sendAddr'
        }
        json_info = json.dumps(info)
        sock.sendto(bytes(json_info,"utf-8"), addr)

        data, addr = sock.recvfrom(byte)
        text = data.decode("utf-8")
        print(text)
    if data == "2":
        info = {
            'Name': "Device2",
            'Info': 'getAddr'
        }
        json_info = json.dumps(info)
        sock.sendto(bytes(json_info,"utf-8"), addr)

        data, addr = sock.recvfrom(byte)
        jsonData = json.loads(data)
        ip = jsonData['ip']
        port = jsonData['port']
        otherAddr = (ip, port)

        try:
            sock.sendto(bytes("hai","utf-8"), otherAddr)
            data = sock.recvfrom(byte)
            text = data.decode("utf-8")

        except:
            print("error")


    if data == "4":
        threading._start_new_thread(myrevc, (sock,))
    else:
        print(data)

sock.close()

C#系统端代码:

WPF界面代码:

<Window x:Class="TestPeanutHull.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestPeanutHull"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>

        <TextBox x:Name="toIP" HorizontalAlignment="Left" Height="23" Margin="94,22,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120"/>
        <TextBox x:Name="toPort" HorizontalAlignment="Left" Height="23" Margin="94,45,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120"/>
        <Label Content="对方端口" HorizontalAlignment="Left" Margin="29,52,0,0" VerticalAlignment="Top"/>
        <Label Content="对方IP" HorizontalAlignment="Left" Margin="29,22,0,0" VerticalAlignment="Top"/>
        <Button Content="向服务发起连接" HorizontalAlignment="Left" Margin="29,104,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click2" RenderTransformOrigin="0.48,2"/>
        <Button Content="接收对方IPPort" HorizontalAlignment="Left" Margin="109,104,0,0" VerticalAlignment="Top" Width="105" Click="Button_Click"/>
        <TextBlock x:Name="showMsg"  HorizontalAlignment="Left" Height="58" Margin="29,151,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="185" Text="Text"/>
        <Button Content="发送消息" HorizontalAlignment="Left" Height="21" Margin="252,203,0,0" VerticalAlignment="Top" Width="103" Click="Button_Click_1"/>
        <TextBox x:Name="forAnother" HorizontalAlignment="Left" Height="127" Margin="252,52,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="213"/>
        <Button Content="保持连接" HorizontalAlignment="Left" Height="27" Margin="34,219,0,0" VerticalAlignment="Top" Width="166" Click="Button_Click_2"/>

    </Grid>
</Window>

界面后台代码:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Timers;
using System.Windows;

namespace TestPeanutHull
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        Thread thrRecv;
        private byte[] data = new byte[1024];
        private Socket socket;
        private IPEndPoint serverIP;
        private IPEndPoint otherIP;
        public MainWindow()
        {
            InitializeComponent();
           
        }

        //启动UDP服务
        private void Button_Click2(object sender, RoutedEventArgs e)
        {
            //设置服务器IP,PORT 
            serverIP = new IPEndPoint(IPAddress.Parse("云服务器公网ip"), 32769);

            //定义网络类型,数据连接类型和网络协议UDP  
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);


            //发送本机地址
            Msg msg = new Msg
            {
                Name = "Device2",
                Info = "sendAddr"
            };

            string jsonData = JsonConvert.SerializeObject(msg);
            ;
            data = Encoding.UTF8.GetBytes(jsonData);

            socket.SendTo(data, data.Length, SocketFlags.None, serverIP);


            
            // 创建负责监听的线程;
            thrRecv = new Thread(ListenConnecting)
            {
                IsBackground = true
            };
            thrRecv.Start();
            MessageBox.Show("启动UDP监听");
        }

        private void ListenConnecting()
        {
            EndPoint sender = new IPEndPoint(IPAddress.Any, 0);
            byte[] data = new byte[1024];
            int recv = socket.ReceiveFrom(data, ref sender);
            string info = Encoding.UTF8.GetString(data, 0, recv);
            //Console.WriteLine("get info" + info);

            System.Windows.Application.Current.Dispatcher.Invoke(() =>
            {
                this.showMsg.Text = info;
            });

        }

        /// <summary>
        /// 获取另一端IP,地址
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            
            Msg msg = new Msg();
            msg.Name = "Device1";
            msg.Info = "getAddr";

            string jsonData2 = JsonConvert.SerializeObject(msg);
            data = Encoding.UTF8.GetBytes(jsonData2);
            socket.SendTo(data, data.Length, SocketFlags.None, serverIP);



            EndPoint send = new IPEndPoint(IPAddress.Any, 0);
            byte[] IPdata = new byte[1024];
            int re = socket.ReceiveFrom(IPdata, ref send);

            string info = Encoding.UTF8.GetString(IPdata, 0, re);

            JObject jo = (JObject)JsonConvert.DeserializeObject(info);
            String IPaddr = jo["ip"].ToString();
            String port = jo["port"].ToString();
            otherIP = new IPEndPoint(IPAddress.Parse(IPaddr), int.Parse(port));

            System.Windows.Application.Current.Dispatcher.Invoke(() =>
            {
                this.toIP.Text = IPaddr;
                this.toPort.Text = port;
            });

        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            String dataString = this.forAnother.Text;

            socket.SendTo(Encoding.UTF8.GetBytes(dataString), otherIP);

            System.Windows.Application.Current.Dispatcher.Invoke(() =>
            {
                this.forAnother.Text = "";
            });
        }


        System.Timers.Timer aTimer = new System.Timers.Timer();
        //不停发包
        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            
            //到时间的时候执行事件  
            aTimer.Elapsed += new ElapsedEventHandler(sendHeart);
            aTimer.Interval = 10000;
            aTimer.AutoReset = true;//执行一次 false,一直执行true  
            //是否执行System.Timers.Timer.Elapsed事件  
            aTimer.Enabled = true;
            MessageBox.Show("启动UDP心跳");
        }
        //发送心跳
        private void sendHeart(object source, System.Timers.ElapsedEventArgs e)
        {
            String dataString = "1";

            socket.SendTo(Encoding.UTF8.GetBytes(dataString), otherIP);
            //MessageBox.Show("发送心跳");
        }

        private void Button_Click_3(object sender, RoutedEventArgs e)
        {

        }
    }
}

另外涉及到的一个消息实体类,主要用来封装json信息的:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestPeanutHull
{
    class Msg
    {

        private String name;
        private String info;

        public string Name { get => name; set => name = value; }
        public string Info { get => info; set => info = value; }
    }
}

第四步,运行代码,按照流程打洞。

同时运行设备和系统代码,python 选择pycharm测试,选择1,然后c#界面点击向服务发起连接按钮,然后点击接收对方IPPort,上面IP和Port有内容表示第一步成功,然后python端选择2开始打洞,打印error,表示包被系统端丢掉,此时系统端可以对设备发起连接。选择4,等待系统端的消息。系统端再text框输入消息点击发送,发送成功,python会打印信息出来。然后就可以点击保持连接按钮保持这个UDP连接。一个简易的UDP打洞就成功了。

运行效果




  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值