目录
学习不是一蹴而就的,日积月累比一天速成更有效果!
用通俗的话来讲,Socket是由IP地址+端口所组成的,是支持TCP/IP协议的网络通信的最小操作单元。形象来说IP地址就是某地的一栋楼,端口则是这栋楼中具体的一个房间。
网络通信的两种方式:TCP和UDP,本文使用TCP进行通信,也就是Socket技术,因为其较为可靠稳定。
内网穿透原理:外网通过Socket发送请求,也就是找到内网的IP地址以及端口,一旦内容匹配成功,并且接受了你的请求,那么此时就可以实现网络通信。
假设服务端的小林:拥有一台电脑,其IP地址是192.168.xxx,并且拥有一个端口5900,此时客户端小郑发送了请求,想要连上小林电脑上的192.168.xxx的IP地址5900的端口。
那么此时,服务端小林接受了客户端小郑的请求,那么他们就可以实现网络通信,互发信息和文件就不成问题勒!
前面我们说到,Socket通信通常有服务端和客户端,接下来小郑就用简单代码带大家理解一下何为内网穿透。
首先我们先看下如何用代码实现最基本的网络通信。
服务端代码
- public static void main(String[] args) throws IOException {
- ServerSocket serverSocket=new ServerSocket(5900);
- //可以利用socket进行通信
- Socket socket=serverSocket.accept();
- System.out.println("开启了5900端口");
- }
可以看到服务端代码中开放了一个5900端口,当有人在同一网络下使用我们的IP地址和端口进行连接时,我们进行接受,就可以实现网络通信。
客户端代码
- public static void main(String[] args) throws IOException {
- Socket socket=new Socket("localhost",5900);
- }
客户端则利用Socket连接了IP地址和端口,如果你想要连别人的电脑,就需要知道别人电脑的IP地址,这里的localhost是代表自己主机的IP地址。
此时,我们需要先运行服务端,再运行客户端。
可以看到,当我们先运行服务端代码后,客户端不运行,服务端还没有任何反应;但是当我们将客户端也进行运行后,可以看到控制台打印了一句话:“开启了5900端口”,这是因为服务端accept(接受)了客户端发来的请求。
当然这几句代码还不足以进行两端互发信息、传文件等操作,所以接下来我们就需要完善我们的代码咯。
为了直观的感受网络通信接发消息、文件等操作,我们先使用本地主机进行尝试,有条件的小伙伴也可以和其他小伙伴一起尝试。
为了实现同网络接发信息,我们就需要用到前面文章所讲到的IO流知识了,需要请看文章:Java:I/O流之输入流和输出流、字节流和字符流详解,附带代码-CSDN博客
服务端代码
- public static void main(String[] args) throws IOException {
- ServerSocket serverSocket=new ServerSocket(5900);
- System.out.println("开启了5900端口");
- //可以利用socket进行通信
- Socket socket=serverSocket.accept();
- //只要有人连接进来就发消息给客户端:欢迎进入xxx的聊天室
- OutputStream outputStream=socket.getOutputStream();
- //使用包装流,因为getOutputStream()是字节流,但是发送信息是要用字符流
- OutputStreamWriter outputStreamWriter=new OutputStreamWriter(outputStream);
- //再次使用包装流,为了使客户端能够接收到发送的信息
- PrintWriter printWriter=new PrintWriter(outputStreamWriter);
- printWriter.write("欢迎进入zmh聊天室");
- printWriter.flush();//刷新缓存,不然信息太少,数据还留在缓存区,发不出来
- printWriter.close();
- }
客户端代码
- public static void main(String[] args) throws IOException {
- Socket socket=new Socket("localhost",5900);
- //读入
- InputStream inputStream=socket.getInputStream();
- //这边也是使用包装流
- InputStreamReader inputStreamReader=new InputStreamReader(inputStream);
- //再次使用包装流读取信息
- BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
- //读一行信息,也就是服务端传来的信息
- String message =bufferedReader.readLine();
- System.out.println("收到消息:"+message);
- bufferedReader.close();
- }
代码中使用了包装流的原因是因为,Socket是基于字节流的。当我们想要获取socket中的内容时,只能够使用字节流,但是我们发送文件需要用的是字符流,所以我们需要将其包装成字符流进行信息发送。
运行结果
通过结果我们可以发现,此时服务端只能发送一次信息,客户端也只能接收一次信息,并且不能够手动输入,所以我们就需要完善我们此时的代码。
想要无限多次的接收发信息,我们就需要使用到循环这一技术。
服务端代码
- public static void main(String[] args) throws IOException {
- ServerSocket serverSocket=new ServerSocket(5900);
- System.out.println("正在开启5900端口……");
- //可以利用socket进行通信
- Socket socket=serverSocket.accept();
- System.out.println("有新用户连入……");
- OutputStream outputStream=socket.getOutputStream();
- //使用包装流,因为getOutputStream()是字节流,但是发送信息是要用字符流
- OutputStreamWriter outputStreamWriter=new OutputStreamWriter(outputStream);
- //再次使用包装流,为了使客户端能够接收到发送的信息
- PrintWriter printWriter=new PrintWriter(outputStreamWriter);
- Scanner input=new Scanner(System.in);
- //使用循环为了让服务端能够给客户端发送多条信息
- while(true)
- {
- System.out.print("请输入你要发送的信息:");
- String message= input.nextLine();
- printWriter.println(message);
- printWriter.flush();//刷新缓存,不然信息太少,数据还留在缓存区,发不出来
- }
- }
客户端代码
- public static void main(String[] args) throws IOException {
- Socket socket=new Socket("localhost",5900);
- //读入
- InputStream inputStream=socket.getInputStream();
- //这边也是使用包装流
- InputStreamReader inputStreamReader=new InputStreamReader(inputStream);
- //再次使用包装流读取信息
- BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
- while(true) {
- //读一行信息,也就是服务端传来的信息
- String message =bufferedReader.readLine();
- System.out.println("收到消息:"+message);
- }
- }
看到就这些代码,哥们不禁发出一句:简单~
但是,不能总是你服务端发信息,我客户端不能发呀,所以我们接下来就进行代码完善。
进行到这里,我们则会接触一个关键的技术点:多线程
看到这,哥们也只能说一句简单~,多线程不过是为了同步完成多个任务,能够从软件或硬件中实现多个线程并发执行的一个技术。
举个例子:哥们现在是大老板,现在要在码头找人卸下十吨货,请问哥们:十个人卸得快还是一个人卸的快?哥们又不是蝙蝠侠,包是卸不过十个人呀。
服务端代码
- public static void main(String[] args) throws IOException {
- ServerSocket serverSocket=new ServerSocket(5900);
- System.out.println("正在开启5900端口……");
- //可以利用socket进行通信
- Socket socket=serverSocket.accept();
- //创建Socket类对象传递
- GetMessageThread getMessageThread=new GetMessageThread(socket);
- getMessageThread.start();
-
- System.out.println("有新用户连入……");
- OutputStream outputStream=socket.getOutputStream();
- //使用包装流,因为getOutputStream()是字节流,但是发送信息是要用字符流
- OutputStreamWriter outputStreamWriter=new OutputStreamWriter(outputStream);
- //再次使用包装流,为了使客户端能够接收到发送的信息
- PrintWriter printWriter=new PrintWriter(outputStreamWriter);
- Scanner input=new Scanner(System.in);
- //使用循环为了让服务端能够给客户端发送多条信息
- while(true)
- {
- System.out.println("请输入你要发送的信息:");
- String message= input.nextLine();
- printWriter.println(message);
- printWriter.flush();//刷新缓存,不然信息太少,数据还留在缓存区,发不出来
- }
- }
- }
- class GetMessageThread extends Thread{
- Socket socket;
- public GetMessageThread(Socket socket){
- this.socket=socket;
- }
- public void run(){
- try{
- InputStream inputStream=socket.getInputStream();
- InputStreamReader inputStreamReader=new InputStreamReader(inputStream);
- BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
- while(true) {
- String message =bufferedReader.readLine();
- System.out.println("收到客户端消息:"+message);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- }
- }
客户端代码
- public class Client {
- public static void main(String[] args) throws IOException {
- Socket socket=new Socket("localhost",5900);//用于连接某一主机的5900端口
- //创建对象,把socket传递过去
- SendMessageThread sendMessageThread=new SendMessageThread(socket);
- sendMessageThread.start();
- //读入
- InputStream inputStream=socket.getInputStream();
- // DataInputStream dataInputStream=new DataInputStream(inputStream);
- //这边也是使用包装流
- InputStreamReader inputStreamReader=new InputStreamReader(inputStream);
- //再次使用包装流读取信息
- BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
- while(true) {
- //读一行信息,也就是服务端传来的信息
- String message =bufferedReader.readLine();
- // String message=dataInputStream.readUTF();
- System.out.println("收到服务端消息:"+message);
- }
- }
- }
- class SendMessageThread extends Thread{
- Socket socket;
- //创建一个带参的构造函数
- public SendMessageThread(Socket socket)
- {
- this.socket=socket;
- }
- public void run(){
- Scanner input =new Scanner(System.in);
- OutputStream outputStream= null;
- try {
- outputStream = socket.getOutputStream();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- OutputStreamWriter outputStreamWriter=new OutputStreamWriter(outputStream);
- PrintWriter printWriter=new PrintWriter(outputStreamWriter);
- while(true){
- System.out.println("请输入你想要发送的信息:");
- String message=input.nextLine();
- printWriter.println(message);
- printWriter.flush();
- }
- }
- }
运行结果
我们能够看到,这个时候无论是客户端还是服务端都能够接发信息了。
至于为什么要使用多线程,哥们可以在main方法中同时输入两个while(true)尝试一下,根本走不通呀!
实际上发送文件和发送信息大同小异,区别在于:发送信息是字符流,所以我们需要用到包装流;而发送文件是字节流,不需要进行包装了。
服务端代码
- public static void main(String[] args) throws IOException {
- ServerSocket serverSocket=new ServerSocket(6666);
- //接收连接
- Socket socket=serverSocket.accept();
- //发送文件使用输出流
- FileInputStream fileInputStream=new FileInputStream("D:/java/Test/内网穿透.pptx");
- byte buff[]=new byte[1024];
- int ch=0;
- OutputStream outputStream = socket.getOutputStream();
- while((ch=fileInputStream.read(buff))!=-1)
- {
- outputStream.write(buff,0,ch);
- }
- fileInputStream.close();
- outputStream.close();
- socket.close();
- }
客户端代码
- public static void main(String[] args) throws IOException {
- Socket socket=new Socket("localhost",6666);
- InputStream inputStream = socket.getInputStream();
- FileOutputStream fileOutputStream =new FileOutputStream("D:/桌面/hello.pptx");
- byte buff[]=new byte[1024];
- int ch=0;
- while((ch=inputStream.read(buff))!=-1)
- {
- fileOutputStream.write(buff,0,ch);
- }
- fileOutputStream.close();
- inputStream.close();
- socket.close();
- }
运行结果
可以看到,文件被顺利的收发勒!
好勒,看到这里大家估计对于同网络通信有了一定的理解,接下来我们讲下跨网络通信!
费劲千辛万苦,终于实现了这一步,原本很简单的一部,但是因为自己电脑存在问题,配置了好久,才成功。如果电脑是win11家庭版的,请先参看其他博主的这篇文章进行配置win11家庭版安装远程桌面服务(使用RDPWra解决windows家庭版无法远程桌面问题)_rdpwrap win11-CSDN博客
在进行阿里云服务器跨网络之前,我们需要下载frp文件。所谓frp是专注于内网穿透的高性能的反向代理应用,支持TCP、UDP、HTTP、HTTPS等多种协议。
我们可以到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.此时再回到文件夹中解压缩刚刚的压缩包
服务端:配置frps.ini文件--使用命令 vi frps.ini
但是这个通常不用修改,保持原样
客户端配置:在本地打开文件夹中的frpc.ini文件
[common]
server_addr = 阿里云服务器公网IP
server_port = 7000[tcp]
type = tcp
local_ip = 192.168.0.104
local_port = 6666
remote_port = 6000
进行到这里,我们还需要在阿里云服务器上建立一个安全组(开放所有端口)
至此,文件的配置就完成了,我们就可以进行内网穿透模拟了!!!
我们可以使用章节3.21中的代码进行实验,因为我自己实验,客户端和服务端代码都在我这里运行,小伙伴和可以找你的好基友一起进行实验。
基友A
1.需要到阿里云启动frps--使用命令 ./frps
2.启动前面3.21节的代码Send.java,但是要将代码稍微修改一下。
- ServerSocket serverSocket=new ServerSocket(6666);
- //该端口6666要和4.12中的local_port一致
3.启动本地目录下的frpc.exe(不可以直接启动)两种启动方式
找到文件夹所在目录--输入cmd
然后输入命令进行运行:frpc.exe -c frpc.ini
在frp目录下新建一个文件start.bat,然后使用记事本进行编辑保存。编辑内容:
@echo off
:home
frpc -c frpc.ini
goto home
双击运行:
基友B
这位基友的活就相当简单咯,只需要运行前面的客户端代码Client.java,也需要稍微先修改一下代码。(谨记,先A再B,基友A操作完基友B再进行操作)
- Socket socket = new Socket("阿里云服务器公网ip地址", 6000);
- //IP地址指的是阿里云服务器的地址,要和第4.12中的server_addr一致;端口6000要和第3步的remote_port一致
运行结果
基友B的桌面上会出现基友A传来的文件
最后,到这里就大功告成了!!!呼,其实到这里我也想说一下,学习是一个很需要耐心的东西。这篇文章我前前后后也花了一周的时间,因为解决问题很多时候会消耗我们的耐心,看似不难,但是小小的失误也能导致极大的问题。
希望这篇文章能够同样帮助到你,保持耐心,坚持下去一定会很不错!!!