------- android培训、java培训、期待与您交流! ----------
一、网络通信协议
通过计算机网络可以使用多态计算机实现连接,位于同一个网络中的计算机在运行连接和通信时需要遵守一定的规则,被称为网络通信协议。它对数据的传输格式、传输速率、传输步骤等做出了统一规定,通信双方必须同时遵守才能完成数据交换。
网络通信协议有很多种,目前应用最广泛的是 TCP/IP 协议(Transmission Control Protocal/Internet Protocal 传输控制协议、因特网互联协议),他是一个包括 TCP协议、IP协议、UDP协议(User Datagram Protocal)、ICMP协议(Internet Control Message Protocal)和其他一些协议的协议组。
TCP/IP 协议中的四层分别是应用层、传输层、网络层和链路层,每层负责不同的通信功能。
- 链路层:用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、双绞线提供的驱动。
- 网络层:整个 TCP/IP 协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
- 运输层:主要是网络进行通信,在进行网络通信时,可以采用 TCP 协议,也可以采用 UDP 协议。
- 应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
1、IP地址和端口号
要想使用网络中的计算机进行通信,必须为每台计算机指定一个标识号,通过这个标识号来指定接收数据的计算机或者发送数据的计算机。在 TCP/IP 协议中,这个标识号就是 IP 地址,它可以唯一标识一台计算机。
- IPv4:由 4 个字节大小的二进制表示,每个字节用一个十进制数(0~255)表示,数字间用符号 . 分开。
- IPv6:使用16个字节表示 IP 地址。
通过 IP 地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号,不同的应用程序时通过端口号区分的。端口号是用两个自己表示的,它的取值范围是 0~65535 ,其中 0~1023之间的端口号用于一些知名的网络服务和应用。
2、InetAddress
在 JDK 中提供了一个 InetAddress 类,用于封装一个 IP 地址,并提供了一系列与 IP 地址相关的方法。
import java.net.*;
class InetAddressDemo{
public static void main(String[] args){
InetAddress local = InetAddress.getLocalHost();
InetAddress itcast = InetAddress.getByName("www.itcast.com");
System.out.println("本机的IP地址:"+local.getHostAddress());
System.out.println("itcast的IP地址:"+itcast.getHostAddress());
System.out.println("3秒是否可达:"+itcast.isReachable(3000));
System.out.println("itcast的主机名:"+itcast.getHostName());
}
}
3、UDP 与 TCP 协议
- UDP 是 User Datagram Protocol的简称,称为用户数据协议。
UDP 是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑链接。简单来说,当一台计算机想另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用 UDP 协议消耗资源小,通信效率高,所以通常都会用于音频。视频和普通数据的传输,因为这种情况及时偶尔丢失一两个数据包,也不会对接受结果产生太大的影响。但是在使用 UDP 协议传送数据时,由于 UDP 面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用 UDP 协议。
- TCP 是 Transmission Control Protocol 的简称,称为传输控制协议。
TCP 协议是面向连接的通信协议,即在传输数据钱现在发送端和接收端建立逻辑链接,然后再传输数据,它提供了两台计算机之间可靠无差别的数据传输。
在 TCP 连接中必须要明确客户端与服务器端,由客户端想服务器端发出连接请求,每次链接的创建爱你都需要经过“三次握手”。第一次握手,客户端想服务器端发出连接请求,等待服务器确认;第二次握手,服务器端想客户端会送一个响应,通知客户端收到连接请求;第三次握手,客户端再次向服务器端发送确认信息,确认连接。
由于 TCP 协议的面向连接特性,它可以保证数据的俺去啊你性,所以是一个被广泛采用的协议,例如下载文件。
二、UDP 通信
1、DatagramPacket
UDP 的通信过程就像货运公司在两个码头见发送货物一样,在发送和接收货物时都需要使用集装箱来装载货物,UDP通信也是如此,发送和接收的数据也需要进行打包。在 JDK 中提供了一个 DatagaramPacket 类,用于封装 UDP 通信中发送或接收的数据。
在创建发送端和接收端的 DatagramPacket 对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端 IP 地址和端口号。
- DataramPacket(byte[] buf,int length):使用该构造方法在创建 DatagramPacket 对象时,指定了封装数据的字节数组和数据的大小,没有指定 IP 和端口号。很明显,这样的对象只能用于接收端,不能用于发送端。因为发送端一定要明确指出数据的目的地(IP地址和端口号),而接收端不需要明确知道数据的来源,只需要接收到即可。
- DatagramPacket(byte[] buf,int length,InetAddress addr,int port):使用该构造方法在创建 DatagramPacket 对象时,不仅指定了封装数据的字节数组和数据大小,还指定了数据包的目标 IP 地址和端口号。该对象通常用于发送端,因为在发送数据时必须指定接收端的 IP 地址和端口号。
- DatagramPacket(byte[] buf,int offset,int length):该构造方法与第一个构造方法类似,同样用于接收端,只不过在第一个否早方法的基础上,增加了一个 offset 参数,该参数用于指定接收到的数据在存入 buf 缓冲区数组时是从 offset 开始的。
- DatagramPacket(byte[] buf,int offset,int length,InetAddress addr,int port):该构造方法与第二个构造方法类似,同样用于发送端,只不过在第二个构造方法的基础上,增加了一个 offset 参数,该参数用于指定一个数组中发送数据的偏量为offset,即从 offset 位置开始发送数据。
DatagramPacket 类中的常用方法:
2、DatagramSocket
DatagramPacket 数据包的作用就如同是“集装箱”,可以将发送端或者接收端的数据封装起来,然而运送货物只有集装箱是不够的,还需要有码头。在程序中需要实现通信只有 DatagramPacket 数据包是不行的,为此 JDK 中提供了一个 DatagramSocket 类。DatagramSocket 类的作用类似于码头,使用这个类的实例对象就可以发送和接收 DatagramPacket 数据包。
在创建发送端和接收端的 DatagramPacket 对象时,使用的构造方法也有所不同,下面对 DatagramSocket 类中常用的构造方法进行讲解。
- DatagramSocket(): 该构造方法用于创建发送端的 DatagramSocket 对象,在创建 DatagramSocket 对象时,并没有指定端口号,此时系统会分配一个没有被其他网络应用程序所使用的端口号。
- DatagramSocket(int port):该构造方法即可用于创建接收端的 DatagramSocket 对象,又可以创建发送端的 DatagramSocket 对象,在创建接收端的 DatagramSocket 对象时,必须要指定一个端口号,这样就可以监听指定的端口。
- DatagramSocket(int port,InetAddress addr):使用该构造方法在创建 DatagramSocket 时,不仅指定了端口号,还制定了相关的 IP 地址,这种情况使用与计算机上有多个网卡的情况,可以明确规定数据通过那块网卡向外发送和接收那块网卡的数据。
DatagramSocket 的常用方法:
3、UDP 网络程序
import java.net.*;
//接收端数据
class Receive{
public static void main(String[] args){
byte[] buf = new byte[1024];
DatagramSocket socket = new DatagramSocket(5000);
DatagramPacket packet = new DatagramPacket(buf,1024);
System.out.println("等待接收数据...");
socket.receive(packet);
String str = new String(packet.getData(),0,packet.getLength()+"from"+packet.getAddress().getHostAddress()+":"+packet.getPort());
System.out.println(str);
socket.close();
}
}
import java.net.*;
//发送端数据
class Send{
public static void main(String[] args){
DatagramSocket socket = new DatagramSocket(3000);
String str = "hello java";
DatagramPacket packet = new DatagramPacket(str.getBytes(),str.length(),InetAddress.getByName("localhost"),5000);
System.out.println("发送数据");
socket.send(packet);
socket.close();
}
}
需要注意的是,在创建 DatagramPacket 对象时,需要指定目标 IP 地址和端口号,而且端口号必须和接收端指定的端口号一致。
4、UDP 案例----聊天程序
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.net.*;
class Chat{
public static void main(String[] args){
//创建Socket
DatagramSocket socket;
//定义窗口
JFrame frame = new JFrame("QQ聊天");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400,400);
frame.setResizable(false);
//窗口的North部分
JLabel state = new JLabel("当前还未开启监听");
state.setHorizontalAlignment(JLabel.NORTH);
//窗口的CENTER部分
JTextArea content = new JTextArea();
content.setEditable(false);
content.setBackgraund(new Color(211,211,211));
//窗口的SOUTH部分
JPanel panel = new JPanel(new BorderLayout());
JTextArea input = new JTextArea(5,20);
JPanel bottom = new JPanel(new FlowLayout(FlowLayout.CENTER,5,5));
JTextField ipText = new JTextField("127.0.0.1",8);
JTextField portText = new JTextField(String.valueOf(DEFAULT_PORT),3);
JButton send = new JButton("发送");
send.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
String ip = ipText.getText();
String port = portText.getText();
if(ip == null || port==null || ip.trim().equals("") || port.trim().equals("")){
JOptionPane.showMessageDialog(frame,"请输入IP地址和端口号");
return;
}
//需要发送的内容
String sendContent = input.getText();
byte[] buf = sendContent.getBytes();
content.append("我对"+ip+":"+port+"说:"+input.getText+"\r\n");
content.setCaretPosition(content.getText().length());
socket.send(new DatagramPacket(buf,bug.length,InetAddress.getByname(ip),Integer.parseInt(port)));
input.setText("");
}
});
clear.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
centent.setText("");
}
});
JButton clear = new JButton("清屏");
bottom.add(ipText);
bottom.add(portText);
bottom.add(send);
bottom.add(clear);
panel.add(new JScrollPane(input),BorderLayout.CENTER);
panel.add(bottom,BorderLayout.SOUTH);
//将NORTH、SOUTH、CENTER添加到窗口
frame.add(state,BorderLayout.NORTH);
frame.add(new JScrollPane(content),BorderLayout.Center);
frame.add(panel,BorderLayout.SOUTH);
frame.setVisible(true);
//接收数据
socket = new DatagramSocket(port);
byte[] buff = new byte[1024];
DatagramPacket packet = new DatagramPacket(buff,buff.length);
socket.reveive(packet);
content.setText(packet.getAddress().getHostAddress()+"对我说:"+new String(p.getData,0,packet.getLength()));
content.setCaretPosition(content.getText().length());
}
}
三、TCP 通信
TCP 通信同 UDP 通信一样,都能实现两塔计算机之间的通信,通信的两端都需要创建 Socket 对象。区别在于,UDP 中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。而 TCP 通信时岩哥区分客户端与服务器端的,在通信时必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的链接。
在 JDK 中提供了两个类用于实现 TCP 程序,一个是 ServerSocket 类,用于表示服务器端,一个是 Socket 类,用于表示客户端。通信时,首先创建代表服务器端的 ServerSocket 对象,该对象相当于开启一个服务,并等待客户端的链接,然后创建嗲表客户端的 Socket 对象想服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。
1、ServerSocket
在开发 TCP 程序时,首先需要创建服务器端程序。 JDK 的 java.net 包中提供了一个 ServerSocket 类,该类的实例对象可以实现一个服务器端的程序。ServerSocket 类提供了多种构造方法。
- ServerSocket():使用该构造方法在创建 ServerSocket 对象时并没有绑定端口号,这样的对象创建的服务器端没有监听任何端口,不能直接使用,还需要继续调用 bind(SocketAddress endpoint) 方法将其绑定到指定的端口号上,才可以正常使用。
- ServerSocket(int port):使用构造方法在创建 ServerSocket 对象时,就可以将其绑定到一个指定的端口号上,端口号可以指定为0,此时系统就会分配一个还没有呗其他程序使用的端口号。由于客户端需要根据指定的端口号来访问服务器端程序,因此端口号随机分配的情况并不常用,通常都会让服务器端程序监听一个指定的端口号。
- ServerSocket(int port , int backlog):该构造方法就是在第二个构造方法的基础上,增加了一个 backlog 参数。该参数用于指定服务器忙时,可以预知保持连接请求的客户数量,如果没有指定这个参数,默认为50.
- ServerSocket(int port,int backlog,InetAddress bindAddr):该否早方法在第三个构造方法的基础上,还指定了相关的 IP 地址,这种情况适用于计算机上有多快网卡和多个 IP 的情况。
ServerSocket 的常用方法:
ServerSocket 对象负责监听某台计算机的某个端口号,在创建 ServerSocket 对象后,需要继续调用对象的 accept() 方法,接收来自客户端的请求。当执行了accept() 方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求,accept() 方法才会返回一个 Socket 对象用于和客户端实现通信,程序才能继续向下执行。
2、Socket
ServerSocket 对象可以实现服务器端程序,但只实现服务器端程序还不能完成通信,此时还需要一个客户端程序与之交互,为此 JDK 提供了一个 Socket 类,用于实现 TCP 客户端程序。
- Socket():使用该构造方法在创建 Socket 对象时,并没有指定 IP 地址和端口号,也就意味着只创建了客户端对象,并没有去连接任何服务器。通过该构造方法创建对象后还需要调用 connect(SocketAddress endpoint) 方法,才能完成与指定服务器端的链接,其中参数 endpoint 用于封装 IP 地址和端口号。
- Socket(String host,int port):使用该构造方法在创建 Socket 对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数 host 接收的是一个字符串类型的 IP 地址。
- Socket(InetAddress addr,int port):该方法在使用上虞第二个构造函数类似,参数 addr 用于接收一个 InetAddress 类型的对象,该对象用于封装一个 IP 地址。
Socket 的常用方法:
3、简单的 TCP 程序
创建一个服务端和一个客户端,服务器端向客户端发送一段数据。
import java.net.*;
import java.io.*;
class Server{
//服务器
public static void main(String[] args){
ServerSocket ss = new ServerSocket(7788);
Socket s = ss.accept();
OutputStream out = s.getOutputStream();
System.out.println("开始于客户端交互");
out.write("你好".getBytes());
System.out.println("结束与客户端交互");
out.close();
s.close();
}
}
import java.net.*;
import java.io.*;
class Client{
//客户端
public static void main(String[] args){
Socket s = new Socket(InetAddress.getLocalHost(),7799);
InputStream in = s.getInputStream();
byte[] b = new byte[1024];
int len = in.read(b);
System.out.println(new String(buf,0,len));
in.close();
s.close();
}
}
4、多线程的 TCP 程序
多个用户访问同一台服务器。
import java.net.*;
import java.io.*;
class Server{
public static void main(String[] args){
ServerSocket ss = new ServerSocket();
while(true){
Socket s = ss.accept();
new Thread(){
public void run(){
OutputStream out;
try{
out = s.getOutputStream();
System.out.println("开始于客户端交互");
out.write("你好".getBytes());
System.out.println("结束与客户端的交互");
out.close();
s.close();
}catch(Exception e){
e.printStackTrace();
}
}
}.start();
}
}
}
5、TCP 案例---文件上传
import java.net.*;
import java.io.*;
class Server{
public static void main(String[] args){
ServerSocket ss = new ServerSocket();
while(true){
Socket socket = ss.accept();
new Thread(new ServerThread(socket)).start();
}
}
}
class ServerThread implements Runnable{
private Socket socket;
ServerThread(Socket socket){
this.socket = socket;
}
public void run(){
String ip = socket.getInetAddress().getHostAddress();
int count = 1;
try{
//创建上传文件目录
File diractory = new File("d://upload//");
while(!diractory.exists()){
diractory.mkdir();
}
//创建文件名称
File file = new File(diractory,ip+"("+count+")"+".jpg");
while(file.exists()){
file = new File(diractory,ip+"("+(count++)+")"+".jpg");
}
//得到客户端的读取流
FileInputStream in = socket.getInputStream();
//创建输出流,用于将内容写到硬盘文件
FileOutputStream fos = new FileOutputStream(file);
//读取数据
byte[] buf = new byte[1024];
len = 0;
while((len=in.read(buf))!=-1){
fos.write(buf,0,len);
}
//得到输出流,想客户端发送上传成功
OutputStream out = s.getOutputStream();
out.write("上传成功".getBytes());
fos.close();
socket.close();
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
import java.net.*;
import java.io.*;
class Client{
public static void main(String[] args){
Socket s = new Socket(InetAddress.getLocalHost(),7788);
//得到输出流
OutputStream out = s.getOutputStream();
//创建读取流,用于读取硬盘文件
FileInputStream fis = new FileInputStream("d:\\1.jpg");
byte[] buf = new byte[1024];
int len;
while((len=fis.read(buf))!=-1){
out.write(buf,0,len);
}
//得到读取流,用于读取服务器返回的信息
InputStream in = s.getInputStream();
byte[] b = new byte[1024];
int num = in.read(b);
String msg = new String(b,0,num);
System.out.println(msg);
//关闭流和Socket
fis.close();
s.close();
}
}