Java网络编程

网络通信的要素

  • 对方的IP地址

  • 对应的端口号

192.168.16,124:5900

TCP/IP参考模型

分为四层,应用层,传输层,网络层,数据链路层(包含链路层和物理层)

得到对应域名的IP地址,可以在cmd中使用ping命令+域名

image-20211009100014595

小结:

  1. 网络编程上的主要问题

    • 准确定位到网络上的一台或者多台主机

    • 找到主机之后如何进行数据通信

  2. 网络编程中的要素

    • IP和端口号

    • 网络通信协议

  3. Java中万物皆对象,有这些要素一定有对应的类

 

 

IP

IP地址:InetAddress

可以在cmd用ipconfig指令查询相关数据,但是我们能看到的都是局域网IP,只是这个局域网可能比较大(小区),如果公网IP被查到就会遭受入侵

  • 唯一定位一台网路上的计算机

  • 127.0.0.1是本机IP(localhost),没有网也可以ping通过这个IP

  • IP地址的分类

    • IPV4/IPV6

      • IPV4 127.0.01 ,4个字节组成,每个字节是0-255(2^8),大概有42亿个(2^32),国际不够用,国际分配不均(这些指公网),IPV4私网还够用

      • IPV6 128位,8个无符号整数,十六进制,所以有abcde,这个是用不完的(16^32)

        2001:0bb2:aaaa:0015:0000:0000:1aaaa:1312

         

    • 公网-私网

用到一个网络类InetAddress

package Internet_Stu;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class TestInterAddress {
   public static void main(String[] args) {
       try {

           //查询本机地址的三种方式
  InetAddress inet1=InetAddress. getByName("127.0.0.1") ; //127.0.0.1代表本机地址
           System.out.println(inet1);

  InetAddress inet2=InetAddress. getByName("localhost") ; //localhost代表本地主机
           System.out.println(inet2);

           InetAddress inet3=InetAddress.getLocalHost();
           System.out.println(inet3);
           
           //通过域名查询网站IP地址
InetAddress inet4=InetAddress. getByName("www.baidu.com") ;
           System.out.println(inet4);

           //inet4已经与百度的IP绑定
           System.out.println(inet4.getAddress()); //返回一组地址,这个一般没什么用
           System.out.println(inet4.getCanonicalHostName());   //规范名
           
           System.out.println(inet4.getHostAddress());             //主机地址
           
           System.out.println(inet4.getHostName());                //得到对应主机的域名

      } catch (UnknownHostException e) {
           e.printStackTrace();
      }

  }
}

image-20211009111227417

 

端口

端口表示计算机上的一个程序的进程

  • 不同的进程有不同的端口号,用来区分软件

  • 被规定0--65535,TCP和UDP协议的端口号不冲突,也就是都有0--65535

  • 端口分类

    • 公有端口0~1023

      • HTTP:80 输入http://www.baidu.com:80/也可以进百度

      • HTTPS:443

      • FTP:21

      • Telent:23

    • 程序注册端口:1024~49151,分给用户和程序

      • Tomcat:8080

      • MySQL:3306

    • 动态,私有端口49152~65535,尽量不要用这里面的端口

      //相关控制台命令
      netsat -ano //查看所有端口
      netsat -ano|findstr "5900" //查看指定端口号  
      tasklist|findstr "20496"
        //linux中,| 表示管道,过滤的意思

      image-20211010105606750

通信协议

网络通信协议包括:速率,传输码率,代码结构,传输控制

由于问题非常复杂,所以需要分层

TCP/IP协议簇

其中比较重要的两个:

  • TCP:用户传输协议

  • UDP:用户数据报协议

出名的协议

  • TCP

  • IP:网络互联协议(属于网络层)

TCP UDP对比

  • TCP 打电话

    • 连接,稳定

    • 三次握手,四次挥手

    • 分为客户端,服务端(C/S)

    • 传输完成,释放连接,效率低

  • UDP 发短信

    • 不连接,不稳定

    • 客户端服务端没有明确界限

    • 不管有没有准备好,都可以发给你

    • DDOS:洪水攻击,使用的就是UDP,饱和攻击

TCP实现聊天

package com.he.net;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
//服务器端代码
public class TcpServer {
  {
       try {
           //1,开启一个地址,localhost 9999
           ServerSocket server = new ServerSocket(9999);

           //2,等待客户端链接过来,通过服务器socket的accept方法得到客户端的socket对象
           Socket socket=server.accept();

           /*3,读取客户端的消息,因为在客户端,
            socket这个对象已经通过getOutputStream读取过输入信息了
            所以可以直接拿出来*/
           InputStream is=socket.getInputStream();
           
           //创建一个字节数组管道流
           ByteArrayOutputStream baos=new ByteArrayOutputStream();
           byte[] buffer=new byte[1024];
           int len;
           //所有字节都读进对象里,返回byte数量,如果溢出就返回-1
           while((len=is.read(buffer))!=-1){
               baos.write(buffer,0,len);//offset就是开始位置和终止位置
               
          }
           System.out.println(baos.toString());

      } catch (IOException e) {
           e.printStackTrace();
      }
  }
}



package com.he.net;

import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;

//客户端代码
public class TcpClient {
   public static void main(String[] args) {
       try {
             //1,得到服务器的地址
          InetAddress serverIP= InetAddress.getByName("127.0.0.1");
         //2,得到端口号
           int port=9999;
            //3,为该IP,端口号创建一个Socket连接
           Socket socket=new Socket(serverIP,port);
           //4,发送消息IO流
           OutputStream outputStream = socket.getOutputStream();
           outputStream.write("第一次网络变成学习".getBytes());
      } catch (Exception e) {
           e.printStackTrace();
      }
  }

}
//这两段代码都有在finally处理IO流的资源释放问题,同时注意作用域问题,在finally识别不到try语句块中定义的对象

Tips: 创建绑定端口的服务器进程后, 当客户进程的 Socket构造方法返回成功, 表示客户进程的连接请求被加入到服务器进程的请求连接队列中. 虽然客户端成功返回 Socket对象, 但是还没跟服务器进程形成一条通信线路. 必须在服务器进程通过 ServerSocket 的 accept() 方法从请求连接队列中取出连接请求, 并返回一个Socket 对象后, 服务器进程这个Socket 对象才与客户端的 Socket 对象形成一条通信线路.(转自CSDN《Java网络编程精解》)

  • 客户端创建了对应服务器IP和端口号的socket,就向服务器发送了一条连接请求

  • 服务器端调用ServerSocket的accept()方法,就从请求队列里取出一条连接请求

  • 创建ServerSocket的时候可以输入一个请求队列最大长度,如果溢出就弹出错误

  • 关于IO流到底用I还是用O

    • 首先要声明的是,输入输出都是站在程序所在内存划分的

    • 输入流(input)是从其读取数据,输出流是向其写数据(output)

    • 从磁盘读数据是输入流,server从client的socket里读数据也是用输入流

    • 把内存里的数据写到控制台是输出流,把控制台上键盘输入的数字读到内存中是输入流

//服务器代码
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpService {
   public static void main(String[] args) {

           ByteArrayOutputStream baos=null;
           InputStream is=null;
           Socket socket=null;
           ServerSocket server=null;
           try {
               //服务器做的任务是接收数据并打印
               //1,为服务器进程申请一个端口号
               server = new ServerSocket(9999);
               //2,从消息队列中取出消息,不能直接取,要通过返回一个Socket对象取数据
               socket = server.accept();
               //3,从该Socket对象中读取数据,从数据流中取数据,所以是用InputStream对象接收
                is = socket.getInputStream();
               //使用管道流
                baos = new ByteArrayOutputStream();
               byte[] buffer = new byte[1024];
               int len;
               //从InputStream对象里接收数据
               while ((len = is.read(buffer)) != -1)//取长度
              {
                   //数据写进管道类对象里
                   baos.write(buffer, 0, len);
              }
               System.out.println(baos.toString());


          } catch (IOException e) {
               e.printStackTrace();
          }finally {
               //关闭资源,提升作用域
               if(baos!=null){
               try {
                   baos.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
              }
               if(socket!=null) {
                   try {
                       socket.close();
                  } catch (IOException e) {
                       e.printStackTrace();
                  }
              }
               if(server!=null) {
                   try {
                       server.close();
                  } catch (IOException e) {
                       e.printStackTrace();
                  }
              }
               if(is!=null) {
                   try {
                       is.close();
                  } catch (IOException e) {
                       e.printStackTrace();
                  }
              }
          }

  }
}

//客户端代码
package TcpByMyself;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class TcpClient {

   public static void main(String[] args) {
       OutputStream os=null;
       Socket socket=null;
       try {
           //客户端要做的事情是将一个含内容的socket对象发送给服务器端
           //1,得到服务器IP地址(总得确定要传送给哪个服务器)
           InetAddress inet;
           inet = InetAddress.getByName("127.0.0.1");
           //2,进一步得到端口号
           int port=9999;
           //3,创建一个socket对象,这个socket对应着服务器端对应进程的那个socket
           socket=new Socket(inet,port);
           //4,用IO流对象与socker类对象绑定,socket类有一个getOutputStream()的方法
           os=socket.getOutputStream();
           //5,往io流对象里写内容,write需要的是Byte[],需要用getByte()将String转换成Byte数组
           os.write("第一次学习网络编程".getBytes());

      } catch (UnknownHostException e) {
           e.printStackTrace();
      } catch (IOException e) {
           e.printStackTrace();
      }finally {
           //释放IO流对象的资源
           if(os!=null)
          {
               try {
                   os.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
           if(socket!=null)
          {
               try {
                   socket.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }
  }


}

这里在finally不写close()部分是不能运行通过的

 

 

TCP实现文件上传

客户端代码

package TCstudy;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class TcpClient {
   public static void main(String[] args) throws Exception {
       //连接服务器的socket
       Socket socket =new Socket(InetAddress.getByName("127.0.0.1"),9000);
//得到socket的流对象
       OutputStream os = socket.getOutputStream();
//图片提前放到根目录下了,使用File可以省略路径,因为是传输文件,所以使用FileInputStream
       //文件名记得加后缀
       FileInputStream fis=new FileInputStream(new File("QQ图片20210622225717.jpg"));
//fis读取文件数据到buffer里,再写入到socket的流对象os里,在服务器端可以通过socket的inputStream对象读取内容
       
       
       byte[] buffer=new byte[1024];
       int len=0;
       while((len=fis.read(buffer))!=-1)
           os.write(buffer,0,len);

       //这一步必须要有,如果没有这一步,客户端上面os写入的数据会被下面的inputStream对象接收,相当于没有传输到服务器
       socket.shutdownOutput();

       //确认服务器已经接收
       InputStream inputStream = socket.getInputStream();
       ByteArrayOutputStream baos = new ByteArrayOutputStream();
       //byte类用byte管道流
       byte[] buffer2=new byte[2014];
       int len2;
       while((len2=inputStream.read(buffer2))!=-1)
           baos.write(buffer2,0,len2);
       System.out.println(baos.toString());

       baos.close();
       inputStream.close();
       fis.close();
       os.close();
       socket.close();
  }



}

 

服务器端代码

package TCstudy;

import jdk.internal.util.xml.impl.Input;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
   public static void main(String[] args) throws Exception {
       //注册端口
       ServerSocket server=new ServerSocket(9000);
       //阻塞式监听,会一直等待客户端传输数据
        Socket socket=server.accept();
        //获取输入流
       InputStream is = socket.getInputStream();
       //文件输出
       FileOutputStream fos = new FileOutputStream(new File("recieve.jpg"));
       byte[] buffer=new byte[1024];
       int len;
       while((len=is.read(buffer) )!=-1)
      {
           fos.write(buffer,0,len);
      }

       //通知客户端资源接收完毕
       OutputStream os = socket.getOutputStream();
       os.write("我已经接收完毕,可以释放连接了".getBytes());



       //关闭资源
       fos.close();
       is.close();
       socket.close();
       server.close();
  }
}

 

 

UDP网络编程

UDP实现数据传输

UDP不区分客户端和服务器端

这里客户端表示数据发送方,服务器表示数据接收方

package UDPcode;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;

public class UdpClient {
   public static void main(String[] args) throws Exception {
       //1,建立一个socket
       //在这里为client的socket附上一个端口号,也可以用recieve()接受消息
       DatagramSocket socket = new DatagramSocket();
       //2,建个包,包括数据,IP地址,端口号,并把这些东西绑定到包上
       String msg="服务器你好,我叫阿伟";
       InetAddress localhost = InetAddress.getByName("localhost");
       int port=9090;
       //绑定到包上
       DatagramPacket packet=new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,localhost,port);

       //3,发送包
       socket.send(packet);

  }
}
package UDPcode;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpServer {
   public static void main(String[] args) throws Exception {
       //开放端口
       DatagramSocket socket=new DatagramSocket(9090);
       //接收数据包
       byte[] buffer=new byte[1024];
       DatagramPacket packet=new DatagramPacket(buffer,0,buffer.length);

       //recieve()一个阻塞式接受,类似于sout,不从键盘读取到数据就不跳到下一步
       socket.receive(packet);

       System.out.println(packet.getAddress());
       System.out.println(new String(packet.getData(),0,packet.getLength()));

       //关闭连接
       socket.close();

  }
}

UDP实现聊天

package UdpTalk;

import javax.lang.model.element.NestingKind;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
//客户端只表示发送方
public class UdpClient {
   public static void main(String[] args) throws Exception {
       DatagramSocket socket=new DatagramSocket(9999);

       while(true){
//读取控制台输入
           BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
           //用一个String对象readline,在转换成byte数组(因为packet只能接受byte数组为参数,也许是因为网络传输需要字节形式的数据)
           String data=reader.readLine();
           byte[] msg=data.getBytes();
           
           DatagramPacket packet=new DatagramPacket(msg,0,msg.length,InetAddress.getByName("localhost"),8888); //包与目标进程绑定
           //送出
           socket.send(packet);

           if(data.equals("bye"))
               break;
      }


  }
}
package UdpTalk;

import com.sun.org.apache.xpath.internal.objects.XString;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//服务器只表示接收方
public class UdpServer {
   public static void main(String[] args) throws Exception {
       //注册一个端口
       DatagramSocket socket=new DatagramSocket(8888);
       while(true){
//接收方也需要创建一个packet来作为recieve的接受参数,而这个packet对象创建时需要绑定一个缓冲byte数组
           byte[] buffer=new byte[1024];
           DatagramPacket packet=new DatagramPacket(buffer,0,buffer.length);
           socket.receive(packet);

//从packet里提取数据
           byte[] data=packet.getData();
           String recieveData=new String(data,0, packet.getLength()); //注意这里取长度的形式,写data.length会后跟空字符
           
           System.out.println(recieveData);

           if(recieveData.equals("bye"))
               break;

      }




  }
}

UDP实现在线咨询

由两个主机进行send和recieve的线程,send和recieve是双线程,如果是在本地运行,将有四个进程

 

TalkSend类

package MyChat;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;

public class TalkSend implements Runnable{
   DatagramSocket socket=null;
   BufferedReader reader=null;
   private int fromPort;
   private String toIP;
   private int toPort;

   public TalkSend(int fromPort, String toIP, int toPort) {
       this.fromPort = fromPort;
       this.toIP = toIP;
       this.toPort = toPort;
       try {
           socket=new DatagramSocket(fromPort);
           reader=new BufferedReader(new InputStreamReader(System.in));
      }catch (Exception e){

      }
  }

   @Override
   public void run() {

       while(true){
           try {
               String data=reader.readLine();
               byte[] msg=data.getBytes();
               DatagramPacket packet=new DatagramPacket(msg,0,msg.length, new InetSocketAddress(toIP,toPort));
               socket.send(packet);

               if(data.equals("bye"))
                   break;
          }catch (Exception e){
               e.printStackTrace();
          }

      }
  }
}

 

TalkRecieve类

package MyChat;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class TalkRecieve implements Runnable {
   private int port;
   private String fromWho;
   DatagramSocket socket = null;


   public TalkRecieve(int port, String fromWho) {
       this.port = port;
       this.fromWho = fromWho;
       try {
           socket = new DatagramSocket(port);
      } catch (SocketException e) {
           e.printStackTrace();
      }
  }


   @Override
   public void run() {
       try {
           while (true) {
               byte[] buffer = new byte[1024];
               DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
               socket.receive(packet);


               byte[] data = packet.getData();
               String recieveData = new String(data, 0, packet.getLength());
               //recieveData.trim();
               System.out.println(fromWho+": "+recieveData);

               if (recieveData.equals("bye"))
                   break;
          }
      } catch (SocketException e) {
           e.printStackTrace();
      } catch (IOException e) {
           e.printStackTrace();
      }
  }
}

 

下面是两个聊天对象,四个线程占用了四个端口

package MyChat;

public class TalkStudent {
   public static void main(String[] args) {
       new Thread(new TalkRecieve(5555,"老师")).start();
       new Thread(new TalkSend(6666,"localhost",7777)).start();
  }

}
package MyChat;

public class TalkTeacher {

   public static void main(String[] args) {
       new Thread(new TalkSend(8888,"localhost",5555)).start();
       new Thread(new TalkRecieve(7777,"学生")).start();
  }
}

 

URL

统一资源定位符:定位互联网上的某一个资源,资源地址

DNS域名解析: www.baidu.com本身也是一个IP,只不过这是为了方便记忆,DNS可以把域名解析成IP


协议://IP地址:端口号/项目名/资源

URL一般就由这五部分构成

整体思路很简单

创建一个URL存储一个资源地址,再用URL的openConnection方法创建一个对应协议的URLConnection类对象,随后使用IO流进行数据搬运,注意URLConnection也内置了IO流

package URLDown;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class URLDown {
   public static void main(String[] args) throws Exception {
      //1,下载地址
              URL url=new URL("https://m804.music.126.net/20211020004640/af154c5dcb664963d63581bd631b8b20/jdyyaac/025e/515c/5452/54d91df327f6833fc1e2c8523fe8b5c7.m4a?authSecret=0000017c995bc559192f0aa4635300f4");

       //2,连接到这个资源
       HttpURLConnection urlConnection=(HttpURLConnection) url.openConnection();

       InputStream inputStream=urlConnection.getInputStream();

       FileOutputStream fos=new FileOutputStream("届かない恋.m4a");
       byte[] buffer=new byte[1024];
       int len;
       while((len=inputStream.read(buffer))!=-1)
           fos.write(buffer,0,len);

fos.close();
        inputStream.close();
        urlConnection.disconnect();
  }
}

这样就可以从网站上扒拉下来一首《届かない恋》

如此,Java网络编程的学习就暂时告一段落了

最后の最后に、「届かない恋」をお届けします

 

 

更多文章请关注《万象专栏》