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

文章目录

  • 前言
  • 一、局域网通信
    • 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地址。

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库获取子网掩码

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

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

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

3)进行广播

# 创建一个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))

接收广播

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和一个端口上,之后监听就可以了。如果需要发送消息就对连接上的客户端发消息,需要接收就接收。

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'))

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

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源码

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

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()

下面是客户端代码

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()

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

    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

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源码

服务端代码

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()

客户端代码

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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/424503.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中国电子学会2019年12月份青少年软件编程Scratch图形化等级考试试卷四级真题。

第 1 题 【 单选题 】 1.以下模块&#xff0c;可以“说”出“我喜欢Apple”的是&#xff1f; A&#xff1a; B&#xff1a; C&#xff1a; D&#xff1a; 2.某学校为教师外出提供车辆服务&#xff0c;当外出人数小于5人时&#xff0c;派轿车&#xff1b;当外出人数为5至7人的话…

初阶数据结构:二叉树

目录 1. 树的相关概念1.1 简述&#xff1a;树1.2 树的概念补充 2. 二叉树2.1 二叉树的概念2.2 二叉树的性质2.3 二叉树的存储结构与堆2.3.1 存储结构2.3.2 堆的概念2.3.3 堆的实现2.3.3.1 堆的向上调整法2.3.3.2 堆的向下调整算法2.3.3.3 堆的实现 1. 树的相关概念 1.1 简述&a…

链表基础知识详解(非常详细简单易懂)

概述&#xff1a; 链表作为 C 语言中一种基础的数据结构&#xff0c;在平时写程序的时候用的并不多&#xff0c;但在操作系统里面使用的非常多。不管是RTOS还是Linux等使用非常广泛&#xff0c;所以必须要搞懂链表&#xff0c;链表分为单向链表和双向链表&#xff0c;单向链表很…

[Linux]如何理解kernel、shell、bash

文章目录 概念总览kernelshell&bash 概念总览 内核(kernel) &#xff0c;外壳(shell) &#xff0c;bash kernel kernel是指操作系统中的核心部分&#xff0c;用户一般是不能直接使用kernel的。它主要负责管理硬件资源和提供系统服务&#xff0c;如内存管理、进程管理、文件…

国内chatgpt写作软件,chatgpt国内使用

随着人工智能技术的不断发展&#xff0c;国内涌现出了一些基于ChatGPT模型的写作软件&#xff0c;这些软件不仅能够实现智能化的文章写作&#xff0c;还支持批量生成各种类型的文章。本文将深入探讨国内ChatGPT写作软件&#xff0c;以及它们在批量文章创作方面的应用与优势。 C…

如何使用Docker搭建StackEdit编辑器并结合内网穿透实现远程办公

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…

K线实战分析系列之十七:三法形态——接连犹豫后再次坚定

K线实战分析系列之十七&#xff1a;三法形态——接连犹豫后再次坚定 一、三法形态二、总结三法形态 一、三法形态 前后两根长K线中间夹了三根短小的K线 二、总结三法形态 中间的几根小阴线数量限制没有那么严苛中间小K线的颜色不一定是依次下降的小阴线或小阳线&#xff0c;也…

NOC2023软件创意编程(学而思赛道)python小高组复赛真题

目录 下载原文档打印做题: 软件创意编程 一、参赛范围 1.参赛组别:小学低年级组(1-3 年级)、小学高年级组(4-6 年级)、初中组。 2.参赛人数:1 人。 3.指导教师:1 人(可空缺)。 4.每人限参加 1 个赛项。 组别确定:以地方教育行政主管部门(教委、教育厅、教育局) 认…

【C++】vector的使用和模拟实现(超级详解!!!!)

文章目录 前言1.vector的介绍及使用1.1 vector的介绍1.2 vector的使用1.2.1 vector的定义1.2.2 vector iterator 的使用1.2.3 vector 空间增长问题1.2.3 vector 增删查改1.2.4 vector 迭代器失效问题。&#xff08;重点!!!!!!&#xff09;1.2.5 vector 在OJ中有关的练习题 2.ve…

朱维群将出席用碳不排碳碳中和顶层科技路线设计开发

演讲嘉宾&#xff1a;朱维群 演讲题目&#xff1a;“用碳不排碳”碳中和顶层科技路线设计开发 简介 姓名&#xff1a;朱维群 性别&#xff1a;男 出生日期&#xff1a;1961-09-09 职称&#xff1a;教授 1998年毕业于大连理工大学精细化工国家重点实验室精细化工专业&…

AWTK 开源串口屏开发(11) - 天气预报

# AWTK 开源串口屏开发 - 天气预报 天气预报是一个很常用的功能&#xff0c;在很多设备上都有这个功能。实现天气预报的功能&#xff0c;不能说很难但是也绝不简单&#xff0c;首先需要从网上获取数据&#xff0c;再解析数据&#xff0c;最后更新到界面上。 在 AWTK 串口屏中…

探索那些能唤起情感共鸣的壁纸

1、方小童在线工具集 网址&#xff1a; 方小童 该网站是一款在线工具集合的网站&#xff0c;目前包含PDF文件在线转换、随机生成美女图片、精美壁纸、电子书搜索等功能&#xff0c;喜欢的可以赶紧去试试&#xff01;

基于Beego 1.12.3的简单website实现

参考 用Beego开发web应用 https://www.cnblogs.com/zhangweizhong/p/10919672.htmlBeego官网 Homepage - beego: simple & powerful Go app frameworkbuild-web-application-with-golang https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/pr…

概率基础——多元正态分布

概率基础——多元正态分布 介绍 多元正态分布是统计学中一种重要的多维概率分布&#xff0c;描述了多个随机变量的联合分布。在多元正态分布中&#xff0c;每个随机变量都服从正态分布&#xff0c;且不同随机变量之间可能存在相关性。本文将以二元标准正态分布为例&#xff0…

PVLAN组网实验

一&#xff0c;PVLAN类型 主VLAN 主VLAN可以由多个辅助私用VLAN组成&#xff0c;而这些辅VLAN与主VLAN属于同一子网。 辅助VLAN ① 团体VLAN&#xff1a;如果某个端口属于团体VLAN&#xff0c;那么它就不仅能够与相同团体VLAN中的其他端口进行通信&#xff0c;而且还能够与…

【5G 接口协议】GTP-U协议介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

HTML~

HTML HTML是一门语言&#xff0c;所有的网页都是用HTML这门语言编写出来的HTML(HyperText Markup Language):超文本标记语言 超文本:超越了文本的限制&#xff0c;比普通文本更强大。除了文字信息&#xff0c;还可以定义图片、音频、视频等内容 标记语言:由标签构成的语言 …

SpringBoot源码解读与原理分析(三十八)SpringBoot整合WebFlux(一)WebFlux的自动装配

文章目录 前言第13章 SpringBoot整合WebFlux13.1 响应式编程与Reactor13.1.1 命令式与响应式13.1.2 异步非阻塞13.1.3 观察者模式13.1.4 响应性13.1.5 响应式流13.1.6 背压13.1.7 Reactor13.1.7.1 Publisher13.1.7.2 Subscriber13.1.7.3 Subscription13.1.7.4 Processor13.1.7.…

Python爬虫——解析常用三大方式之Xpath

目录 Xpath 安装xpath 安装lxml库 导入lxml库 解析本地文件 etree.parse&#xff08;&#xff09; 解析服务器响应文件 etree.HTML() xpath基本语法 小案例&#xff1a;获取百度首页的百度一下 大案例&#xff1a;爬取站长素材图片 总结 Xpath 安装xpath 首先要学会安…

大模型(LLM)的量化技术Quantization原理学习

在自然语言处理领域&#xff0c;大型语言模型&#xff08;LLM&#xff09;在自然语言处理领域的应用越来越广泛。然而&#xff0c;随着模型规模的增大&#xff0c;计算和存储资源的需求也急剧增加。为了降低计算和存储开销&#xff0c;同时保持模型的性能&#xff0c;LLM大模型…