Java入门(18)-- 网络通信

2022-04-07 15:43:02 浏览数 (1)

计算机网络实现了多台计算机间的互联,使得它们彼此之间能够进行数据交流。网络应用程序就是在已连接的不同计算机上运行的程序,这些程序借助于网络协议,相互之间可以交换数据。

18.1 网络程序设计基础

网络程序设计编写的是与其他计算机进行通信的程序,Java已经将网络程序所需要的东西封装成不同的类,我们只要创建这些类的对象,使用相应的方法,就可以编写网络通信程序。

18.1.1 局域网与因特网

服务器是指提供信息的计算机或程序,客户机是指请求信息的计算机或程序,网络用于连接服务器与客户机,实现两者间的相互通信。

局域网(Local Area Network,LAN)就是一群通过一定形式连接起来的计算机,它可以由两台计算机组成,也可以由同一区域内的上千台计算机组成。将LAN延伸到更大的范围,这样的网络称为广域网(Wide Area Network,WAN),而因特网(Internet)就是由无数的LAN和WAN组成。

18.1.2 网络协议

网络协议规定了计算机之间连接的物理、机械(网线与网卡的连接规定)、电器(有效的电平范围)等特征,计算机之间的相互寻址规则,数据发送冲突的解决方式,长数据如何分段传送与接收等内容。

1. IP协议

IP是Internet Protocol的简称,是一种网络协议,Internet网络采用的协议是TCP/IP协议,其全称是Transmission Control Protocol/Internet Protocol。Internet依靠TCP/IP协议,在全球范围内实现了不同硬件结构、不同操作系统、不同网络系统间的互联。

在Internet网络上存在着数以亿计的主机,每台主机都用网络为其分配的Internet地址代表自己,这个地址就是IP地址。IP地址用4个字节,也就是32位的二进制数来表示,称为IPv4,为了便于使用,通常取用每个字节的十进制数,并且每个字节之间用圆点隔开来表示IP地址,比如127.1.1.1。

TCP/IP模式是一种层次结构,共分为4层,分别为应用层、传输层、互联网层和网络层,各层实现特定的功能,提供特定的服务和访问接口,并具有相对的独立性:

2. TCP与UDP协议

在TCP/IP协议栈中,有两个高级协议,即传输控制协议(Transmission Control Protocol,TCP)与用户数据报协议(User Datagram Protocol,UDP)。

TCP协议是一种以固接连线为基础的协议,它提供两台计算机间可靠的数据传送。TCP可以保证从一端数据送至连接的另一端时,数据能够确实送达,而且抵达的数据的排列顺序和送出时的顺序相同。TCP协议适合可靠性要求比较高的场合,HTTP、FTP和Telnet等都需要使用可靠的通信频道。

UDP是无连接通信协议,不保证数据的可靠传输,但能够向若干个目标发送数据,或接收来自若干个源的数据。UDP以独立发送数据包的方式进行。UDP协议适合于一些对数据准确性要求不高,但对传输速度和时效性要求非常高的网站。

注:一些防火墙和路由器会设置成不允许UDP数据包传输,因此若遇到UDP连接方面的问题,应先确定所在网络是否允许UDP协议。

18.1.3 端口和套接字

一般而言,一台计算机只有单一的连接到网络的物理连接(Physical Connection),所有的数据都通过此连接对内、对外送达特定的计算机,这就是端口。网络程序设计中的端口(port)并非真实的物理存在,而是一个假想的连接装置,端口被规定为一个在0~65535之间的整数。HTTP服务一般使用80端口,FTP服务使用21端口。

通常,0~1023之间的端口数用于一些知名的网络服务和应用,用户的普通网络应用程序应该使用1024以上的端口数,以免端口号与另一个应用或系统服务所用端口冲突。

网络程序中的套接字(Socket)用于将应用程序与端口连接起来。套接字是一个假想的连接装置,就像插座一样可连接电器与电线。Java将套接字抽象化为类,我们只需创建Socket类对象,即可使用套接字。

18.2 TCP程序设计基础

TCP网络程序设计是指利用Socket类编写通信程序,利用TCP协议进行通信的两个应用程序是有主次之分的,一个称为服务器程序,另一个称为客户机程序,两者的功能和编写方法大不一样。

服务器端与客户端的交互:

①——服务器程序创建一个ServerSocket(服务器端套接字),调用accept()方法等待客户机来连接;

②——客户端程序创建一个Socket,请求与服务器建立连接;

③——服务器接收客户机的连接请求,同时创建一个新的Socket与客户建立连接,随后服务器继续等待新的请求。

18.2.1 InetAddress类

java.net包中的InetAddress类是与IP地址相关的类,利用该类可以获取IP地址、主机地址等信息。

InetAddress类的常用方法:

代码语言:javascript复制
package core;
import java.net.*;
public class Address {
  public static void main(String[] args) {
    InetAddress ip;
    try {
      ip = InetAddress.getLocalHost();
      String localname = ip.getHostName();
      String localip = ip.getHostAddress();
      System.out.println("本机名:"   localname);//本机名:DESKTOP-JB314BH
      System.out.println("本机IP地址:"   localip);//本机IP地址:192.168.1.8
    } catch (UnknownHostException e) {
      e.printStackTrace();
    }
  }
}

注:InetAddress类的方法会抛出UnknownHostException异常,所以必须进行异常处理,这个异常在主机不存在或网络连接错误时发生。

18.2.2 ServerSocket类

java.net包中的ServerSocket类用于表示服务器套接字,其主要功能是等待来自网络上的“请求”,它可通过指定的端口来等待连接的套接字。

服务器套接字一次可以与一个套接字连接,如果多台客户机同时提出连接请求,服务器套接字会将请求连接的客户机存入列队中,然后从中取出一个套接字,与服务器新建的套接字连接起来。若请求连接数大于最大容纳数,则多出的连接请求被拒绝。队列的默认大小是50。

ServerSocket类的构造方法通常会抛出IOException异常,具体有以下几种形式:

ServerSocket():创建非绑定服务器套接字

ServerSocket(int port):创建绑定到特定端口的服务器套接字

ServerSocket(int port, int backlog):利用指定的backlog创建服务器套接字,并将其绑定到指定的本地端口号上

ServerSocket(int port, int backlog, InetAddress bindAddress):使用指定的端口、侦听backlog和要绑定到的本地IP地址创建服务器。这种情况适用于计算机上有多快网卡和多个IP地址的情况,可以明确规定ServerSocket在哪块网卡或哪个IP地址上等待客户的连接请求。

ServerSocket类的常用方法:

调用ServerSocket类的accept()方法,会返回一个和客户端Socket对象相连接的Socket对象。服务器端的Socket对象使用getOutputStream()方法获得的输出流,将指向客户端Socket对象使用getInputStream()方法获得的那个输入流;同样,服务器端的Socket对象使用getInputStream()方法获得的输入流,将指向客户端Socket对象使用getOutputStream()方法获得的那个输出流。也就是说,当服务器向输出流写入信息时,客户端通过相应的输入流就能读取,反之亦然。

注:accept()方法会阻塞线程的继续进行,直到接收到客户的呼叫。如果没有客户请求,accept()方法没有发生阻塞,肯定是程序出现了问题。通常是使用了一个被其他程序占用的端口号,ServerSocket绑定没有成功。

18.2.3 TCP网络程序

在网络编程中如果只要求客户机向服务器发送消息,不要求服务器向客户机发送消息,称为单向通信。客户机套接字和服务器套接字连接成功后,客户机通过输出流发送数据,服务器则通过输入流接收数据。

MyTcp.java:

代码语言:javascript复制
package core;
import java.io.*;
import java.net.*;
public class MyTcp {
  private BufferedReader reader;
  private ServerSocket server;
  private Socket socket;
  void getserver() {
    try {
      server = new ServerSocket(8998);
      System.out.println("服务器套接字已经创建成功");
      while(true) {
        System.out.println("等待客户机的连接");
        socket = server.accept();
        reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        getClientMessge();
      }
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
  private void getClientMessge() {
    try {
      while(true) {
        System.out.println("客户机:"   reader.readLine());
      }
    } catch(Exception e) {
      e.printStackTrace();
    }
    try {
      if(reader != null) {
        reader.close();
      }
      if(socket != null) {
        socket.close();
      }
    }catch(IOException e) {
      e.printStackTrace();
    }
  }
  public static void main(String[] args) {
    MyTcp tcp = new MyTcp();
    tcp.getserver();
  }
}

运行结果:

MyClien.java:

代码语言:javascript复制
package core;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.swing.*;
import javax.swing.border.BevelBorder;
public class MyClien extends JFrame{
  private static final long serialVersionUID = 1L;
  private PrintWriter writer;
  Socket socket;
  private JTextArea ta = new JTextArea();
  private JTextField tf = new JTextField();
  Container cc;
  public MyClien(String title) {
    super(title);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    cc = this.getContentPane();
    final JScrollPane scrollPane = new JScrollPane();
    scrollPane.setBorder(new BevelBorder(BevelBorder.RAISED));
    getContentPane().add(scrollPane, BorderLayout.CENTER);
    scrollPane.setViewportView(ta);
    cc.add(tf,"South");
    tf.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        writer.println(tf.getText());
        ta.append(tf.getText()   'n');
        ta.setSelectionEnd(ta.getText().length());
        tf.setText("");
      }
    });
  }
  private void connect() {
    ta.append("尝试连接n");
    try {
      socket = new Socket("127.0.0.1", 8998);
      writer = new PrintWriter(socket.getOutputStream(), true);
      ta.append("完成连接n");
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
  public static void main(String[] args) {
    MyClien clien = new MyClien("向服务器送数据");
    clien.setSize(400, 200);
    clien.setVisible(true);
    clien.connect();
  }
}

运行结果:

注:当一台机器上安装了多个网络应用程序时,很可能指定的端口号已被占用,还可能遇到以前运行良好的网络程序突然运行不了的情况。这种情况很可能也是由于端口被别的程序占用了,此时可以运行netstat -help来获得帮助,使用命令netstat -an来查看该程序所使用的端口。

18.3 UDP程序设计基础

用户数据报协议(UDP)是网络信息传输的另一种形式,使用UDP传递数据时,用户无法知道数据能否正确地到达主机,也不能确定到达目的地的顺序是否和发送的顺序相同。

基于UDP通信的基本模式如下:

将数据打包(称为数据包),然后将数据包发往目的地;

接收别人发来的数据包,然后查看数据包。

发送数据包的步骤:

1. 使用DatagramSocket()创建一个数据包套接字;

2. 使用DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)创建要发送的数据包;

3. 使用DatagramSocket类的send()方法发送数据包。

接收数据包的步骤:

1. 使用DatagramSocket(int port)创建数据包套接字,绑定到指定的端口;

2. 使用DatagramPacket(byte[] buf, int length)创建字节数组来接收数据包;

3. 使用DatagramSocket类的receive()方法接收UDP包。

注:DatagramSocket类的receive()方法接收数据时,如果还没有可以接收的数据,在正常情况下receive()方法将阻塞,一直等到网络上有数据传来,receive()方法接收该数据并返回。如果网络上没有数据发送过来,receive()方法也没有阻塞,肯定是程序有问题,大多数情况下是因为使用了一个被其他程序占用的端口号。

18.3.1 DatagramPacket类

java.net包的DatagramPacket类用来表示数据包,构造函数有

代码语言:javascript复制
DatagramPacket(byte[] buf, int length)
DatagramPacket((byte[] buf, int length, InetAddress address, int port)
18.3.2 DatagramSocket类

java.net包中的DatagramPacket类用于表示发送和接收数据包的套接字,构造函数有

代码语言:javascript复制
DatagramSocket()
DatagramSocket(int port)
DatagramSocket(int port, InetAddress addr)
18.3.3 UDP网络程序

广播数据报是一项较新的技术,其原理类似于电台广播。广播电台需要在指定的波段和频率上广播信息,收听者也要将收音机调到指定的波段和频率,才可以收听广播内容。

例:主机不断地重复播出节目预报,加入到同一组内的主机随时可接收到广播信息;接收者将正在接收的信息放在一个文本域中,并将接收的信息放在另一个文本域中。

广播主机程序不断地向外播出信息:

代码语言:javascript复制
package core;
import java.net.*;
public class Weather extends Thread{
  String weather = "节目预报:晚上八点有郭德纲的相声,请收听";
  int port = 9898;
  InetAddress iaddress = null;
  MulticastSocket socket = null;
  Weather(){
    try {
      iaddress = InetAddress.getByName("224.255.10.0");
      socket = new MulticastSocket(port);
      socket.setTimeToLive(1);//指定发送范围是本地网络
      socket.joinGroup(iaddress);//加入广播组
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
  public void run() {
    while(true) {
      DatagramPacket packet = null;
      byte data[] = weather.getBytes();
      packet = new DatagramPacket(data, data.length, iaddress, port);
      System.out.println(new String(data));
      try {
        socket.send(packet);
        sleep(10000);
      } catch(Exception e) {
        e.printStackTrace();
      }
    }
  }
  public static void main(String[] args) {
    Weather w = new Weather();
    w.start();
  }
}

运行结果:

接收广播程序:

代码语言:javascript复制
package core;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import javax.swing.*;
public class Receive extends JFrame implements Runnable, ActionListener{
  int port;
  InetAddress group = null;
  MulticastSocket socket = null;
  JButton ince = new JButton("开始接收");
  JButton stop = new JButton("停止接收");
  JTextArea inceAr = new JTextArea(10,10);
  JTextArea inced = new JTextArea(10,10);
  Thread thread;
  boolean b = false;
  public Receive() {
    super("广播数据报");
    setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    thread = new Thread(this);
    ince.addActionListener(this);
    stop.addActionListener(this);
    inceAr.setForeground(Color.blue);
    JPanel north = new JPanel();
    north.add(ince);
    north.add(stop);
    add(north, BorderLayout.NORTH);
    JPanel center = new JPanel();
    center.setLayout(new GridLayout(1, 2));
    center.add(inceAr);
    center.add(inced);
    add(center, BorderLayout.CENTER);
    validate();
    port = 9898;
    try {
      group = InetAddress.getByName("224.255.10.0");
      socket = new MulticastSocket(port);
      socket.joinGroup(group);
    } catch(Exception e) {
      e.printStackTrace();
    }
    setBounds(100, 50, 360, 380);
    setVisible(true);
  }
  @Override
  public void run() {
    while(true) {
      byte data[] = new byte[1024];
      DatagramPacket packet = null;
      packet = new DatagramPacket(data, data.length, group, port);
      try {
        socket.receive(packet);
        String message = new String(packet.getData(), 0, packet.getLength());
        inceAr.setText("正在接收的内容:n"   message);
        inced.append(message   "n");
      } catch(Exception e) {
        e.printStackTrace();
      }
      if(b == true) {
        break;
      }
    }
  }
  @Override
  public void actionPerformed(ActionEvent e) {
    if(e.getSource() == ince) {
      ince.setBackground(Color.red);
      stop.setBackground(Color.yellow);
      if(!(thread.isAlive())) {
        thread = new Thread(this);
      }
      thread.start();
      b = false;
    }
    if(e.getSource() == stop) {
      ince.setBackground(Color.yellow);
      stop.setBackground(Color.red);
      b = true;
    }
  }
  public static void main(String[] args) {
    Receive rec = new Receive();
    rec.setSize(500, 300);
  }
}

运行结果:

注:发出广播和接收广播的主机地址必须位于同一组内,地址范围为224.0.0.1~224.255.255.255,该地址并不代表某个特定主机的位置,加入到同一个组的主机可以在某个端口上广播信息,也可以在某个端口上接收信息。

0 人点赞