JAVA——从基础学起(十五)网络通信

15 网络通信

15.1 网络程序设计基础

Java已经将网络程序所需要的内容封装成不同的类,用户只需要创建这些类的对象,使用相应的方法,即使不具备相关的网络知识也可以编写出网络通信程序。

15.1.1 局域网与因特网

服务器指的是提供信息的计算机或者程序,客户机指的是请求信息的计算机或程序。网络用于连接服务器与客户及,实现两者之间的相互通信。不过有时候在某个网络中很难讲客户机与服务器区分开来。局域网(LAN)是一些以特定方式连接起来的计算机群,将LAN延伸到更大范围则形成了广域网(WAN)。我们熟悉的因特网就是由无数的LAN和WAN组成的。

15.1.2 网络协议

网络协议规定了计算机之间连接的各种特征、规则等信息。下面介绍几种比较常见的网络协议:

  1. IP协议
    IP协议是Internet Protocol的简称。Internet网络采用的是TCP/IP协议,依靠该协议在全球范围内实现了不同硬件结构,不同操作系统等之间的互联。每台主机都使用网络为其分配的Internet地址代表自己,这就是IP地址。到目前为止IP地址用4个字节32位二进制数来表示,称为IPv4,如192.168.1.1。还有的IP地址使用IPv6表示。TCP/IP模式是一种层次结构,共分为应用层,传输层,互联网层和网络层。各层实现特定的功能和提供特定的服务。
  2. 在TCP/IP协议栈中,有两个高级协议是程序编写者应该了解的,即传输控制协议TCP和用户数据包协议UDP。
  3. TCP协议是一种以固接连线为基础的协议,他提供两台计算机间可靠地数据传送 。UDP是无连接通信协议,不保证数据的可靠传输,适用于一些对数据准确性要求不高但是对速度和实效性要求非常高的场合。
15.1.3 端口和套接字
  1. 一般而言,一台计算机只有单一的与网络的物理连接,所有的数据都通过此进行疏桉树,这就是端口。网络程序设计中的端口并非是真实的物理存在,而是一个假想的连接装置,筒仓被规定在0-65535之间。HTTP服务一般使用80端口,FYO读物使用21端口。
    通常,0-1023之间的端口数用于一些致命的网络服务和应用,用户的普通网络应用服务应该使用1024以上的端口,这样才能避免端口冲突。
  2. 网络程序中的套接字Socket用于将应用程序与端口连接起来。套接字是一个假想的连接方式,程序员只需要创建Socket对象就可以使用套接字。

15.2 TCP程序设计基础

TCP网络程序设计是指使用Socket类编写通信程序。利用TCP协议进行通信应用程序分为服务器程序和客户机程序。

15.2.1 InetAddress类

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

方法 返回值 说明
getByName() InetAddress 获取与Host相对应的InetAddress对象
getHostAddress() String 获取InetAddress对象包含的IP地址
getHostName() String 获取此IP地址的主机名
getLocalHost() InetAddress 返回本地主机的InetAddress对象

下面使用实例进行说明:

import java.net.*
public class Address{
    public static void main(String[] args){
       InetAddress ip ;
       try{
           ip = InetAddress.getLocalHost();      //返回本地主机的InetAddress对象
           String localName = ip.getHostName();  //获取本地主机名
           String localAddress = ip.getLocalAddress() //获取本地主机IP地址
           System.out.println(localName + localAddress);
       }catch(UnkonwnHostException e){
           //这个异常通常会在主机不存在或者网络连接错误时发生
           e.printStackTrance();
       }
    }
}
15.2.2 ServerSocket类

java.net包中的SeverSocket类用于表示服务器套接字,主要功能是等待来自网络上的连接请求。服务器套接字一次可以与一个套接字进行连接,如果多台客户机请求连接,那么服务器套接字会将这些客户及存入队列中。如果请求数大于队列容纳数,那么多出来的请求会被拒绝。队列大小默认是50。

ServerSocket对象的构造方法有多种形式:
public ServerSocket();:创建非绑定服务器套接字
public ServerSocket(int port);:创建绑定到指定端口的服务器套接字
public ServerSocket(int port , int backlog);:使用指定的backlog创建服务器套接字,并将其绑定到指定端口
public ServerSocket(int port , int backlog , InetAdress bindAddress);:使用指定的端口,指定的backlog和要绑定到的本地IP地址创建服务器。适用于计算机上有多块网卡和多个IP地址的情况。

ServerSocket类的常用方法有:

方法 返回值 说明
accept() Socket 等待客户机的连接,若连接成功则返回与该客户机相连的Socket对象
isBound() boolean 判断此ServerSocket的绑定状态
getInetAddress() InetAddress 返回此服务器套接字的本地地址
isClosed() boolean 返回服务器套接字的关闭状态
close() void 关闭服务器套接字
bind(SocketAddress endpoint) void 将ServerSocket绑定到特定地址(IP地址和端口号)
getInetAddress() int 返回服务器套接字等待的端口号

调用ServerSocket对象的accept()方法,会返回一个和客户机Socket对象相连的Socket对象。服务器端的Socket对象使用getOutputStream()方法获得输出流,将指向客户机Socket使用getInputStream()方法获得的输入流,反之亦然。同时,accept()方法会阻塞线程的继续执行,直到收到客户机的连接请求。

15.2.3 TCP网络程序

下面使用实例说明客户机程序与服务器程序的编写:

import java.io.*;
import java.net.*;
import java.util.Random;

public class MyClient {  //客户机
	private Socket socket;
	private int r = 0;
	private DataInputStream dis;
	private DataOutputStream dos;
	
	public void connect(){
		try {
				socket = new Socket("127.0.0.1", 8998);
				dis = new DataInputStream(socket.getInputStream());
				dos = new DataOutputStream(socket.getOutputStream());

				dos.writeInt(r);          //传输半径
				dos.flush();               //刷新输出流
				
				System.out.println("圆的面积为:" + dis.readDouble());
				if (dis != null) {
					dis.close();
				}
				if (socket != null) {
					socket.close();
				}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public int setR(int r){
		this.r = r;
		return r;
	}
}
class MyTCPServer {          //服务器
	private DataInputStream dis;
	private DataOutputStream dos;

	private ServerSocket server;                         
	private Socket socket;                            
	
	void getServer(){
		try {
			server = new ServerSocket(8998);           //创建服务器套接字
			System.out.println("服务器套接字创建成功!");
			while (true) {                   
				System.out.println("等待客户机连接..."); 
				socket = server.accept();                     //等待客户机连接
				System.out.println("客户机连接成功!");
				dis = new DataInputStream(socket.getInputStream());
				dos = new DataOutputStream(socket.getOutputStream());
				
				
				int R = dis.readInt();           //读取客户机传送的信息
	 			System.out.println("半径为:" + R);
	 			double Area = Math.PI * R * R ;

	 			dos.writeDouble(Area);           //将信息传输回客户机
				dos.flush();
				if (dis != null) {
					dis.close();
				}
				if (socket != null) {
					socket.close();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		MyTCPServer server= new MyTCPServer();
		server.getServer();
	}
}

class MyThread extends Thread{
	private int r = new Random().nextInt(10) ;
	private MyClient client;
	@Override
	public void run() {
		client = new MyClient();
		client.setR(r);
		try {
			client.connect();
			Thread.sleep(1000);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		MyThread threadA = new MyThread();
		threadA.start();
	}
}

该实例通过在客户机中将半径传输给服务器,在服务器中计算面积后又将数据传送回客户机显示。完成了服务器与客户机之间的数据传输。

15.3 UDP程序设计基础

用户数据报协议UDP是网络信息传输的另一种方式。基于UDP的信息传输速度更快,但是不提供可靠的保证使用UDP传输数据时,用户无法知道数据能否正确到达主机,也不能确定数据顺序是否和原先一致。
发送数据包的步骤如下:

  1. 使用DatagramSocket()创建一个数据包套接字
  2. 使用DatagramPacket(byte[] buf , int offset , int length , InetAddress address , int port)创建要发送的数据包
  3. 使用DatagramSocket类的send()方法发送数据包

接收数据包的步骤如下:

  1. 使用DatagramSocket()创建一个数据包套接字
  2. 使用DatagramPacket(byte[] buf , int offset , int length)创建字节数组接受数据包
  3. 使用DatagramSocket类的receive()方法发送数据包

DatagramSocket类的receive方法同样也会阻断线程,直到接收到数据包线程才会恢复。

15.3.1 DatagramPacket类

DatagramPacket类用于表示数据包,该类的构造函数有:
DatagramPacket(byte[] buf , int offset , int length):指定了数据包的内存空间与大小
DatagramPacket(byte[] buf , int offset , int length , InetAddress address , int port):还指定了数据包的目标地址以及端口,因此用于发送数据

15.3.2 DatagramSocket类

DatagramSocket类用于表示发送和接收数据包的套接字。该类的构造函数有:
DatagramSocket():默认构造,将该套接字绑定到本地主机的任意可用端口上。
DatagramSocket(int port):绑定到指定端口上
DatagramSocket(int port , InetAddress address):用于多网卡和多IP地址的情况
在接受数据时必须指定端口号,因为数据包在发送过程中指定了端口,所以必须使用指定端口号的套接字进行接收。

15.3.3 UDP网络程序

下面使用一个实例来对UDP网络程序进行介绍,主要对他的非固定连接方式进行理解:

//---------------------------UDP服务器-----------------------------------//
package com.mw04;

import java.io.*;
import java.net.*;

public class UDPServer {
	public static void main(String[] args) {
		byte[] buf = new byte[1024];                //创建数据接收字节数组
		DatagramPacket dp = new DatagramPacket(buf, buf.length);       //创建数据接收包,并指定内存及大小
		try {
			DatagramSocket ds = new DatagramSocket(8998);              //创建数据包套接字
			System.out.println("服务器套接字创建成功!等待客户机连接...");       
			while (true) {
				ds.receive(dp);                                       //等待数据包的接收
				System.out.println("连接成功!");                        //接收数据包成功!
				System.out.println("接收到" + new String(buf , 0 , dp.getLength()));       //输出数据包中的内容
			}
			
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}	
	}
}
//---------------------以下是UDP客户机-------------------------//
package com.mw04;

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

public class UDPClient {
	public static void main(String[] args) {
		DatagramSocket ds;
		try {
			while (true) {
				ds = new DatagramSocket(5678);              //创建指定端口的数据包套接字
				byte[] buf = (new String("今晚有广播!请注意收听!端口号为:" + ds.getLocalPort())).getBytes();        //创建数据包内的内容
				DatagramPacket dp = new DatagramPacket(buf, buf.length,new InetSocketAddress("127.0.0.1" , ((int)(Math.random()*5 + 8995))));
				//创建发送数据包,并指定发送到的端口及IP地址,需与Server中的端口及IP地址一致,否则无法接收
				ds.send(dp);                      //发送数据包
				System.out.println("发送数据成功!!");
				Thread.sleep(1000);
				ds.close();
			}
			
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

由实例我们可以看到,客户机每隔一秒都会随机向端口(8995-9000)发送一次数据包,而只有当目的端口和服务器数据包套接字接收端口一致(8998)时才能成功接收。这就说明了UDP网络程序的服务机与客户机之间并没有固定的连接,每一台主机都可以成为服务器存在来发送或接收数据。这样的设计使得信息的实效性得到了保证,但是无法保证信息的可靠性。


2020.6.22

你可能感兴趣的:(Java)