计算机网络实现了多台计算机间的互联,使得它们彼此之间能够进行数据交流。网络应用程序就是在已连接的不同计算机上运行的程序,这些程序借助于网络协议,相互之间可以交换数据。
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,该地址并不代表某个特定主机的位置,加入到同一个组的主机可以在某个端口上广播信息,也可以在某个端口上接收信息。