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

news2024/11/18 14:02:48

文章目录

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

腾讯云服务器CVM_云主机_云计算服务器_弹性云服务器

腾讯云服务器CVM提供安全可靠的弹性计算服务&#xff0c;腾讯云明星级云服务器&#xff0c;弹性计算实时扩展或缩减计算资源&#xff0c;支持包年包月、按量计费和竞价实例计费模式&#xff0c;CVM提供多种CPU、内存、硬盘和带宽可以灵活调整的实例规格&#xff0c;提供9个9的数…

研发日记,MatlabSimulink开箱报告(九)——Simulink Test模块

文章目录 前言 Simulink Test模块 静态测试 动态测试 逻辑测试 前言 见《开箱报告&#xff0c;Simulink Toolbox库模块使用指南&#xff08;四&#xff09;——S-Fuction模块》 见《开箱报告&#xff0c;Simulink Toolbox库模块使用指南&#xff08;五&#xff09;——S-F…

Canvs的js库:Fabric.js简单强大,用于绘制各种图形

Fabric.js是一个用于创建交互式的HTML5 Canvas应用程序的JavaScript库。它提供了一个简单而强大的API&#xff0c;用于在Web浏览器中绘制和操作图形对象。Fabric.js可以用于创建各种图形应用程序&#xff0c;例如绘图编辑器、图像编辑器、流程图、地图和数据可视化等。 官网文…

初识C语言—常见关键字

变量的命名最好有意义 名字必须是字母&#xff0c;数字&#xff0c;下划线组成&#xff0c;不能有特殊字符&#xff0c;同时不能以数字开头 变量名不能是关键字 typedef---类型定义&#xff0c;类型重命名 #include <stdio.h>typedef unsigned int uint; //将unsigne…

链表类型题目

文章目录 简介链表的常用技巧两数相加原理代码代码|| 两两交换链表中的节点代码原理 重排链表(重要)原理代码 合并 K 个升序链表代码递归代码 K 个一组翻转链表原理代码 简介 大家好,这里是jiantaoyab,这篇文章给大家带来的是链表相关的题目练习和解析,希望大家能相互讨论进步 …

LCR 152. 验证二叉搜索树的后序遍历序列

解题思路&#xff1a; 分治 public class Solution {// 验证给定的后序遍历数组是否可以来自一个二叉搜索树(BST)public boolean verifyTreeOrder(int[] postorder) {// 调用递归方法来验证整个postorder数组return verify(postorder, 0, postorder.length - 1);}// 递归方法&…

vue2+若依框架plus交互 路由介绍

本周及寒假 参加了校企合作的工程过程管理&#xff0c;和学长学姐一起写项目&#xff0c;之前学了vue也没有应用&#xff0c;然后对框架很多组件的用法不太了解&#xff0c;前期耽误了一些时间。 框架模块 首先是框架模块的介绍 api存了一些系统管理及发送请求的方法 例如p…

C习题002:澡堂洗澡

问题 输入样例 在这里给出一组输入。例如&#xff1a; 2 5 1 3 3 2 3 3 输出样例 在这里给出相应的输出。例如&#xff1a; No代码长度限制 16 KB 时间限制 400 ms 内存限制 64 MB 栈限制 8192 KB 代码 #include<stdio.h> int main() {int N,W,s,t,p;int arr_s[…

Vue+SpringBoot打造大学计算机课程管理平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 实验课程档案模块2.2 实验资源模块2.3 学生实验模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 实验课程档案表3.2.2 实验资源表3.2.3 学生实验表 四、系统展示五、核心代码5.1 一键生成实验5.2 提交实验5.3 批阅实…

Redis安全加固策略:服务账号管理 开启redis密码认证 开启防护模式

Redis安全加固策略&#xff1a;服务账号管理 & 开启redis密码认证 & 开启防护模式 1.1 服务账号管理1.1.1 检测方法1.1.2 加固参考配置操作 1.2 开启redis密码认证1.2.1 检测方法1.2.2 加固参考配置操作 1.3 开启防护模式1.3.1 检测方法1.3.2 加固参考配置操作 &#x…

静态时序分析:SDC约束命令set_case_analysis详解

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 目录 指定值 指定端口/引脚列表 简单使用 set_case_analysis命令用于对电路进行特定模式的设定&#xff0c;例如对于一个工作在正常模式下的芯片&#xff0c;…

Springboot 项目读取yaml的配置文件信息给静态方法使用,以及通过配置 ResourceBundle 类读取config.properties

读取yaml 的配置文件 配置文件信息 iot_saas_tenement:user_id: 7........8d9bprivate_key: MII.......qQbj_url: http://4.....5:8088project_name: iot_s.......rojectdevice_name: te.....ice 创建一个类 ProxyProperties 读取配置文件信息&#xff0c;并对外提供get方法 …

零基础如何快速入门伦敦金交易

伦敦金交易是金融市场中备受关注的一种投资方式。对于想要学习如何炒伦敦金并快速开始交易的人来说&#xff0c;本文将为您提供一份全面而详细的指南。无论您是初学者还是有经验的交易者&#xff0c;本文都将帮助您了解伦敦金交易的基本知识&#xff0c;并提供一些实用的技巧和…

linux环境安装cuda toolkit

1 全新安装 如果环境中没安装过cuda版本&#xff0c; 这种情况下比较简单。 直接在https://developer.nvidia.com/cuda-toolkit-archive选择对应版本下载安装即可。 如下为安装cuda toolkit 11.8. 2 环境中已经存在其他版本 这种情况下比较复杂一些。 首先要确认最高支持的…

Grid-Based Continuous Normal Representation for Anomaly Detection 论文阅读

Grid-Based Continuous Normal Representation for Anomaly Detection 论文阅读 摘要简介方法3.1 Normal Representation3.2 Feature Refinement3.3 Training and Inference 4 实验结果5 总结 文章信息&#xff1a; 原文链接&#xff1a;https://arxiv.org/abs/2402.18293 源码…

06 OpenCV增加图像的对比度

文章目录 理论API代码 理论 图像变换可以看作如下&#xff1a; 像素变换 – 点操作邻域操作 – 区域 调整图像亮度和对比度属于像素变换-点操作 API saturate_cast(value)确保值大小范围为0~255之间Mat.at(y,x)[index]value 给每个像素点每个通道赋值 代码 #include <…

学习:吴恩达:什么是神经元?神经网络如何工作?

学习-吴恩达《AI for everyone》2019 深度学习非技术解释 第2部分 可选.zh_哔哩哔哩_bilibili 深度学习Deep learning 人工神经网络Artificial Neural network 什么是神经网络&#xff1f; 只有一个神经元 4个神经元的神经网络 神经网路的绝妙之处 神经网路的绝妙之处就在…

matplotlib从起点出发(14)_Tutorial_imshow_origin_extent

0 总述 imshow()允许你将图像&#xff08;将进行颜色映射——基于norm和cmap——的2D数组或将按原样使用的3D RGB(A)的数组&#xff09;渲染到数据空间中的矩形区域。最终渲染中图像的方向由原点和范围关键字参数&#xff08;以及生成的AxesImage实例上的属性&#xff09;和Ax…

GVIM常见命令

一、模式之间的切换 二、退出保存 &#xff1a;q 不保存就退出vim &#xff1a;w 保存但不退出vim &#xff1a;wq 保存并退出vim 三、打开侧边栏 如下所示&#xff1a; 在命令模式下输入vt打开侧边栏目录&#xff0c;输入r更新目录 四、光标的移动 不仅是括号&#xff0c;b…

19. 学习人工智能如何从阅读论文中获取一手信息,并推荐一些技术论文

本文为 「茶桁的 AI 秘籍 - BI 篇 第 19 篇」 文章目录 Hi&#xff0c;你好。我是茶桁。 上节课给大家预告了&#xff0c;今天这节课咱们来看一篇论文。我们之前几节课中讲解的内容其实是在一些论文里面有使用到的。 我们先看一下论文的内容&#xff0c;讲讲 ALS。 就像我上节…