网络编程
5.1.2:IP地址
目前Java对IPv6和IPv两种地址均进行了封装,其实现类分别是Inet4Address和Inet6Address,它们都继承了类InetAddress。
InetAddress是Java对Ip地址的封装,在java.net中许多类都用到。InetAddress对域名进行解析是用本地机器配置或者网络命名服务(如DNS)和网络信息服务(NIS)。以下是获取InetAddress对象获取最常用的方法getByName(String host)。
import java.net.InetAddress; import java.net.UnknownHostException; public class NetTest { public static void main(String[] args) throws UnknownHostException { InetAddress address = InetAddress.getByName("www.cnblogs.com"); System.out.println("==========获取博客园的IP地址============"); System.out.println(address.toString()); InetAddress[] addresses = InetAddress.getAllByName("www.cnblogs.com"); System.out.println("==========获取博客园的IP地址列表=========="); for(InetAddress add : addresses) System.out.println(add.toString()); } }
运行结果:
5.2 TCP编程
TCP是一种可靠的、基于连接的网络协议,它是面向字节流的,即从一个进程到另一个进程的二进制序列。一条TCP连接需要两个断电,这两个端点需要分别创建各自的套接字。通常一方用于发送请求和数据(成为客户端),而另一方用于监听网络请求和数据(成为服务端)。
5.2.1 核心类
java.net包中有恒多用于网络编程的类,其中有两个常用于TCP编程。
① Socket ,Socket是建立网络连接时用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。
方法 | 说明 |
---|---|
public Socket(String host,int port) | 创建一个流套接字并将其连接到指定主机上的指定端口号 |
public Socket(InetAddress address,int port,Inetaddress loaclAddr, int localPort) | 创建一个流套接字并制定了本地的端口以及目的地地址的端口。 |
public Socket() | 创建一个流套接字,但此套接字并未连接 |
public InputStream getInputStream() | 该方法返回程序中套接字所能读取的输入流 |
public OutputStream getOutputStream() | 返回套接字中的输出流 |
public void close | 关闭指定的套接字,套接字中的输入输出流也即将被关闭 |
②ServerSocket ServerSocket类实现服务器套接字,等待请求通过网络传入,给予该请求执行某些操作,然后可能向请求者返回结果
方法 | 说明 |
---|---|
public ServerSocket() | 创建一个服务器套接字,并未指明地址和端口 |
public ServerSocket(int port) | 创建一个服务器套接字,指明了监听的端口,默认接受的最大连接数为50,但与则拒绝新接入 |
public ServerSocket(int port,int backlog) | 创建一个服务器套接字,指明了监听的端口,接受的最大连接数由参数backlog设定,多于则拒绝。 |
public Socket accept() | 建立连接后,该方法会返回一个套接字,用于处理客户端请求以及服务端响应 |
public void setSoTimeout(int timeout) | 用于设置accept()方法的最大阻塞时间,超过将抛出异常 |
public void close() | 关闭服务器套接字 |
5.2.2 一对一通信
Server和Socket能创建进行通信的网络程序,下面示例演示了这一过程,盖里定义了一个客户端进程、一个服务端进程。客户端进程发送请求,服务端接收请求后,返回处理结果,代码如下:
//Server.java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(888); Socket socket = server.accept(); InputStreamReader reader = new InputStreamReader(socket.getInputStream()); BufferedReader bufferReader = new BufferedReader(reader); PrintWriter writer = new PrintWriter(socket.getOutputStream()); String request = bufferReader.readLine(); System.out.println("Client say:" + request); String line = "Hello, too!"; writer.println(line); writer.flush(); writer.close(); bufferReader.close(); socket.close(); server.close(); } } //Client.java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws Exception { Socket socket = new Socket("127.0.0.1",888); InputStreamReader reader = new InputStreamReader(socket.getInputStream()); BufferedReader bufferReader = new BufferedReader(reader); PrintWriter writer = new PrintWriter(socket.getOutputStream()); String readLine = "Hello!"; writer.println(readLine); writer.flush(); String response = bufferReader.readLine(); System.out.println("Server say:" + response); writer.close(); bufferReader.close(); socket.close(); } }
运行结果:
5.2.3一对多通信
前面的 Client/ Server程序只能实现 Server和一个客户的对话。在实际应用中,往往在服务器上运行一个永久的程序,接收来自其他多个客户端的请求,提供相应的服务。为了实现在服务器方给多个客户提供服务的功能,需要利用多线程实现多客户机制。服务器总是在指定的端口上监听是否有客户请求,一旦监听到客户请求,服务器就会启动一个专门的服务线程来响应该客户的请求,而服务器本身在启动完线程后马上又进入监听状态,等待下个客户的到来,下面用例5-4来实现这一过程:在服务端定义两个类,一个类负责监听连接和线程分配,另一个类负责套接字及流的处理;客户端代码仍然使用例5-3中的客户端。
import java.net.ServerSocket; import java.net.Socket; public class server1 { public static void main(String[] args) throws Exception { ServerSocket server = new ServerSocket(888); while (true) { Socket socket = server.accept(); SocketHandler handler = new SocketHandler(socket); Thread thread = new Thread(handler); thread.start(); } } } import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class SocketHandler implements Runnable { private Socket socket; public SocketHandler(Socket socket) { this.socket = socket; } public void run() { try { InputStreamReader reader = new InputStreamReader(socket.getInputStream()); BufferedReader buffer_reader = new BufferedReader(reader); PrintWriter writer = new PrintWriter(socket.getOutputStream()); String client = "<" + socket.getInetAddress().toString() + ":" + socket.getPort() + ">"; String request = buffer_reader.readLine(); System.out.println(client+" say: " + request); String line = client + " Hello, too! "; writer.println(line); writer.flush(); buffer_reader.close(); socket.close(); } catch (Exception e) { e.printStackTrace(); } } }
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws Exception { Socket socket = new Socket("127.0.0.1",888); InputStreamReader reader = new InputStreamReader(socket.getInputStream()); BufferedReader bufferReader = new BufferedReader(reader); PrintWriter writer = new PrintWriter(socket.getOutputStream()); String readLine = "Hello!"; writer.println(readLine); writer.flush(); String response = bufferReader.readLine(); System.out.println("Server say:" + response); writer.close(); bufferReader.close(); socket.close(); } }
运行结果:
5.3 UDP编程
UDP是一种不可靠无连接的网络协议,与可靠连接协议TCP相比,它不能保证发送的数据一定能被对方按顺序收到,如果数据在传送的过程中丢失,也不会自动重发。然而由于它不需要像TCP那样,每次通信都要建立一条特定的连接通道,进行传输控制,UDP本身的数据就自带了传输控制信息,因此UDP传输能节省系统开销,而且在数据传输效率上UDP要比TCP高。
5.3.1核心类
①DatagramPacket 与TCP发送接收消息都是用流不同,UDP使用数据报文,当 发送/接受 数据报文时Java程序都创建数据报文实例DatagramPacket封装发送信息/存储接受的报文信息。
方法 | 说明 |
---|---|
public DatagramPacket(byte[] buf,int length) | 构造DatagramPacket接受长度为length的数据包 |
public DatagramPacket(byte[] buf,int offset,int length) | 构造DatagramPacket接受长度为length的数据包,缓冲区中指定了offset偏移量 |
public DatagramPacket(byte[] buf,int offset,int length, InetAddress address,int port) | 构造数据包,用来将长度为length偏移量为offset的包发送到指定主机上的指定端口号 |
public void setAddress(InetAddress iaddr) | 设置目标主机的IP地址 |
public void setPort(int iport) | 设置目标程序的端口 |
public void setSocketAddress(SocketAddress address) | 设置数据包的目的地(包括IP地址和端口号) |
public void setData(byte[] buf) | 为此包设置数据缓冲区。将此DatagramPacket设置为偏移量为0,长度为buf |
public void setData(byte[] buf,int offset,int length) | 为此包设置数据缓冲区,包括数据、长度和偏移量 |
②DatagramSocket 用于发送和接受UDP数据包,在Java中即为接受和发送DatagramPacket,与TCP的Socket不同的是,通信前无须事先建立连接。
方法 | 说明 |
---|---|
public DatagramSocket() | 创建数据包套接字,该套接字并未指明监听的地址和端口,一般为发送方使用 |
创建数据包套接字并指明了监听地址、端口,一般为接收方使用 | |
public DatagramSocket(int port,InetAddress) | 创建数据包套接字,指明了监听的地址和端口(主机有多个地址)和监听的端口,该数据包只接受发往指定地址和端口的数据包 |
public void send(DatagramPaccket p) | 此方法用于发送封装好的数据报,数据报中序含有发送的数据、数据的长度以及目标地址和端口等信息。 |
public void recevie(DatagramPaccket p) | 此方法用于接收封装好的数据报,数据报中序含有发送的数据、数据的长度以及目标地址和端口等信息。 |
public void Connect(InetAddress address,int port) | 用于连接指定IP地址和端口的目的地址,建立连接后,接收方就只能接收目标地址发送的数据报,其他的数据报将被丢弃。 |
public void connect(SocketAddress addr) | 与connect(InetAddress address,int port)类似 |
5.3.2 UDP传输实例
下面是一个简单的UDP传输实例。客户端封装在一个数据包DatagramPacket对象,该对象包含目标的IP地址和端口以及需要传输的数据。然后使用UDP套接字DatagramSocket发送出去。服务端一直使用UDP套接字监听指定端口,当监听到数据时,条用接收方法,填充DatagramPacket对象,接着发送一条确认信息会客户端。
package UDP编程; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public class Client { public static void main(String[] args) { try { DatagramSocket sendSocket = new DatagramSocket(); //创建发送方的套接字,IP默认为本地,端口号随机 String mes = "你好,接收方!"; //由于数据报中传输的数据是以字节数组的形式存储的,所以要把字符串转化为字节数组 byte[] buf = mes.getBytes(); int port = 8888; //确定发送方的IP地址及端口号,地址为本地机器地址 InetAddress ip = InetAddress.getLocalHost(); DatagramPacket sendPacket = new DatagramPacket(buf,buf.length, ip,port); //创建发送类型的数据包 sendSocket.send(sendPacket); //通过套接字发送数据 byte[] getBuf = new byte[1024]; //确定接受反馈数据的缓冲存储器,即存储数据的字节数组 DatagramPacket getPacket = new DatagramPacket(getBuf, getBuf.length); //创建接收类型的数据报 sendSocket.receive(getPacket); //套接字接收数据 String backMes = new String(getBuf,0,getPacket.getLength()); //解析反馈信息并打印 System.out.println("接收方返回的信息:" + backMes); sendSocket.close(); //关闭套接字 }catch(Exception e) { e.printStackTrace(); } } } package UDP编程; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketAddress; public class Server { public static void main(String[] args) { try { InetAddress ip = InetAddress.getLocalHost(); int port = 8888; DatagramSocket getSocket = new DatagramSocket(port,ip); //创建接收方的套接字,并指定端口号和IP地址 byte[] buf = new byte[1024]; //确定接收数据报的数据的数组大小 DatagramPacket getPacket = new DatagramPacket(buf,buf.length); //创建接收类型的数据报,并存储在buf中 getSocket.receive(getPacket); //套接字接收数据 String getMes = new String(buf,0,getPacket.getLength()); System.out.println("对方发送的消息: " + getMes); InetAddress sendIP = getPacket.getAddress(); //通过数据包获得发送放的套接字地址 int sendPort = getPacket.getPort(); System.out.println("对方地址是:" + sendIP.getHostAddress() + ": " + sendPort); SocketAddress sendAddress = getPacket.getSocketAddress(); String feedback = "接收方说: 我收到了!"; byte[] backBuf = feedback.getBytes(); DatagramPacket sendPacket = new DatagramPacket(backBuf,backBuf.length, sendAddress); getSocket.send(sendPacket); getSocket.close(); }catch(Exception e) { e.printStackTrace(); } } }