一、网络通信协议
计算机网络中,连接和通信的规则称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交互。
目前应用最广泛的网络通信协议是TCP/IP、UDP、ICMP和其他一些协议的协议组。
基于TCP/IP的参考模型将协议分为4个层次,链路层、网络层、传输层和应用层,每层负责不同的通信功能。
链路层:也称为网络接口层,负责监视数据在主机和网络之间的交互。
网络层:也称为网络互联层,是整个TCP/IP协议的核心,主要用于将传输的数据进行分组,将分组数据发送到目标计算机或网络。
传输层:主要完成网络程序的通信,进行网络通信时可以采用TCP或UDP。
应用层:主要负责应用程序的协议,如HTTP、FTP等。
1.1 IP地址和端口号
要想使网络中的计算机能够通信,必须为每台计算机制定一个标识号,在TCP/IP协议中,这个标识号就是IP地址。
目前IP地址广泛使用的版本是IPv4,它由四个字节大小的二进制数来表示,每个字节可以用一个十进制数字(0~255)表示,数字间用符号“.”分开。为了解决IP地址面临的枯竭问题,IPv6使用16个字节表示IP地址。
IP地址由“网络.主机”两部分组成。其中网络部分是网络的地址编码,主机部分是网络中一个主机的地址编码,二者是主从关系。IP地址总共分为5类,常用的有以下3类:
A类地址:由第一段的网络地址和其余三段的主机地址组成,范围1.0.0.0~127.255.255.255
B类地址:由前两段的网络地址和其余两段的主机地址组成,范围128.0.0.0~191.255.255.255
C类地址:由前三段的网络地址和最后一段的主机地址组成,范围192.0.0.0~223.255.255.255
回送地址127.0.0.1是指本机地址,常用来测试使用。
计算机中,不同的应用程序通过端口号区分。通过IP地址可以连接到指定的计算机,并通过端口号访问目标计算机中的某个应用程序。
端口号用两个字节表示,取值范围0~65535,其中0~1023的端口号由操作系统的网络服务占用,用户的普通应用程序需要使用1024以上的端口号。
1.2 InetAddress
Java中提供了一个与IP地址相关的InetAddress类,该类封装一个IP地址,常用方法如下:
eg.
import java.net.InetAddress;
public class Main{
public static void main(String[] args){
InetAddress localAddress = InetAddress.getLocalHost();
InetAddress remoteAddress = InetAddress.getByName("www.baidu.com");
System.out.println("本机的IP地址:"+localAddress.getHostAddress());
System.out.println("baidu的IP地址:"+remoteAddress.getHostAddress());
System.out.println("3s是否可到达:"+remoteAddress.isReachable(3000));
System.out.println("baidu的主机名为:"+remoteAddress.getHostName());
}
}
1.3 UDP与TCP
传输层的两个重要协议是UDP和TCP。UDP称为用户数据报协议,TCP称为传输控制协议。
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。UDP消耗资源小,通信效率高,通常用于音频、视频和普通数据的传输,重要数据不建议使用UDP。
TCP是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据。TCP连接中必须要明确客户端和服务器端,由客户端向服务器端发出连接请求,每次连接的创建都要经过“三次握手”。TCP可以保证数据传输的安全性。
二、UDP通信
2.1 DatagramPacket
DatagramPacket类用于封装UDP通信中发送或接收的数据。
创建发送端和接收端的DatagramPacket对象的构造方法有所不同。
接收端:
(1)DatagramPacket(byte[] buf, int length) 指定了封装数据的字节数组和数据的大小
(2)DatagramPacket(byte[] buf, int offset, int length) 增加了接收的数据开始的位置
发送端:
(1)DatagramPacket(byte[] buf, int length, InetAddress addr, int port) 增加了目标IP地址和端口号
(2)DatagramPacket(byte[] buf, int offset, int length, InetAddress addr, int port) 增加了发送的数据开始的位置
DatagramPacket类常用方法如下。
2.2 DatagramSocket
使用DatagramSocket类的实例对象可以发送和接收DatagramPacket数据包。
创建发送端和接收端的DatagramSocket对象的构造方法有所不同。
(1)DatagramSocket() 用于创建发送端的对象
(2)DatagramSocket(int port) 两个都可以
(3)DatagramSocket(int port, InetAddress addr) 两个都可以,适用于计算机上有多个网卡
DatagramSocket类常用方法如下。
2.3 UDP网络程序
为了避免发送端发送数据时找不到接收端造成数据丢失,要先完成接收端的编写。
import java.net.*;
//接收端程序
public class Receiver{
public static void main(String[] args) throws Excpetion{
byte[] buf = new byte[1024];
DatagramSocket ds = new DatagramSocket(8954);
DatagramPacket dp = new DatagramPacket(buf, buf.length);
System.out.println("等待接收数据");
ds.receive(dp);//等待接收数据,如果没有数据则阻塞
String str = new String(dp.getData(), 0, dp.getLength())+
"from" + dp.getAddress().getHostAddress() + ":" + dp.getPort();
System.out.println(str);
ds.close();
}
}
只有接收到发送端发送的数据时,receive()方法才会结束阻塞状态。
import java.net.*;
//发送端程序
public class Sender{
public static void main(String[] args) throws Exception{
DatagramSocket ds = new DatagramSocket();
String str = "hello world";
byte[] arr = str.getBytes();//将字符串转为字节数组
DatagramSocket ds = new DatagramSocket(arr, arr.length,
InetAddress.getByName("localhost"), 8954);
System.out.println("发送信息");
ds.close();
}
}
创建DatagramPacket对象时必须指定和接收端一样的端口号。
2.4 多线程的UDP网络程序
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class Main{
public static void main(String[] args){
new Receive().start();
new Send().start();
}
}
class Receive extends Thread{
public void run(){
try{
DataSocket ds = new DataSocket(6666);
byte[] buf = new byte[1024];
DataPacket dp = new DataPacket(buf, buf.length);
while(true){
ds.receive(dp);
byte[] arr = dp.getData();
int len = dp.getLength();
String ip = dp.getAddress().getHostAddress();
System.out.println(ip + ":" + String(arr, 0, len));
}
ds.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
class Send extends Thread{
try{
DatagramSocket ds = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while(true){
String str = sc.nextLine();
if("quit".equals(str)){
break;
}
byte[] arr = str.getBytes();
int len = arr.length();
DatagramPacket dp = new DatagramPacket(arr, len,
InetAddress.getByName("localhost"), 6666);
ds.send(dp);
}
ds.close();
}catch(IOException e){
e.printStackTrace();
}
}
三、TCP通信
TCP和UDP的区别在于,UDP只有发送端和接收端,可以任意发送数据;TCP严格区分客户端和服务器端,通信时必须先启动服务器端,由客户端连接服务器端。
通信时要先创建代表服务器端的ServerSocket对象,调用该对象的accept()方法接受来自客户端的请求,然后创建代表客户端的Socket对象,使用该对象向服务器端发送连接请求,服务器端响应请求后,两者才建立连接开始通信。
客户端和服务器端建立连接后,数据是以I/O流的形式交互的。
3.1 ServerSocket
ServerSocket类提供了多种构造方法。
(1)ServerSocket()
该方法创建的ServerSocket对象不与任何端口绑定,不能直接使用,还需要继续调用bind(SocketAddress endpoint)方法将其绑定到指定的端口号上。其中endpoint参数用于封装IP地址和端口号
(2)ServerSocket(int port)
该方法最常用,指定了绑定的端口号。端口号可以指定为0,系统会分配一个未被使用的端口号。
(3)ServerSocket(int port, int backlog)
增加了backlog参数,用于指定在服务器忙时,可以与之保持连接请求的等待客户数量,如果未指定,默认为50。
(4)ServerSocket(int port, int backlog, InetAddress bindAddr)
增加了bindAddr参数,用于指定相关的IP地址。适用于计算机上有多个网卡和多个IP的情况。
ServerSocket类的常用方法如下。
3.2 Socket
Socket类有多种构造方法。
(1)Socket()
最常用的方法,该方法没有指定IP地址和端口号,即没有连接任何服务器,还需要调用connect(SocketAddress endpoint)方法完成于指定服务器的连接。
(2)Socket(String host, int port)
该方法指定了IP地址和端口号,其中host参数接受字符串类型的IP地址。
(3)Socket(InetAddress address, int port)
该方法与法二类似,参数address接受InetAddress类型的对象。
Socket类的常用方法如下。
3.3 TCP网络程序
import java.io.*;
import java.net.*;
public class Server{
public static void main(String[] args)throws Exception{
new TCPServer().listen();
}
}
//TCP服务器端
class TCPServer{
private static final int PORT = 7788;
public void listen()throws Exception{
ServerSocket server = new ServerSocket(PORT);
Socket client = server.accept(); //接收数据
OutputStream os = client.getOutputStream();
os.write("welcome".getBytes());//当客户端连接到服务器端时,向客户端输出数据
os.close();
client.close();
}
}
import java.io.*;
import java.net.*;
public class Client{
public static void main(String[] args)throws Exception{
new TCPClient().connect();
}
}
//TCP服务器端
class TCPClient{
private static final int PORT = 7788;
public void connect()throws Exception{
Socket client = new Socket(InetAddress.getLocalHost(), PORT);
InputStream is = client.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
System.out.println(new String(buf, 0, len));
client.close();
}
}
3.4 多线程的TCP网络程序
改进服务器端程序,实现多客户访问服务器。
import java.io.*;
import java.net.*;
public class Server{
public static void main(String[] args)throws Exception{
new TCPServer().listen();
}
}
//TCP服务器端
class TCPServer{
private static final int PORT = 7788;
public void listen()throws Exception{
ServerSocket server = new ServerSocket(PORT);
while(true){
final Socket client = server.accept();
new Thread(){
public void run(){
try{
OutputStream os = client.getOutputStream();
os.write("welcome".getBytes());
os.close();
client.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
}
}