Java已经将网络程序所需要的内容封装成不同的类,用户只需要创建这些类的对象,使用相应的方法,即使不具备相关的网络知识也可以编写出网络通信程序。
服务器指的是提供信息的计算机或者程序,客户机指的是请求信息的计算机或程序。网络用于连接服务器与客户及,实现两者之间的相互通信。不过有时候在某个网络中很难讲客户机与服务器区分开来。局域网(LAN)是一些以特定方式连接起来的计算机群,将LAN延伸到更大范围则形成了广域网(WAN)。我们熟悉的因特网就是由无数的LAN和WAN组成的。
网络协议规定了计算机之间连接的各种特征、规则等信息。下面介绍几种比较常见的网络协议:
TCP网络程序设计是指使用Socket类编写通信程序。利用TCP协议进行通信应用程序分为服务器程序和客户机程序。
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();
}
}
}
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()方法会阻塞线程的继续执行,直到收到客户机的连接请求。
下面使用实例说明客户机程序与服务器程序的编写:
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();
}
}
该实例通过在客户机中将半径传输给服务器,在服务器中计算面积后又将数据传送回客户机显示。完成了服务器与客户机之间的数据传输。
用户数据报协议UDP是网络信息传输的另一种方式。基于UDP的信息传输速度更快,但是不提供可靠的保证使用UDP传输数据时,用户无法知道数据能否正确到达主机,也不能确定数据顺序是否和原先一致。
发送数据包的步骤如下:
DatagramSocket()
创建一个数据包套接字DatagramPacket(byte[] buf , int offset , int length , InetAddress address , int port)
创建要发送的数据包send()
方法发送数据包接收数据包的步骤如下:
DatagramSocket()
创建一个数据包套接字DatagramPacket(byte[] buf , int offset , int length)
创建字节数组接受数据包receive()
方法发送数据包DatagramSocket类的receive方法同样也会阻断线程,直到接收到数据包线程才会恢复。
DatagramPacket类用于表示数据包,该类的构造函数有:
DatagramPacket(byte[] buf , int offset , int length)
:指定了数据包的内存空间与大小
DatagramPacket(byte[] buf , int offset , int length , InetAddress address , int port)
:还指定了数据包的目标地址以及端口,因此用于发送数据
DatagramSocket类用于表示发送和接收数据包的套接字。该类的构造函数有:
DatagramSocket()
:默认构造,将该套接字绑定到本地主机的任意可用端口上。
DatagramSocket(int port)
:绑定到指定端口上
DatagramSocket(int port , InetAddress address)
:用于多网卡和多IP地址的情况
在接受数据时必须指定端口号,因为数据包在发送过程中指定了端口,所以必须使用指定端口号的套接字进行接收。
下面使用一个实例来对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