【计算机网络通信】计算机之间的局域网通信和互联网通信方法(附Python和C#代码)

news/2024/5/18 16:37:22 标签: python, c#, 网络, 信息与通信, tcp/ip, udp

文章目录

  • 前言
  • 一、局域网通信
    • 1.1 基本原理和方法
      • 1.1.1 获取本地ip
      • 1.1.2 实现局域网内的广播
      • 1.1.3 进行局域网通信
    • 1.2 实现多客户端连接
    • 1.3 Python源码
    • 1.4 C#源码
    • 1.5 可能存在的问题
  • 二、互联网通信
    • 2.1 实现原理
      • 2.1.1 内网穿透软件
      • 2.1.2 实现互联网通信
    • 2.2 Python源码
    • 2.3 C#源码
  • 结语

前言

本文整合了在局域网和互联网两种情况下的通信都应该怎么实现。网上的资料大多在教学socket使用时只会教学怎么实现局域网通信,导致还需要搜很多额外的资料才能接触到互联网通信。
这里需要注意,个人电脑上可以自己和自己通信成功,不代表代码放到其他计算机上就可以成功实现局域网或互联网通信,有条件一定要尝试两个不同计算机的通信。互联网通信如果不确定是不是依旧是使用的局域网通信方法,可以将一台计算机连路由器,一台计算机连手机热点(总之不是一个局域网内即可)。


一、局域网通信

1.1 基本原理和方法

1.1.1 获取本地ip

获取本地IP的方法有很多,这里介绍三种方法。分别是cmd中查看、python和C#调用函数查看。
1、cmd查看本地IP
此种方法有较大的局限性,因为本地ip在每次的分配过程中有可能会改变,想要每次都连接上对方的计算机需要每次都修改成当前的本地IP地址,添加了不必要的工作量。当然,如果只是初学做一个实验测试还是可以使用的。
1)首先win + R打开“运行”,在搜索框输入cmd。
打开运行并输入cmd
2)点击确定后进入cmd命令窗口,输入ipconfig并回车执行命令,就可以得到结果。
获取本地ip
可以看到图中标注红框的部分,192.168.0.103就是本机的本地IP。
2、python查看本地IP
由于本地IP每次分配是有很大可能是会变化的,所以大部分应用场景需要程序中直接获取,而不是在程序中写一个既定的IP地址。

python">import socket
ip = socket.gethostbyname(socket.gethostname())

socket.gethostname()将返回本机名称,socket.gethostbyname()参数放入本机名称后就会返回本地IP。

3、C#查看本地IP

using System.Net;
// 主机名
string hostName = Dns.GetHostName();
// 获取本机本地ip
IPAddress address = Dns.GetHostAddresses(hostName)[1];

步骤与python是类似的,就不多解释了。
注:其实也可以用python和C#的系统调用,调用cmd里输入的命令获取返回值。

1.1.2 实现局域网内的广播

在知道如何获取本机的本地IP之后,需要一种方法将服务端的IP地址告诉客户端,这个时候就需要用到广播技术(因为不通过广播告诉客户端服务端的IP地址,客户端将无从得知应该连接哪一个IP)。首先服务端要不断的广播,将自己的IP地址广播出去,随后客户端要接听广播。当客户端收到广播之后,再给服务端广播,告知服务端已经收到了服务端的广播。这时服务端进入监听阶段,服务端进入连接阶段。连接成功后就可以开始通信了。
整个流程如图所示:
建立连接过程
当然,上述这种方式是线性的流程,只适用于有一个客户端时候的通信。之后再讲一种可以连接多个客户端的方法。
1、python进行广播的方法:
1)先用subprocess库获取子网掩码

python">import subprocess
subnet_mask = subprocess.getoutput("ipconfig | findstr 子网掩码")
subnet_mask = subnet_mask.split(":")[-1].strip()

2)根据本地IP和子网掩码获取广播地址

python">import ipaddress
network = ipaddress.ip_network(f"{self.ip}/{self.subnet_mask}", strict=False)
broadcast_address = network.broadcast_address

3)进行广播

python"># 创建一个socket对象, 参数表示使用IPV4和TCP协议
broadcast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 设置广播选项
# socket.SOL_SOCKET表示选项的级别是socket级别, 这意味着这个选项将应用于socket本身, 而不是特定的协议
# socket.SO_BROADCAST表示要设置的选项是广播选项, 决定了socket是否可以发送广播消息
# 1表示启用广播选项
broadcast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# 发送广播信息
broadcast_socket.sendto(info.encode("utf-8"), (self.broadcast_ip, self.port))

接收广播

python">broadcast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
broadcast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# 绑定到一个特定的端口
broadcast_socket.bind(("", broadcast_port))
# 接收广播信息
data, addr = broadcast_socket.recvfrom(1024)

2、C#进行广播的方法
1)获取子网掩码

using System.Net.NetworkInformation;

// 获取所有网络接口
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
// 遍历所有网络接口
foreach (var networkInterface in networkInterfaces) {
    // 获取IP属性
    var ipProperties = networkInterface.GetIPProperties();
    // 获取单播地址
    var unicastAddresses = ipProperties.UnicastAddresses;
    // 获取IPv4单播地址
    var ipv4UnicastAddresses = unicastAddresses.Where(x => x.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);

    foreach (var unicastAddress in ipv4UnicastAddresses) {
        // 如果这个地址是本机的本地IP地址
        if (unicastAddress.Address.ToString() == ip) {
            // 获取子网掩码
            var subnetMask = unicastAddress.IPv4Mask;
            return subnetMask.ToString();
        }
    }
}

还有一种使用System.Management包的方法,代码量会低一些,但是这种方法只支持windows操作系统,所以这里给出的是另一种方法。
2)计算广播地址

string broadcastAddress = "";
// 分割IP地址和子网掩码
string[] ipArray = ip.Split('.');
string[] subnetMaskArray = subnetMask.Split('.');
// 计算广播地址
for (int i = 0; i < 4; i++) {
    int ipInt = Convert.ToInt32(ipArray[i]);
    int subnetMaskInt = Convert.ToInt32(subnetMaskArray[i]);
    // 广播地址 = IP地址 | (~子网掩码 & 0xff)
    int broadcastInt = ipInt | (~subnetMaskInt & 0xff);
    // 拼接广播地址
    broadcastAddress += broadcastInt.ToString() + ".";
}
broadcastAddress = broadcastAddress.Substring(0, broadcastAddress.Length - 1);

3)进行广播

// 创建UDP客户端
UdpClient udpClient = new UdpClient();
// 允许发送广播
udpClient.EnableBroadcast = true;
// 广播地址
IPEndPoint broadcastPoint= new IPEndPoint(broadcastAddress , 8080);

// 要发送的数据
byte[] bytes = Encoding.ASCII.GetBytes(info);
while (toBroadcast) {
    // 发送数据
    broadcastClient.Send(bytes, bytes.Length, broadcastPoint);
    // 等待1秒
    System.Threading.Thread.Sleep(delay);
}

接收广播

UdpClient udpClient = new();
IPEndPoint endPoint = new(IPAddress.Any, 8080);
// 绑定本地端口
udpClient.Client.Bind(endPoint);

while (true) {
    // 接收广播
    byte[] bytes = udpClient.Receive(ref endPoint);
    string message = Encoding.ASCII.GetString(bytes);
    Console.WriteLine($"接收到广播: {message} 来自: {endPoint.Address}:{endPoint.Port}");
}

注:端口号是需要提前设定好的,是用来区别应用程序的标志。

1.1.3 进行局域网通信

1、python实现局域网通信
服务端需要一个socket对象,再让其绑定到本地IP和一个端口上,之后监听就可以了。如果需要发送消息就对连接上的客户端发消息,需要接收就接收。

python">import socket
# 创建一个socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定到一个特定的端口
server_socket.bind((ip, post))
# 开始监听连接, 参数表示最大连接数
server_socket.listen(max_connections)
print("服务器已启动,等待连接...")

while True:
    # 接受一个连接, 返回一个客户的socket对象和客户端的IP地址
    client_socket, client_address = server_socket.accept()
    print(f"客户端{client_address}已连接")
    # 接收数据, 参数表示最大接收字节数
    data = client_socket.recv(1024)
    print(f"{client_address}接收到数据:{data.decode('utf-8')}")
    # 发送反馈
    client_socket.send("数据已接收".encode('utf-8'))

客户端需要连接服务端,连接上之后就可以发送和接收消息。

python">import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 尝试连接服务器
client_socket.connect((server_ip, server_post))

2、C#实现局域网通信
服务端

// 创建服务端, TcpListener是采用TCP协议的监听器
// UDP协议的监听器是UDPListener
TcpListener server = new(IPAddress.Parse(serverIp), serverPost);
server.Start();
Console.WriteLine("服务器已启动,等待连接...");

while (true) {
    // 接收客户端连接
    TcpClient client = server.AcceptTcpClient();
    Console.WriteLine("客户端已连接");
    // 获取客户端的网络
    NetworkStream stream = client.GetStream();
    byte[] buffer = new byte[1024];
    // 读取客户端发送的数据
    int bytesRead = stream.Read(buffer, 0, buffer.Length);
    string data = Encoding.UTF8.GetString(buffer, 0, bytesRead);
    Console.WriteLine($"接收到数据:{data}");
    // 反馈信息
    byte[] response = Encoding.UTF8.GetBytes("数据已接收");
    stream.Write(response, 0, response.Length);
    // 结束连接
    client.Close();
}

客户端

// 创建客户端, TCPClient是采用TCP协议的客户端
// UDP协议的客户端是UDPClient
TcpClient client = new("192.168.0.103", 8888);
// 发送数据给服务器
NetworkStream stream = client.GetStream();
byte[] data = Encoding.UTF8.GetBytes("你好, 服务器");
stream.Write(data, 0, data.Length);
// 接收服务器的响应
byte[] buffer = new byte[1024];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"接收到服务器的响应:{response}");
// 关闭连接
client.Close();

1.2 实现多客户端连接

在1.1中,讲述了如何实现最简单的单客户端和服务端的连接,而想要实现多客户端连接,使用多线程技术比较容易一些。这里将广播、监听、接收和发送信息这三个功能分别创建了一个线程。如果有其他需求,比如想要接受和发送分开或者要修改什么内容之类的,可以自行设置。在设计某些功能时候可能会用到更多的多线程相关知识,这里就不做解释了。如果想要详细学习多线程,请移步到【Python】Python多线程详解和C#高级–多线程详解等教程。
分为三个线程后,就可以一边广播,一边监听,一边接收发送信息。这样就互不影响了,也就可以让多个客户端连接上服务端了。具体的代码实现见1.3 Python源码和1.4 C#源码

1.3 Python源码

源码分为服务端和客户端代码。下面是服务端代码

python">import time
import socket
import threading
import ipaddress
import subprocess

class Server:
    def __init__(self) -> None:
        # 创建一个socket对象, 参数表示使用IPV4和TCP协议
        # IPV6使用AF_INET6, UDP使用SOCK_DGRAM
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 广播用socket对象
        self.broadcast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 设置广播选项
        self.broadcast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

        # 获取本机的IP地址
        self.ip = socket.gethostbyname(socket.gethostname())
        # 子网掩码
        self.subnet_mask = self.get_subnet_mask()
        # 广播地址
        self.broadcast_ip = self.get_broadcast_ip()
        # 端口号
        self.broadcast_port = 8080
        self.server_port = 8081

        # 客户端列表
        self.clients = {}
        # 最大连接数
        self.max_connections = 1
        # 是否要广播
        self.to_broadcast = True
        # 是否要接收客户连接
        self.to_accept = True
                     
    def get_subnet_mask(self):
        """获取子网掩码
        """
        subnet_mask = subprocess.getoutput("ipconfig | findstr 子网掩码")
        return subnet_mask.split(":")[-1].strip()
    
    def get_broadcast_ip(self):
        """获取广播地址
        """
        # 计算广播地址
        network = ipaddress.ip_network(f"{self.ip}/{self.subnet_mask}", strict=False)
        return str(network.broadcast_address)
        
    def broadcast(self, info: str, delay=1):
        """进行广播
        """
        while self.to_broadcast:
            # 发送广播消息
            self.broadcast_socket.sendto(info.encode("utf-8"), (self.broadcast_ip, self.broadcast_port))
            # 等一段时间
            time.sleep(delay)

    def accept_client(self):
        """启动服务器
        """
        # 绑定到一个特定的端口
        self.server_socket.bind((self.ip, self.server_port))
        # 开始监听连接, 参数表示最大连接数
        self.server_socket.listen(self.max_connections)
        print("服务器已启动,等待连接...")

        while self.to_accept:
            # 接受一个连接, 返回一个客户的socket对象和客户端的IP地址
            client_socket, client_address = self.server_socket.accept()
            self.clients[client_address] = client_socket
            print(f"客户端{client_address}已连接")
            # 如果连接数达到最大连接数, 则不再接受连接
            if len(self.clients) == self.max_connections:
                self.to_accept = False
                print("已达到最大连接数")

            time.sleep(0.01)

    def receive_data(self):
        """接收数据并发送反馈
        """
        while True:
            for client_address, client_socket in self.clients.items():
                # 接收数据, 参数表示最大接收字节数
                data = client_socket.recv(1024)
                print(f"{client_address}接收到数据:{data.decode('utf-8')}")
                # 发送反馈
                client_socket.send("数据已接收".encode('utf-8'))

            time.sleep(0.01)

    def run(self):
        """运行服务器
        """
        # 广播线程
        broadcast_thread = threading.Thread(target=self.broadcast, args=(self.ip, 1))
        # 客户端连接线程
        accept_thread = threading.Thread(target=self.accept_client)
        # 接收数据线程
        receive_thread = threading.Thread(target=self.receive_data)

        # 启动线程
        broadcast_thread.start()
        accept_thread.start()
        receive_thread.start()

if __name__ == "__main__":
    s = Server()
    s.run()

下面是客户端代码

python">import time
import socket
import threading

class Client:
    def __init__(self):
        # 创建一个UDP socket
        self.broadcast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 设置socket的广播选项
        self.broadcast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        # 创建一个TCP socket
        self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 端口号
        self.broadcast_port = 8080
        self.server_post = 8081

        # 服务端地址
        self.server_ip = None

    def receive_broadcast(self):
        """接收广播消息
        """
        # 绑定到一个特定的端口
        self.broadcast_socket.bind(("", self.broadcast_port))

        while self.server_ip is None:
            # 接收广播消息
            data, addr = self.broadcast_socket.recvfrom(1024)
            print(f"接收到消息: {data} 来自: {addr}")
            # 设置服务端地址
            if data.decode("utf-8") == addr[0]:
                self.server_ip = addr[0]
            # 暂停一段时间
            time.sleep(0.01)
        
    def connect_server(self):
        """连接服务器
        """
        # 等待接收到广播消息
        while self.server_ip is None:
            time.sleep(1)
            
        # 连接服务器
        self.client_socket.connect((self.server_ip, self.server_post))
        print("连接服务器成功")

        while True:
            data = self.client_socket.recv(1024)
            if data:
                print(f"接收到数据: {data.decode('utf-8')}")
            else:
                print("服务器断开连接")
                break

    def run(self):
        """运行客户端
        """
        receive_broadcast_thread = threading.Thread(target=self.receive_broadcast)
        connect_server_thread = threading.Thread(target=self.connect_server)
        receive_broadcast_thread.start()
        connect_server_thread.start()


if __name__ == "__main__":
    client = Client()
    client.run()

代码当中服务端的监听和广播都是在不符合要求后就会直接退出函数的,也就是说假如有客户端退出后也不会再次运行这两个函数。如果想要持续监听和广播,可以改为如下的形式:

python">    def broadcast(self, info: str, delay=1):
        """进行广播
        """
        while True:
            # 不需要广播就暂停1秒后再查看需不需要广播
            if not self.to_broadcast:
                time.sleep(1)
            # 发送广播消息
            self.broadcast_socket.sendto(info.encode("utf-8"), (self.broadcast_ip, self.broadcast_port))
            # 等一段时间
            time.sleep(delay)

监听也是如此,将外部条件改为无条件循环,内层再进行判别。

1.4 C#源码

服务端代码

using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Net.NetworkInformation;

class Server {
    // 服务端
    public TcpListener server;
    // 广播套接字
    public UdpClient broadcastClient;
    // 本地IP地址
    public string ip;
    // 广播地址
    public string broadcastAddress;
    public IPEndPoint broadcastPoint;
    // 连接了的客户端
    public TcpClient[] clients;

    // 最大连接数量
    public int maxClient;
    // 是否要广播
    public bool toBroadcast;
    // 是否要接收客户连接
    public bool toAccept;

    public Server() {
        ip = Convert.ToString(Dns.GetHostAddresses(Dns.GetHostName())[1]);
        // 创建服务端, TcpListener是采用TCP协议的监听器
        // UDP协议的监听器是UDPListener
        server = new(IPAddress.Parse(ip), 8081);
        // 获取广播地址
        broadcastAddress = GetBroadcastAddress(ip, GetSubnetMask());
        // 创建广播套接字
        broadcastPoint = new IPEndPoint(IPAddress.Parse(broadcastAddress), 8080);
        broadcastClient = new UdpClient();
        // 启用广播
        broadcastClient.EnableBroadcast = true;
        // 最大连接数量
        maxClient = 1;
        clients = new TcpClient[maxClient];

        toBroadcast = true;
        toAccept = true;
    }

    // 获取本地IP子网掩码
    public string GetSubnetMask() {
        // 获取所有网络接口
        var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
        // 遍历所有网络接口
        foreach (var networkInterface in networkInterfaces) {
            // 获取IP属性
            var ipProperties = networkInterface.GetIPProperties();
            // 获取单播地址
            var unicastAddresses = ipProperties.UnicastAddresses;
            // 获取IPv4单播地址
            var ipv4UnicastAddresses = unicastAddresses.Where(x => x.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);

            foreach (var unicastAddress in ipv4UnicastAddresses) {
                // 如果这个地址是本机的本地IP地址
                if (unicastAddress.Address.ToString() == ip) {
                    // 获取子网掩码
                    var subnetMask = unicastAddress.IPv4Mask;
                    return subnetMask.ToString();
                }
            }
        }
        return "";
    }

    // 根据子网掩码和IP地址获取广播地址
    public string GetBroadcastAddress(string ip, string subnetMask) {
        string broadcastAddress = "";
        // 分割IP地址和子网掩码
        string[] ipArray = ip.Split('.');
        string[] subnetMaskArray = subnetMask.Split('.');
        // 计算广播地址
        for (int i = 0; i < 4; i++) {
            int ipInt = Convert.ToInt32(ipArray[i]);
            int subnetMaskInt = Convert.ToInt32(subnetMaskArray[i]);
            // 广播地址 = IP地址 | (~子网掩码 & 0xff)
            int broadcastInt = ipInt | (~subnetMaskInt & 0xff);
            // 拼接广播地址
            broadcastAddress += broadcastInt.ToString() + ".";
        }
        return broadcastAddress.Substring(0, broadcastAddress.Length - 1);
    }

    // 进行广播
    public void Broadcast(string info, int delay=1000) {
        // 要发送的数据
        byte[] bytes = Encoding.ASCII.GetBytes(info);
        while (toBroadcast) {
            // 发送数据
            broadcastClient.Send(bytes, bytes.Length, broadcastPoint);
            // 等待1秒
            System.Threading.Thread.Sleep(delay);
        }
    }

    // 接收客户端连接
    public void AcceptClient() {
        // 开始监听
        server.Start();
        Console.WriteLine("服务器已启动,等待连接...");

        // 接收客户端连接
        while (toAccept) {
            // 接收客户端连接
            TcpClient client = server.AcceptTcpClient();
            // 添加到客户端列表
            for (int i = 0; i < clients.Length; i++) {
                if (clients[i] == null) {
                    clients[i] = client;
                    break;
                }
            }
            Console.WriteLine($"客户端{client.Client.RemoteEndPoint}已连接");
            // 如果连接数超过最大连接数
            int count = 0;
            for (int i = 0; i < maxClient; i++) {
                if (clients[i] != null) {
                    count++;
                }
            }
            if (count > maxClient) {
                Console.WriteLine("连接数超过最大连接数");
                toAccept = false;
                break;
            }
            // 暂停0.01秒
            System.Threading.Thread.Sleep(10);
        }
    }

    // 接收数据并反馈
    public void ReceiveData() {
        // 接收数据
        while (true) {
            foreach (TcpClient client in clients) {
                if (client == null) {
                    continue;
                }
                // 获取网络
                NetworkStream stream = client.GetStream();
                // 接收数据
                byte[] bytes = new byte[1024];
                int length = stream.Read(bytes, 0, bytes.Length);
                string data = Encoding.ASCII.GetString(bytes, 0, length);
                Console.WriteLine($"接收到来自{client.Client.RemoteEndPoint}的数据:{data}");
                // 发送数据
                byte[] msg = Encoding.ASCII.GetBytes("已收到数据");
                stream.Write(msg, 0, msg.Length);
            }
        }
    }

    // 运行服务器
    public void Run() {
        // 广播线程
        Thread broadcastThread = new(() => Broadcast(ip, 1000));
        // 接收客户端连接线程
        Thread acceptThread = new(AcceptClient);
        // 接收数据线程
        Thread receiveThread = new(ReceiveData);

        // 启动线程
        broadcastThread.Start();
        acceptThread.Start();
        receiveThread.Start();
    }
}

class MainClass {
    static void Main() {
        Server server = new();
        server.Run();
    }
}

客户端代码

using System.Net;
using System.Text;
using System.Net.Sockets;

class Client {
    // 客户端
    public TcpClient client;
    // 广播套接字
    public UdpClient broadcastClient;
    // 广播地址
    public IPEndPoint broadcastPoint;
    // 服务端IP地址
    public string serverIp;

    public Client() {
        client = new TcpClient();
        // 创建广播套接字并启用广播
        broadcastClient = new UdpClient();
        broadcastClient.EnableBroadcast = true;
        // 设置广播地址
        broadcastPoint = new(IPAddress.Any, 8080);
        // 设置服务端IP地址
        serverIp = "";
    }

    // 接收广播消息
    public void ReceiveBroadcast() {
        // 绑定广播地址
        broadcastClient.Client.Bind(broadcastPoint);

        while (serverIp == "") {
            // 接收广播消息
            byte[] data = broadcastClient.Receive(ref broadcastPoint);
            // 设置服务端IP地址
            serverIp = Encoding.UTF8.GetString(data);
            // 输出服务端IP地址
            Console.WriteLine("获取到服务端ip地址: " + serverIp);
        }
    }

    // 连接服务端
    public void ConnectServer() {
        // 等待接收广播消息
        while (serverIp == "") {
            Thread.Sleep(1000);
        }

        // 连接服务端
        client.Connect(serverIp, 8081);
        // 输出连接成功
        Console.WriteLine("连接成功");

        // 接收数据
        while (true) {
            byte[] bytes = new byte[1024];
            int length = client.GetStream().Read(bytes, 0, bytes.Length);
            string data = Encoding.UTF8.GetString(bytes, 0, length);
            Console.WriteLine("接收到来自服务端的数据:" + data);
        }
    }

    // 运行客户端
    public void Run() {
        // 接收广播消息线程
        Thread receiveBroadcastThread = new(() => ReceiveBroadcast());
        Thread connectServerThread = new(() => ConnectServer());
        receiveBroadcastThread.Start();
        connectServerThread.Start();
    }
}

class Program {
    static void Main(string[] args) {
        Client client = new();
        client.Run();
    }
}

1.5 可能存在的问题

局域网通信存在可能无法通信的情况,即服务端开始监听后,客户端却连接不到服务端。这种情况首先需要检查是否在同一个路由器的公网IP下,并要确认是否处于同一个子网。随后再查看防火墙的设置,有时候防火墙会阻止通信,可以关掉防火墙试试。
检查公网IP的方法。这里依旧是三种方法。
1、cmd中查看公网IP
输入curl 4.ipw.cn,执行后便会返回一个IP地址的结果,该结果便是公网ip。
查看公网ip
图中马赛克部分就是我的公网IP了。
2、python查看公网IP

python">import subprocess
ip = subprocess.getoutput("curl 4.ipw.cn").split("\n")[-1].strip()

3、C#查看公网IP

// 获取公网IP所需的URL
string url = "http://checkip.dyndns.org";
// 请求URL
WebRequest request = WebRequest.Create(url);
// 获取响应
WebResponse response = request.GetResponse();
// 读取响应流
StreamReader stream = new StreamReader(response.GetResponseStream());
// 读取返回的HTML
string publicIP = stream.ReadToEnd();

// 清理返回的HTML标签
int first = publicIP.IndexOf("Address: ") + 9;
int last = publicIP.LastIndexOf("</body>");
// 获取公网IP
publicIP = publicIP.Substring(first, last - first);

确认一致后再确认是否处于一个子网中,依旧是输入ipconfig来检查。
检查子网
这里可以看到,本地IP为192.168.0.103,子网掩码为255.255.255.0,那么根据小学三年级学过的数学知识将其转换为二进制(如果嫌麻烦可以使用windows自带的程序员计算器):
1100 0000.1010 1000.0000 0000.0110 0111
1111 1111.1111 1111.1111 1111.0000 0000
上下对应着做与运算,就可以得到结果了:
1100 0000.1010 1000.0000 0000.0000 0000
也就是说我当前处于的子网是192.168.0.0。相同步骤去检查另一台计算机,如果一致,那大概率是防火墙阻止了,这时就应该试试关闭防火墙。关闭防火墙就不详细讲了,直接上链接。
Win10怎么关闭防火墙,Win10系统防火墙关闭的方法
Win11关闭防火墙,5种不同路径,总有一款适合你

二、互联网通信

2.1 实现原理

互联网上的通信稍微复杂些,因为内网是无法直接在互联网上通信的,想要互联网通信就需要暴露在公网上才行,例如可以和一个有公网IP的服务器通信。个人计算机想做到这一点也并不是很困难,本文介绍一种方法,可以实现互联网通信,该技术被称作内网穿透技术。该技术有一个形象的解释视频,可以去看看→学会突破那一层,收获更多快乐←
据说还可以更改路由器的转发表来达到效果,不过本文就不采用这种方式了。

2.1.1 内网穿透软件

内网穿透有很多网站提供了软件来方便我们打通隧道,本文将以cpolar为例进行解释如何使用。
cpolar官网地址:cpolar - secure introspectable tunnels to localhost
1、首先注册一个账号,这一步就不多说了。
2、下载软件,并打开软件。
打开软件
3、根据网站给的authtoken执行代码
authtoken
执行命令
执行指令根据自己的系统目录确定用./cpolar还是直接cpolar。
4、最后就可以打通隧道了,根据自己想要用的协议和端口来设置。
例如用TCP协议和端口号为808,那么就执行cpolar tcp 808,执行后便会给出一个URL,之后就可以根据URL来通信了。
结果
有些网站给出的将会是一个公网IP,那么就将这里给出的URL替换为公网IP即可,这两个是一个效果。

2.1.2 实现互联网通信

其实程序实现互联网通信和实现局域网通信的过程最大不同就是内网穿透的实现,其余的便是服务器地址的确定方式不同。互联网应用服务器公网IP一般是放入配置文件或采用DNS服务,不像局域网应用一般是随便一个主机都可能当服务端,需要广播实现。当然,这里为了方便实现,就直接写入程序了。
由此可见,只是简单实现互联网通信,其实程序还要比局域网通信少一个广播的过程。所以这里就不再详细解释代码了,直接放上源码。
要注意的是,在互联网通信中,服务端的IP可以是127.0.0.10.0.0.0,端口号是打通隧道时候用的端口号,例打通隧道时用的cpolar tcp 8080,那么端口号也要用8080。客户端的IP应当是返回的URL或公网IP,假如是URL,例tcp://16.tcp.cpolar.top:11858,那么IP位置应该写16.tcp.cpolar.top,端口号应该写11858。

2.2 Python源码

服务端代码

python">import time
import socket
import threading

class Server:
    def __init__(self) -> None:
        # 创建一个socket对象
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 端口号
        self.port = 8080

        # 客户端列表
        self.clients = {}
        # 最大连接数
        self.max_connections = 1
        # 是否要接收客户连接
        self.to_accept = True

    def accept_client(self):
        """启动服务器
        """
        # 绑定到一个特定的端口
        self.server_socket.bind(("0.0.0.0", self.port))
        # 开始监听连接, 参数表示最大连接数
        self.server_socket.listen(self.max_connections)
        print("服务器已启动,等待连接...")

        while self.to_accept:
            # 接受一个连接, 返回一个客户的socket对象和客户端的IP地址
            client_socket, client_address = self.server_socket.accept()
            self.clients[client_address] = client_socket
            print(f"客户端{client_address}已连接")
            # 如果连接数达到最大连接数, 则不再接受连接
            if len(self.clients) == self.max_connections:
                self.to_accept = False
                print("已达到最大连接数")

            time.sleep(0.01)

    def receive_data(self):
        """接收数据并发送反馈
        """
        while True:
            for client_address, client_socket in self.clients.items():
                # 接收数据, 参数表示最大接收字节数
                data = client_socket.recv(1024)
                print(f"{client_address}接收到数据:{data.decode('utf-8')}")
                # 发送反馈
                client_socket.send("数据已接收".encode('utf-8'))

            time.sleep(0.01)

    def run(self):
        """运行服务器
        """
        # 客户端连接线程
        accept_thread = threading.Thread(target=self.accept_client)
        # 接收数据线程
        receive_thread = threading.Thread(target=self.receive_data)

        # 启动线程
        accept_thread.start()
        receive_thread.start()

if __name__ == "__main__":
    s = Server()
    s.run()

客户端代码

python">import socket
import threading

class Client:
    def __init__(self):
        # 创建一个TCP socket
        self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 服务端地址
        self.server_ip = "18.tcp.cpolar.top"
        # 服务端端口
        self.server_post = 11925
        
    def connect_server(self):
        """连接服务器
        """
        # 连接服务器
        self.client_socket.connect((self.server_ip, self.server_post))
        print("连接服务器成功")

        while True:
            data = self.client_socket.recv(1024)
            if data:
                print(f"接收到数据: {data.decode('utf-8')}")
            else:
                print("服务器断开连接")
                break

    def run(self):
        """运行客户端
        """
        # 启动接收广播消息的线程
        connect_server_thread = threading.Thread(target=self.connect_server)
        connect_server_thread.start()


if __name__ == "__main__":
    client = Client()
    client.run()

2.3 C#源码

服务端代码

using System.Net;
using System.Text;
using System.Net.Sockets;
using System.Net.NetworkInformation;

class Server {
    // 服务端
    public TcpListener server;

    // 最大连接数量
    public int maxClient;
    // 客户端列表
    public TcpClient[] clients;
    // 是否要接收客户连接
    public bool toAccept;

    public Server() {
        // 创建服务端
        server = new TcpListener(IPAddress.Parse("0.0.0.0"), 8080);
        // 最大连接数量
        maxClient = 1;
        // 客户端列表
        clients = new TcpClient[maxClient];
        // 是否要接收客户连接
        toAccept = true;
    }

    // 接收客户端连接
    public void AcceptClient() {
        // 开始监听
        server.Start();
        Console.WriteLine("服务器已启动,等待连接...");

        // 接收客户端连接
        while (toAccept) {
            // 接收客户端连接
            TcpClient client = server.AcceptTcpClient();
            // 添加到客户端列表
            for (int i = 0; i < clients.Length; i++) {
                if (clients[i] == null) {
                    clients[i] = client;
                    break;
                }
            }
            Console.WriteLine($"客户端{client.Client.RemoteEndPoint}已连接");
            // 如果连接数超过最大连接数
            int count = 0;
            for (int i = 0; i < maxClient; i++) {
                if (clients[i] != null) {
                    count++;
                }
            }
            if (count > maxClient) {
                Console.WriteLine("连接数超过最大连接数");
                toAccept = false;
                break;
            }
            // 暂停0.01秒
            System.Threading.Thread.Sleep(10);
        }
    }

    // 接收数据并反馈
    public void ReceiveData() {
        // 接收数据
        while (true) {
            foreach (TcpClient client in clients) {
                if (client == null) {
                    continue;
                }
                // 获取网络
                NetworkStream stream = client.GetStream();
                // 接收数据
                byte[] bytes = new byte[1024];
                int length = stream.Read(bytes, 0, bytes.Length);
                string data = Encoding.ASCII.GetString(bytes, 0, length);
                Console.WriteLine($"接收到来自{client.Client.RemoteEndPoint}的数据:{data}");
                // 发送数据
                byte[] msg = Encoding.ASCII.GetBytes("已收到数据");
                stream.Write(msg, 0, msg.Length);
            }
        }
    }

    // 运行服务器
    public void Run() {
        // 接收客户端连接线程
        Thread acceptThread = new(AcceptClient);
        // 接收数据线程
        Thread receiveThread = new(ReceiveData);

        // 启动线程
        acceptThread.Start();
        receiveThread.Start();
    }
}

class MainClass {
    static void Main() {
        Server server = new();
        server.Run();
    }
}

客户端代码

using System.Net;
using System.Text;
using System.Net.Sockets;

class Client {
    // 客户端
    public TcpClient client;
    // 服务端IP地址
    public string serverIp;
    // 服务端端口
    public int serverPort;

    public Client() {
        client = new TcpClient();
        // 设置服务端IP地址
        serverIp = "18.tcp.cpolar.top";
        // 设置服务端端口
        serverPort = 11925;
    }

    // 连接服务端
    public void ConnectServer() {
        // 连接服务端
        client.Connect(serverIp, serverPort);
        // 输出连接成功
        Console.WriteLine("连接成功");

        // 接收数据
        while (true) {
            byte[] bytes = new byte[1024];
            int length = client.GetStream().Read(bytes, 0, bytes.Length);
            string data = Encoding.UTF8.GetString(bytes, 0, length);
            if (data == "") {
                continue;
            }
            Console.WriteLine("接收到来自服务端的数据:" + data);
        }
    }

    // 运行客户端
    public void Run() {
        // 接收广播消息线程
        Thread connectServerThread = new(() => ConnectServer());
        connectServerThread.Start();
    }
}

class Program {
    static void Main(string[] args) {
        Client client = new();
        client.Run();
    }
}

结语

程序在不同环境可能会有些许问题,本文的编程环境是python 3.9.10和.net8。


http://www.niftyadmin.cn/n/5403596.html

相关文章

【数仓】zookeeper软件安装及集群配置

相关文章 【数仓】基本概念、知识普及、核心技术【数仓】数据分层概念以及相关逻辑【数仓】Hadoop软件安装及使用&#xff08;集群配置&#xff09;【数仓】Hadoop集群配置常用参数说明 一、环境准备 准备3台虚拟机 Hadoop131&#xff1a;192.168.56.131Hadoop132&#xff…

【每日前端面经】2024-03-02

题目来源: 牛客 图片懒加载实现方式 添加页面滚动事件监听&#xff0c;判断图片位置和当前位置来给src赋值 addEventListener("scroll", () > {const image document.querySelector("#image");const offsetTop image.offsetTop;const clientHeight…

机器学习-5

文章目录 前言Numpy库四则运算编程练习 前言 本片将介绍Numpy库中的四则运算。 Numpy库四则运算 Numpy库可以直接进行一些四则运算&#xff0c;快速的处理两个Numpy数组&#xff1a; a np.array([[1,2,3],[4,5,6]]) b np.array([[4,5,6],[1,2,3]])向量与向量之间 1.加法 …

django学习记录06-Ajax的初识

Ajax请求 1.1Ajax请求与get、post请求的区别 form请求&#xff1a;浏览器向网站发送请求时&#xff0c;url和表单的形式提交 GET请求&#xff1a;url方式获取数据POST请求&#xff1a;以表单的形式提交数据 特点: 一次完整的GET或POST请求&#xff0c;会进行一次页面刷新 基于…

用于制作耳机壳的UV树脂耳机壳UV胶价格高不高?

制作耳机壳的UV树脂价格相对于一些其他材料可能会略高&#xff0c;但具体的价格取决于多个因素&#xff0c;如品牌、型号、质量等。一些高端的UV树脂品牌和型号可能会价格较高&#xff0c;但它们也通常具有更好的性能和更广泛的应用范围。 此外&#xff0c;UV树脂的价格也与购买…

Linux:kubernetes(k8s)搭建mater节点(kubeadm,kubectl,kubelet)(2)

安装k8有多种方式如&#xff1a; minikube kubeadm 二进制安装 命令行工具 我这里就使用kubeadm进行安装 环境 3台centos7 master ip &#xff1a;192.168.113.120 2G运存 2内核 node1 ip &#xff1a;192.168.113.121 2G运存 2内核 node2 ip &#xff1a;192.168.1…

第19章-IPv6基础

1. IPv4的缺陷 2. IPv6的优势 3. 地址格式 3.1 格式 3.2 长度 4. 地址书写压缩 4.1 段内前导0压缩 4.2 全0段压缩 4.3 例子1 4.4 例子 5. 网段划分 5.1 前缀 5.2 接口标识符 5.3 前缀长度 5.4 地址规模分类 6. 地址分类 6.1 单播地址 6.2 组播地址 6.3 任播地址 6.4 例子 …

凌特杯,第二届,数字音频传输。simulink matlab

终于比赛进入了尾声&#xff0c;最为指导老师也是非常的激动。接下来进入了论文写作阶段和视频拍摄阶段。 第二届凌特杯规定的硬件是ADI的Pluto&#xff0c;成本在2k以内&#xff0c;能支持MATLAB&#xff0c;它能够流畅的实时播放接收到的音乐数据&#xff0c;并把数据保存成…