• Java:使用Socket实现内网穿透---本地通信&跨网络通信(可给他人或自己发送文件、信息)


    目录

    每日一讲

    1.Socket简介

    1.1Socket的几个相关概念 

    2.内网穿透原理 

    2.1情景再现

    2.2穿透初现

    3.本地主机,实现同网络通信

    3.1同网络接发消息

    3.11两端信息单次接发

    3.12两端能多次发送/接收信息

    3.13服务端客户端实现接发信息同步

    3.2同网络接发文件

    3.21服务端传文件到客户端

    4.使用阿里云服务器,实现跨网络通信

    4.1 初始配置

    4.11下载frp文件

    4.12服务端和客户端配置

    4.13内网穿透进行


    每日一讲

    学习不是一蹴而就的,日积月累比一天速成更有效果!

    1.Socket简介

    用通俗的话来讲,Socket是由IP地址+端口所组成的,是支持TCP/IP协议的网络通信的最小操作单元。形象来说IP地址就是某地的一栋楼,端口则是这栋楼中具体的一个房间。

    1.1Socket的几个相关概念 

    • TCP:传输控制协议,是面向连接的、可靠的、基于字节流的通信协议。能够保证数据传输的可靠性以及传输的效率。
    • IP协议:网络通信协议,负责路由数据包,使得数据能够在不同的设备间进行有效的传输。
    • 路由:通过互联网的网络把信息从源地址传输到目的地址的一种活动。

    2.内网穿透原理 

    网络通信的两种方式:TCP和UDP,本文使用TCP进行通信,也就是Socket技术,因为其较为可靠稳定。

    内网穿透原理:外网通过Socket发送请求,也就是找到内网的IP地址以及端口,一旦内容匹配成功,并且接受了你的请求,那么此时就可以实现网络通信。

    2.1情景再现

    假设服务端的小林:拥有一台电脑,其IP地址是192.168.xxx,并且拥有一个端口5900,此时客户端小郑发送了请求,想要连上小林电脑上的192.168.xxx的IP地址5900的端口。

    那么此时,服务端小林接受了客户端小郑的请求,那么他们就可以实现网络通信,互发信息和文件就不成问题勒!

    2.2穿透初现

    前面我们说到,Socket通信通常有服务端和客户端,接下来小郑就用简单代码带大家理解一下何为内网穿透。

    首先我们先看下如何用代码实现最基本的网络通信。

    服务端代码

    1. public static void main(String[] args) throws IOException {
    2. ServerSocket serverSocket=new ServerSocket(5900);
    3. //可以利用socket进行通信
    4. Socket socket=serverSocket.accept();
    5. System.out.println("开启了5900端口");
    6. }

     可以看到服务端代码中开放了一个5900端口,当有人在同一网络下使用我们的IP地址和端口进行连接时,我们进行接受,就可以实现网络通信。

    客户端代码

    1. public static void main(String[] args) throws IOException {
    2. Socket socket=new Socket("localhost",5900);
    3. }

    客户端则利用Socket连接了IP地址和端口,如果你想要连别人的电脑,就需要知道别人电脑的IP地址,这里的localhost是代表自己主机的IP地址。 

    此时,我们需要先运行服务端,再运行客户端。

     可以看到,当我们先运行服务端代码后,客户端不运行,服务端还没有任何反应;但是当我们将客户端也进行运行后,可以看到控制台打印了一句话:“开启了5900端口”,这是因为服务端accept(接受)了客户端发来的请求。

    当然这几句代码还不足以进行两端互发信息、传文件等操作,所以接下来我们就需要完善我们的代码咯。

    3.本地主机,实现同网络通信

    为了直观的感受网络通信接发消息、文件等操作,我们先使用本地主机进行尝试,有条件的小伙伴也可以和其他小伙伴一起尝试。

    3.1同网络接发消息

    为了实现同网络接发信息,我们就需要用到前面文章所讲到的IO流知识了,需要请看文章:Java:I/O流之输入流和输出流、字节流和字符流详解,附带代码-CSDN博客

    3.11两端信息单次接发

    服务端代码

    1. public static void main(String[] args) throws IOException {
    2. ServerSocket serverSocket=new ServerSocket(5900);
    3. System.out.println("开启了5900端口");
    4. //可以利用socket进行通信
    5. Socket socket=serverSocket.accept();
    6. //只要有人连接进来就发消息给客户端:欢迎进入xxx的聊天室
    7. OutputStream outputStream=socket.getOutputStream();
    8. //使用包装流,因为getOutputStream()是字节流,但是发送信息是要用字符流
    9. OutputStreamWriter outputStreamWriter=new OutputStreamWriter(outputStream);
    10. //再次使用包装流,为了使客户端能够接收到发送的信息
    11. PrintWriter printWriter=new PrintWriter(outputStreamWriter);
    12. printWriter.write("欢迎进入zmh聊天室");
    13. printWriter.flush();//刷新缓存,不然信息太少,数据还留在缓存区,发不出来
    14. printWriter.close();
    15. }

    客户端代码

    1. public static void main(String[] args) throws IOException {
    2. Socket socket=new Socket("localhost",5900);
    3. //读入
    4. InputStream inputStream=socket.getInputStream();
    5. //这边也是使用包装流
    6. InputStreamReader inputStreamReader=new InputStreamReader(inputStream);
    7. //再次使用包装流读取信息
    8. BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
    9. //读一行信息,也就是服务端传来的信息
    10. String message =bufferedReader.readLine();
    11. System.out.println("收到消息:"+message);
    12. bufferedReader.close();
    13. }

    代码中使用了包装流的原因是因为,Socket是基于字节流的。当我们想要获取socket中的内容时,只能够使用字节流,但是我们发送文件需要用的是字符流,所以我们需要将其包装成字符流进行信息发送。

    运行结果

     通过结果我们可以发现,此时服务端只能发送一次信息,客户端也只能接收一次信息,并且不能够手动输入,所以我们就需要完善我们此时的代码。

    3.12两端能多次发送/接收信息

    想要无限多次的接收发信息,我们就需要使用到循环这一技术。

    服务端代码

    1. public static void main(String[] args) throws IOException {
    2. ServerSocket serverSocket=new ServerSocket(5900);
    3. System.out.println("正在开启5900端口……");
    4. //可以利用socket进行通信
    5. Socket socket=serverSocket.accept();
    6. System.out.println("有新用户连入……");
    7. OutputStream outputStream=socket.getOutputStream();
    8. //使用包装流,因为getOutputStream()是字节流,但是发送信息是要用字符流
    9. OutputStreamWriter outputStreamWriter=new OutputStreamWriter(outputStream);
    10. //再次使用包装流,为了使客户端能够接收到发送的信息
    11. PrintWriter printWriter=new PrintWriter(outputStreamWriter);
    12. Scanner input=new Scanner(System.in);
    13. //使用循环为了让服务端能够给客户端发送多条信息
    14. while(true)
    15. {
    16. System.out.print("请输入你要发送的信息:");
    17. String message= input.nextLine();
    18. printWriter.println(message);
    19. printWriter.flush();//刷新缓存,不然信息太少,数据还留在缓存区,发不出来
    20. }
    21. }

    客户端代码

    1. public static void main(String[] args) throws IOException {
    2. Socket socket=new Socket("localhost",5900);
    3. //读入
    4. InputStream inputStream=socket.getInputStream();
    5. //这边也是使用包装流
    6. InputStreamReader inputStreamReader=new InputStreamReader(inputStream);
    7. //再次使用包装流读取信息
    8. BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
    9. while(true) {
    10. //读一行信息,也就是服务端传来的信息
    11. String message =bufferedReader.readLine();
    12. System.out.println("收到消息:"+message);
    13. }
    14. }

    看到就这些代码,哥们不禁发出一句:简单~

    但是,不能总是你服务端发信息,我客户端不能发呀,所以我们接下来就进行代码完善。

    3.13服务端客户端实现接发信息同步

    进行到这里,我们则会接触一个关键的技术点:多线程

    看到这,哥们也只能说一句简单~,多线程不过是为了同步完成多个任务,能够从软件或硬件中实现多个线程并发执行的一个技术。

    举个例子:哥们现在是大老板,现在要在码头找人卸下十吨货,请问哥们:十个人卸得快还是一个人卸的快?哥们又不是蝙蝠侠,包是卸不过十个人呀。

    服务端代码

    1. public static void main(String[] args) throws IOException {
    2. ServerSocket serverSocket=new ServerSocket(5900);
    3. System.out.println("正在开启5900端口……");
    4. //可以利用socket进行通信
    5. Socket socket=serverSocket.accept();
    6. //创建Socket类对象传递
    7. GetMessageThread getMessageThread=new GetMessageThread(socket);
    8. getMessageThread.start();
    9. System.out.println("有新用户连入……");
    10. OutputStream outputStream=socket.getOutputStream();
    11. //使用包装流,因为getOutputStream()是字节流,但是发送信息是要用字符流
    12. OutputStreamWriter outputStreamWriter=new OutputStreamWriter(outputStream);
    13. //再次使用包装流,为了使客户端能够接收到发送的信息
    14. PrintWriter printWriter=new PrintWriter(outputStreamWriter);
    15. Scanner input=new Scanner(System.in);
    16. //使用循环为了让服务端能够给客户端发送多条信息
    17. while(true)
    18. {
    19. System.out.println("请输入你要发送的信息:");
    20. String message= input.nextLine();
    21. printWriter.println(message);
    22. printWriter.flush();//刷新缓存,不然信息太少,数据还留在缓存区,发不出来
    23. }
    24. }
    25. }
    26. class GetMessageThread extends Thread{
    27. Socket socket;
    28. public GetMessageThread(Socket socket){
    29. this.socket=socket;
    30. }
    31. public void run(){
    32. try{
    33. InputStream inputStream=socket.getInputStream();
    34. InputStreamReader inputStreamReader=new InputStreamReader(inputStream);
    35. BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
    36. while(true) {
    37. String message =bufferedReader.readLine();
    38. System.out.println("收到客户端消息:"+message);
    39. }
    40. } catch (IOException e) {
    41. throw new RuntimeException(e);
    42. }
    43. }
    44. }

    客户端代码

    1. public class Client {
    2. public static void main(String[] args) throws IOException {
    3. Socket socket=new Socket("localhost",5900);//用于连接某一主机的5900端口
    4. //创建对象,把socket传递过去
    5. SendMessageThread sendMessageThread=new SendMessageThread(socket);
    6. sendMessageThread.start();
    7. //读入
    8. InputStream inputStream=socket.getInputStream();
    9. // DataInputStream dataInputStream=new DataInputStream(inputStream);
    10. //这边也是使用包装流
    11. InputStreamReader inputStreamReader=new InputStreamReader(inputStream);
    12. //再次使用包装流读取信息
    13. BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
    14. while(true) {
    15. //读一行信息,也就是服务端传来的信息
    16. String message =bufferedReader.readLine();
    17. // String message=dataInputStream.readUTF();
    18. System.out.println("收到服务端消息:"+message);
    19. }
    20. }
    21. }
    22. class SendMessageThread extends Thread{
    23. Socket socket;
    24. //创建一个带参的构造函数
    25. public SendMessageThread(Socket socket)
    26. {
    27. this.socket=socket;
    28. }
    29. public void run(){
    30. Scanner input =new Scanner(System.in);
    31. OutputStream outputStream= null;
    32. try {
    33. outputStream = socket.getOutputStream();
    34. } catch (IOException e) {
    35. throw new RuntimeException(e);
    36. }
    37. OutputStreamWriter outputStreamWriter=new OutputStreamWriter(outputStream);
    38. PrintWriter printWriter=new PrintWriter(outputStreamWriter);
    39. while(true){
    40. System.out.println("请输入你想要发送的信息:");
    41. String message=input.nextLine();
    42. printWriter.println(message);
    43. printWriter.flush();
    44. }
    45. }
    46. }

    运行结果

    我们能够看到,这个时候无论是客户端还是服务端都能够接发信息了。

    至于为什么要使用多线程,哥们可以在main方法中同时输入两个while(true)尝试一下,根本走不通呀! 

    3.2同网络接发文件

    实际上发送文件和发送信息大同小异,区别在于:发送信息是字符流,所以我们需要用到包装流;而发送文件是字节流,不需要进行包装了。

    3.21服务端传文件到客户端

    服务端代码

    1. public static void main(String[] args) throws IOException {
    2. ServerSocket serverSocket=new ServerSocket(6666);
    3. //接收连接
    4. Socket socket=serverSocket.accept();
    5. //发送文件使用输出流
    6. FileInputStream fileInputStream=new FileInputStream("D:/java/Test/内网穿透.pptx");
    7. byte buff[]=new byte[1024];
    8. int ch=0;
    9. OutputStream outputStream = socket.getOutputStream();
    10. while((ch=fileInputStream.read(buff))!=-1)
    11. {
    12. outputStream.write(buff,0,ch);
    13. }
    14. fileInputStream.close();
    15. outputStream.close();
    16. socket.close();
    17. }

    客户端代码

    1. public static void main(String[] args) throws IOException {
    2. Socket socket=new Socket("localhost",6666);
    3. InputStream inputStream = socket.getInputStream();
    4. FileOutputStream fileOutputStream =new FileOutputStream("D:/桌面/hello.pptx");
    5. byte buff[]=new byte[1024];
    6. int ch=0;
    7. while((ch=inputStream.read(buff))!=-1)
    8. {
    9. fileOutputStream.write(buff,0,ch);
    10. }
    11. fileOutputStream.close();
    12. inputStream.close();
    13. socket.close();
    14. }

    运行结果

    可以看到,文件被顺利的收发勒!

    好勒,看到这里大家估计对于同网络通信有了一定的理解,接下来我们讲下跨网络通信!

    4.使用阿里云服务器,实现跨网络通信

    费劲千辛万苦,终于实现了这一步,原本很简单的一部,但是因为自己电脑存在问题,配置了好久,才成功。如果电脑是win11家庭版的,请先参看其他博主的这篇文章进行配置win11家庭版安装远程桌面服务(使用RDPWra解决windows家庭版无法远程桌面问题)_rdpwrap win11-CSDN博客

    4.1 初始配置

    在进行阿里云服务器跨网络之前,我们需要下载frp文件。所谓frp是专注于内网穿透的高性能的反向代理应用,支持TCP、UDP、HTTP、HTTPS等多种协议。

    4.11下载frp文件

    我们可以到github中进行下载:https://github.com/fatedier/frp/releases?page=3

    我下载的版本:图中标注版本,必须下载一个linux和一个windows版本的。

     Linux版本:这个需要下载在secureCRT(支持SSH的终端仿真程序,Windows下登录的Linux服务器主机的软件),我们可以使用命令 wget 下载网址(以下是我的版本,大家也可以选择不同的版本)

    wget https://github.com/fatedier/frp/releases/download/v0.46.1/frp_0.46.1_linux_amd64.tar.gz

    widows版本:这个版本我们需要格外注意,因为通常我们的电脑会自动杀掉里面的frpc.exe文件,所以需要按照以下步骤操作:

    1.在电脑中新建一个文件夹,将下载的压缩包放入其中(切记,此时先不要解压缩!!!)

    2.进入安全中心-->病毒和防护-->病毒的防护设置-->排除项添加刚刚新建的文件夹

     

    3.进入安全中心-->病毒和防护-->病毒的防护设置-->排除项-->添加刚刚新建的文件夹

    4.此时再回到文件夹中解压缩刚刚的压缩包

    4.12服务端和客户端配置

    服务端:配置frps.ini文件--使用命令 vi frps.ini

    img

    但是这个通常不用修改,保持原样

    客户端配置:在本地打开文件夹中的frpc.ini文件

    [common]
    server_addr = 阿里云服务器公网IP
    server_port = 7000

    [tcp]
    type = tcp
    local_ip = 192.168.0.104
    local_port = 6666
    remote_port = 6000

     进行到这里,我们还需要在阿里云服务器上建立一个安全组(开放所有端口)

    img

     至此,文件的配置就完成了,我们就可以进行内网穿透模拟了!!!

    4.13内网穿透进行

    我们可以使用章节3.21中的代码进行实验,因为我自己实验,客户端和服务端代码都在我这里运行,小伙伴和可以找你的好基友一起进行实验。

    基友A

    1.需要到阿里云启动frps--使用命令 ./frps

     2.启动前面3.21节的代码Send.java,但是要将代码稍微修改一下。

    1. ServerSocket serverSocket=new ServerSocket(6666);
    2. //该端口6666要和4.12中的local_port一致

    3.启动本地目录下的frpc.exe(不可以直接启动)两种启动方式

    • 本地cmd运行

    找到文件夹所在目录--输入cmd

    然后输入命令进行运行:frpc.exe -c frpc.ini

    • 配置start.bat文件双击运行

    在frp目录下新建一个文件start.bat,然后使用记事本进行编辑保存。编辑内容:

    @echo off
    :home
    frpc -c frpc.ini
    goto home

    双击运行:

    img

    基友B 

     这位基友的活就相当简单咯,只需要运行前面的客户端代码Client.java,也需要稍微先修改一下代码。(谨记,先A再B,基友A操作完基友B再进行操作)

    1. Socket socket = new Socket("阿里云服务器公网ip地址", 6000);
    2. //IP地址指的是阿里云服务器的地址,要和第4.12中的server_addr一致;端口6000要和第3步的remote_port一致

    运行结果

    基友B的桌面上会出现基友A传来的文件

    最后,到这里就大功告成了!!!呼,其实到这里我也想说一下,学习是一个很需要耐心的东西。这篇文章我前前后后也花了一周的时间,因为解决问题很多时候会消耗我们的耐心,看似不难,但是小小的失误也能导致极大的问题。

    希望这篇文章能够同样帮助到你,保持耐心,坚持下去一定会很不错!!!

  • 相关阅读:
    深度学习-第三章概率与信息论
    编程语言如何推动DeFi成为主流?
    Elasticsearch 的页面工具kibana中 dev tool 菜单使用
    Leetcode1462-课程表 IV
    【21天打卡】前端攻城狮重学算法之-希尔排序
    vue实战——路由访问权限【详解】
    营销邮件主题怎么写?编写邮件主题的技巧?
    【数据分享】上海市道路中心线数据(无需转发\单线\shp格式)
    滚动播报、el-scrollbar
    hystrix断路器
  • 原文地址:https://blog.csdn.net/m0_62749567/article/details/139878672