【计算机网络通信】计算机之间的局域网通信和互联网通信方法(附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 subprocessclass Server:def __init__(self) -> None:# 创建一个socket对象, 参数表示使用IPV4和TCP协议# IPV6使用AF_INET6, UDP使用SOCK_DGRAMself.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 = 8080self.server_port = 8081# 客户端列表self.clients = {}# 最大连接数self.max_connections = 1# 是否要广播self.to_broadcast = True# 是否要接收客户连接self.to_accept = Truedef 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_socketprint(f"客户端{client_address}已连接")# 如果连接数达到最大连接数, 则不再接受连接if len(self.clients) == self.max_connections:self.to_accept = Falseprint("已达到最大连接数")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 threadingclass Client:def __init__(self):# 创建一个UDP socketself.broadcast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 设置socket的广播选项self.broadcast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)# 创建一个TCP socketself.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 端口号self.broadcast_port = 8080self.server_post = 8081# 服务端地址self.server_ip = Nonedef 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("服务器断开连接")breakdef 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协议的监听器是UDPListenerserver = 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 threadingclass 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 = Truedef 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_socketprint(f"客户端{client_address}已连接")# 如果连接数达到最大连接数, 则不再接受连接if len(self.clients) == self.max_connections:self.to_accept = Falseprint("已达到最大连接数")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 threadingclass Client:def __init__(self):# 创建一个TCP socketself.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 服务端地址self.server_ip = "18.tcp.cpolar.top"# 服务端端口self.server_post = 11925def 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("服务器断开连接")breakdef 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.mzph.cn/news/716249.shtml

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

相关文章

基于Java的超市商品管理系统(Vue.js+SpringBoot)

目录 一、摘要1.1 简介1.2 项目录屏 二、研究内容2.1 数据中心模块2.2 超市区域模块2.3 超市货架模块2.4 商品类型模块2.5 商品档案模块 三、系统设计3.1 用例图3.2 时序图3.3 类图3.4 E-R图 四、系统实现4.1 登录4.2 注册4.3 主页4.4 超市区域管理4.5 超市货架管理4.6 商品类型…

一文了解docker与k8s

随着 k8s 作为容器编排解决方案变得越来越流行&#xff0c;有些人开始拿 Docker 和 k8s 进行对比&#xff0c;不禁问道&#xff1a;Docker 不香吗&#xff1f; k8s 是 kubernetes 的缩写&#xff0c;8 代表中间的八个字符。 其实 Docker 和 k8s 并非直接的竞争对手两者相互依存…

Qt外部调用进程类QProcess的使用

有的时候我们需要在自己程序运行过程中调用其他进程&#xff0c;那么就需要用到QProcess。 首先可以了解一些关于进程的相关知识&#xff1a;线程与进程&#xff0c;你真得理解了吗_进程和线程的区别-CSDN博客 进程是计算机中的程序关于某数据集合上的一次运行活动&#xff0…

Java面试——Redis

优质博文&#xff1a;IT-BLOG-CN 一、Redis 为什么那么快 【1】完全基于内存&#xff0c;绝大部分请求是纯粹的内存操作&#xff0c;非常快速。数据存在内存中。 【2】数据结构简单&#xff0c;对数据操作也简单&#xff0c;Redis中的数据结构是专门进行设计的。 【3】采用单线…

【Vue3】全局切换字体大小

VueUse 先安装VueUse <template><header><div class"left">left</div><div class"center">center</div><div class"right">right</div></header><div><button click"cha…

飞天使-学以致用-devops知识点4-SpringBoot项目CICD实现(实验失败,了解大概流程)

文章目录 代码准备创建jenkins 任务测试推送使用项目里面的jenkinsfile 进行升级操作 文字版本流程项目构建 代码准备 推送代码到gitlab 代码去叩叮狼教育找 k8s 创建jenkins 任务 创建一个k8s-cicd-demo 流水线任务 将jenkins 里面构建时候的地址还有token&#xff0c; 给到…

azure devops工具实践分析

对azure devops此工具的功能深挖&#xff0c;结合jira的使用经验的分析 1、在backlog的功能描述&#xff0c;可理解为需求项&#xff0c;这里包括了bug&#xff0c;从开发的角度修复bug也是个工作项&#xff0c;所以需求的范围是真正的需求&#xff08;开发接收到的已经确认的…

【PDF技巧】网上下载的pdf文件怎么才能编辑

不知道大家有没有遇到过网上下载的PDF文件不能编辑的情况&#xff0c;今天我们来详细了解一下导致无法编辑的原因即解决方法有哪些。 第一种原因&#xff1a;PDF文件中的内容是否是图片&#xff0c;如果确认是图片文件&#xff0c;那么我们想要编辑&#xff0c;就可以先使用PD…

分享经典、现代以及前沿软件工程课程

https://www.icourse163.org/course/PKU-1003177002 随着信息技术的发展&#xff0c;软件已经深入到人类社会生产和生活的各个方面。软件工程是将工程化的方法运用到软件的开发、运行和维护之中&#xff0c;以达到提高软件质量&#xff0c;降低开发成本的目的。软件工程已经成为…

字符函数和字符串函数(下)

个人主页&#xff08;找往期文章包括但不限于本期文章中不懂的知识点&#xff09;&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 目录 strncpy函数的使用 函数原型&#xff1a; strncpy的使用 strncat函数的使用 函数原型&#xff1a; strncat的使用 strncmp函数的使用 函…

Vue3快速上手(十六)Vue3路由传参大全

Vue3路由传参 一、传参的多种方式 1.1 拼接方式 这种方式适合传递单个参数的情况&#xff0c;比如点击查看详情&#xff0c;传个id这样的场景 传参&#xff1a; <RouterLink to"/person?id1" active-class"active">person</RouterLink> …

Unity - 相机画面为黑白效果

一、 在Hierarchy中创建一个Global Volume,并设置它为局部作用 二、 将场景出现的作用域范围缩小至相机所在位置&#xff0c;将相机包含即可。 三、添加覆盖组件Color Adjustments,并将Saturation直接拉为-100 。 此时&#xff0c;相机拍摄画面为黑白&#xff0c;场景视图中…

1、Linux-安装

一、Linux和Windows的一些区别 1、Linux严格区分大小写——【Windows创建文件夹时不区分大小写】 2、Linux中所有内容都以文件形式存储&#xff0c;包括硬件 3、Linux不靠拓展名区分文件类型&#xff0c;而是可以通过读取文件开头的一些字节来区分。 但是在实际使用中一般要…

MYSQL---日志

1.日志的概述 日志是MySQL数据库的重要组成部分。日志文件中记录着MySQL数据库运行期间发生的变化&#xff1b;也就是说用来记录MySQL数据库的客户端连接状况、SQL语句的执行情况和错误信息等。当数据库遭到意外的损坏时&#xff0c;可以通过日志查看文件出错的原因&#xff0…

C语言中的字符魔法:大小写转换的艺术

引言 在C语言的世界里&#xff0c;字符处理是一项基础且重要的任务。字符作为编程中最基本的元素之一&#xff0c;承担着信息展示、数据交互等多重角色。特别是在处理文本信息时&#xff0c;字符的转换和识别显得尤为重要。大小写字母的转换就是其中一个常见的需求&#xff0c…

电子电气架构——汽车DoIP诊断通信建立流程

电子电气架构——汽车DoIP诊断通信建立流程 我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 人们会在生活中不断攻击你。他们的主要武器是向你灌输对自己的怀疑:你的价值、你的能力、你的潜力。他们往往会…

4款塞纸条盲盒交友源码,可以对接公众号

一元盲盒交友源码/脱单盲盒源码/交友盲盒/恋爱盲盒公众号版 可以对接自己支付&#xff0c;全部自定义 没有任何bug版本&#xff0c;已经测试完全可以 免费源码&#xff0c;不包搭建指导 源码下载地址专业知识分享社区-专业知识笔记免费分享 (chaobiji.cn)

Globeland30米分辨率土地利用数据的下载

全国地理信息资源目录服务系统 先注册一下&#xff0c;登录 点击查询

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

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

使用最新Hal库实现USART中断收发功能(STM32F4xx)

目录 概述 1 认识STM32F4XX的USART 1.1 USART 功能说明 1.2 USART的中断 1.3 USART 模式配置 1.4 USART的寄存器 2 使用STM32CubeMX 生成工程 2.1 配置参数 2.2 生成工程代码 3 实现软件功能 3.1 软件功能介绍 3.2 认识USART Hal库 3.2.1 初始化函数组 3.2.2 发送…