Java网络编程(一)Socket套接字
一、基础知识
1.TCP:传输控制协议。
2.UDP:用户数据报协议。
二、IP地址封装
1.InetAddress类的常用方法
getLocalHost() 返回本地主机的InetAddress对象 InetAddress类型 getByName(String host) 获取指定主机名称的IP地址 InetAddress类型 getHostName() 获取此主机名 String getHostAddress() 获取主机IP地址 String isReachable(int timeout) 在timeout指定的毫秒时间内,测试IP地址是否可达 Boolean
2.示例1:测试IP地址从“192.168.131.1”到“192.168.131.150”范围内所有可以访问的主机的名称,如果对方没有安装防火墙,并且网络连接正常的话,都可以访问的。从输出可以看出,192.168.131.1是本地主机的IP,其他地址都不能联通,可能是因为1000毫秒太短或者对方主机装有防火墙的原因。
package bigjunoba.bjtu.iptoname; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; public class IPToName { public static void main(String[] args) { String IP = null; for (int i = 1; i <= 150; i++) { IP = "192.168.131." + i; //生成IP字符串 try { InetAddress host; host = InetAddress.getByName(IP); //获取IP封装对象 if (host.isReachable(1000)) { //用1秒的时间测试IP地址是否可达 String hostName = host.getHostName(); System.out.println("IP地址" + IP + "的主机名称是:" + hostName); } } catch (UnknownHostException e) { //捕获未知主机异常 e.printStackTrace(); } catch (IOException e) { //捕获输入输出异常 e.printStackTrace(); } } System.out.println("搜索完毕!"); } }
IP地址192.168.131.1的主机名称是:BigjunOba
搜索完毕!
三、套接字
套接字(Socket)是代表计算机之间网络连接的对象,用于建立计算机之间的TCP连接,使计算机之间可以建立连接并实现网络通信。
1.服务器端套接字
服务器端套接字是SercerSocket类的实例对象,用于实现服务器缓存。ServerSocket类将监视指定的端口,并建立客户端到服务器端套接字的连接,也就是客户负责呼叫任务。
(1)创建服务器端套接字
创建服务器端套接字可以使用4种构造方法。
①ServerScoket()
默认构造方法,可以创建未绑定端口号的服务器套接字。服务器套接字的所有构造方法都需要处理IOException异常。
一般格式为:
try { ServerSocket server = new ServerSocket(); } catch (IOException e) { e.printStackTrace(); }
②ServerScoket(int port)
该构造方法将创建绑定到port参数指定端口的服务器套接字对象,默认的最大连接队列长度为50,也就是说哦如果连接数量超过50个,将不会再接收新的连接请求。
一般格式为:
try { ServerSocket server = new ServerSocket(9527); } catch (IOException e) { e.printStackTrace(); } }
③ServerScoket(int port, int backlog)
使用port参数指定的端口号和backlog参数指定的最大连接长度创建服务器端套接字对象,这个构造方法可以指定超出50的连接数量,如300。
一般格式为:
try { ServerSocket server = new ServerSocket(9527, 300); } catch (IOException e) { e.printStackTrace(); } }
④public ServerSocket(int port, int backlog, InerAddress bindAddr)
使用port参数指定的端口号和backlog参数指定的最大连接队列长度创建服务器端套接字对象。如果服务器有多个IP地址,可以使用bindAddr参数指定创建服务器套接字的地址;如果服务器只有一个IP地址,那么没有必要使用该构造方法。
try {
InetAddress address = InetAddress.getByName("192.168.1.128"); ServerSocket server = new ServerSocket(9527, 300, address); } catch (IOException e) { e.printStackTrace(); } }
(2)接收客户端套接字连接
当服务器建立ServerSocket套接字对象以后,就可以使用该对象的accept()方法接收客户端请求的套接字连接。
语法格式为:
serverSocket.accept();
该方法被调用之后,将等待客户端的连接请求,在接收客户端的套接字连接请求以后,该方法将返回Socket对象,这个Socket对象是已经和客户端建立好连接的套接字,通过这个Socket对象获取客户端的输入输出流来实现数据发送与接收。
该方法可能会产生IOException异常,所以在调用accept()方法时必须捕获并处理该异常。
一般形式为:
try { server,accept(); } catch (IOException e) { e.printStackTrace(); } }
accept()方法将阻塞当前线程,直到接收到客户端的连接请求为止,该方法之后的任何语句都不会执行,必须有客户端发送连接请求;accpet()方法返回Socket套接字以后,当前线程才会继续运行,accpet()方法之后的代码才会被执行。
示例1:System.out.println("已经建立连接!");在server对象接收到客户端的连接请求之前永远都不会执行,这样会导致程序的main主线程阻塞。
package bigjunoba.bjtu.serverSocketTest; import java.io.IOException; import java.net.ServerSocket; public class ServerSocketTest { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(9527); serverSocket.accept(); System.out.println("已经建立连接!"); } catch (IOException e) { e.printStackTrace(); } } }
示例2:将上述语句放在一个新的线程中,作为main主线程的子线程,在新的线程中完成等待客户端连接请求并获取客户端Socket对象的任务,这样就不会阻塞main主线程。
package bigjunoba.bjtu.serverSocketTest; import java.io.IOException; import java.net.ServerSocket; public class ServerSocketTest { public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { try { ServerSocket serverSocket = new ServerSocket(9527); serverSocket.accept(); System.out.println("已经建立连接!"); } catch (IOException e) { e.printStackTrace(); } } }; Thread thread = new Thread(runnable); thread.start(); } }
2.客户端套接字
Socket类是实现客户端套接字的基础。它采用TCP建立计算机之间的连接,并包含了Java语言所有对TCP有关的操作方法,如建立连接、传输数据、断开连接等。
(1)创建客户端套接字
Socket类定义了多个构造方法,它们可以根据InetAddress对象或者字符串指定的IP地址和端口号创建示例。
①Socket(InetAddress address, int port);
使用address参数传递的IP封装对象和port参数指定的端口号创建套接字实例对象。Socket类的构造方法可能会产生UnknownHostException和IOException异常,在使用该构造方法创建Socket对象时必须捕获和处理这两个异常。
一般形式为:
try { InetAddress address = InetAddress.getByName("BigjunOba"); int port = 33; Socket socket = new Socket(address, port); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
②Socket(String host, int port);
使用host参数指定的IP地址字符串和port参数指定的整数类型端口号创建套接字实例对象。
一般形式为:
try { int port = 33; Socket socket = new Socket("192.168.131.1", port); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
③Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
创建一个套接字并将其连接到指定远程地址上的指定远程端口。
一般格式为:
try { InetAddress localHost = InetAddress.getLocalHost(); InetAddress address = InetAddress.getByName("192.168.131.1"); int port1 = 33; int port2 = 44; Socket socket = new Socket(address, port1, localHost, port2) ; } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
④Socket(String host, int port, InetAddress localAddr, int localPort)
创建一个套接字并将其连接到指定远程主机上的指定端口。
一般形式为:
try { InetAddress localHost = InetAddress.getLocalHost(); int port1 = 33; int port2 = 44; Socket socket = new Socket("192.168.131.1", port1, localHost, port2) ; } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
示例1:检测本地计算机中被使用的端口,端口的检测范围是1~256
package bigjunoba.bjtu.clientSocketTest; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; public class CheckPort { public static void main(String[] args) { for (int i = 1; i <= 256; i++) { try { InetAddress localHost = InetAddress.getLocalHost(); Socket socket = new Socket(localHost, i); //如果不产生异常,输出该端口被使用 System.out.println("本机已经使用了端口:" + i); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { // e.printStackTrace(); } } System.out.println("执行完毕!"); }
本机已经使用了端口:135
执行完毕!
(2)发送和接收数据
Socket对象创建成功以后,代表和对方的主机已经建立了连接,可以接受与发送数据了。Socket提供了两个方法分别获取套接字的输入流和输出流,可以将要发送的数据写入输出流,实现发送功能,或者从输入流读取对方发送的数据,实现接受功能。
①接受数据
Socket对象从数据输入流中获取数据,该输入流包含对方发送的数据,这些数据可能是文件、图片、音频或视频。所以,在实现接受数据之前,必须使用getInputStream()方法获取输入流。
语法格式为:
socket.getInputStream()
②发送数据
Socket对象使用输出流,向对方发送数据,所以,在实现数据发送以前,必须使用getOutputStream()方法获取套接字的输出流。
语法格式为:
socket.getOutputStream()
3.创建一些Socket套接字小程序。
示例1:创建服务器Server程序和客户端Client程序,并实现简单的Socket通信程序。
创建Server服务器类:
package bigjunoba.bjtu.CSTest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { try { //创建服务器套接字 ServerSocket server = new ServerSocket(9527); System.out.println("服务器启动完毕!"); //等待客户端连接,返回的是已经连接到服务器的客户端socket对象 Socket socket = server.accept(); System.out.println("客户端已经连接上服务器啦!"); //获取客户端socket对象的输入流 InputStream inputStream = socket.getInputStream(); InputStreamReader reader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(reader); while (true) { String string = bufferedReader.readLine(); //如果接收到exit就退出服务器 if (string.equals("exit")) { break; } //如果接收到的不是exit,就输出接收到的内容 System.out.println("接受内容为:" + string); } System.out.println("连接断开!"); bufferedReader.close(); reader.close(); inputStream.close(); socket.close(); server.close(); } catch (IOException e) { e.printStackTrace(); } } }
创建client客户端类:
package bigjunoba.bjtu.CSTest; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) { try { //创建连接服务器的Socket Socket socket = new Socket("localhost", 9527); //获取Socket输出 OutputStream outputStream = socket.getOutputStream(); //向服务器发送数据(将字符串转化成字节数组) outputStream.write("这是客户端发送给服务器的文字\n".getBytes()); //发送退出信息 outputStream.write("exit\n".getBytes()); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
先启动服务器:
服务器启动完毕!
再启动客户端:
服务器启动完毕!
客户端已经连接上服务器啦!
接受内容为:这是客户端发送给服务器的文字
连接断开!
示例2:示例1中的程序在运行完一次后,程序就会自动结束,因为Socket socket = server.accept();会阻塞当前的main线程,而且客户端连接一次后,程序执行完之后就直接退出了,这样的效果很不好。为了实现客户端能够实时多次发送数据的功能,考虑将Socket socket = server.accept();这行代码更换到一个更适当的位置,并考虑使用多线程技术来实现多次发送的功能,使得程序更加完善。
(1)首先需要解释一下DataInputStream与InputStream的区别:
DataInputStream类继承了InputStream类,比普通的InputStream多了一些方法,DataInputStream、DataOutputStream并没有改变InputStream或OutputStream的行为,读入或写出时的动作还是InputStream、OutputStream负责。DataInputStream、DataOutputStream只是在实现对应的方法时,动态地为它们加上类型判断功能。readUTF()的作用,是从输入流中读取UTF-8编码的数据,并以String字符串的形式返回。writeUTF(value:String)的作用是 :将String字符串转换成UTF-8编码的字节流并写入输出流。
(2)内部类:如果内部类和main方法在同一个类中,那么因为静态方法不能调用动态方法,所以可以将内部类定义成静态static,然后在创建类时就已经new出了实例,因此不需要new语句,调用的时候直接调用即可。
(3)明确服务器端和客户端的发送和接收
客户端:从输入流中接收数据,使用输出流发送数据。即dataOutputStream.writeUTF(line);是客户端发送给服务器的数据,dataInputStream.readUTF()是客户端接收服务器发来的数据。
服务器端:从输入流中接收数据,使用输出流发送数据。即dataOutputStream.writeUTF(replyMessage);是服务器发送给客户端的数据,dataInputStream.readUTF()服务器接收客户端发来的数据。
(3)server服务器端程序修改为:
package bigjunoba.bjtu.CSTest2; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { try { //创建服务器套接字 @SuppressWarnings("resource") ServerSocket server = new ServerSocket(8023); System.out.println("服务器启动完毕!"); while (true) { //等待客户端连接,只有当一个客户端连接上才会返回已经连接到服务器的客户端socket对象 Socket socket = server.accept(); System.out.println("客户端已经连接上服务器啦!"); //客户端连接上服务器后,就开启通信线程 new CommunicationThread(socket).start(); } } catch (IOException e) { e.printStackTrace(); } } public static class CommunicationThread extends Thread{ Socket socket; DataInputStream dataInputStream; DataOutputStream dataOutputStream; public CommunicationThread(Socket socket) { this.socket = socket; try { dataInputStream = new DataInputStream(socket.getInputStream()); dataOutputStream = new DataOutputStream(socket.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } public void run() { super.run(); String message = null; try { //接收从客户端发来的数据 while ((message = dataInputStream.readUTF()) != null) { System.out.println("客户端发来的信息是:" + message); String replyMessage = "OK!我已经收到了:" + message; //发送回应数据给客户端 dataOutputStream.writeUTF(replyMessage); System.out.println("服务器发送给客户端的回应:" + replyMessage); } } catch (IOException e) { e.printStackTrace(); } } } }
(4)client客户端程序修改为:
package bigjunoba.bjtu.CSTest2; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.util.Scanner; public class Client { public static void main(String[] args) { try { @SuppressWarnings("resource") //连接到服务器 Socket socket = new Socket("localhost", 8023); System.out.println("客户端启动完毕!"); DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); @SuppressWarnings("resource") Scanner scanner = new Scanner(System.in); String line = null; listenServerReply(dataInputStream); //读取从键盘输入的第一行 while ((line = scanner.nextLine()) != null) { //发送数据给服务器端 dataOutputStream.writeUTF(line); System.out.println("客户端发送给服务器的信息是:" + line); } } catch (Exception e) { e.printStackTrace(); } } //监听服务器端发送回来的信息 public static void listenServerReply(final DataInputStream dataInputStream) { new Thread() { public void run() { super.run(); String line = null; try { //接收从服务器端发送回来的回应信息 while ((line = dataInputStream.readUTF()) != null) { System.out.println("客户端从服务器接收到的回应是:" + line); } } catch (IOException e) { e.printStackTrace(); } } }.start(); } }
(5)首先启动服务器端,服务器端打印程序为:
服务器启动完毕!
(6)然后启动客户端,
客户端输出为:
客户端启动完毕!
服务器端输出为:
服务器启动完毕!
客户端已经连接上服务器啦!
(7)在客户端控制台出打印一些信息
客户端输出为:
客户端启动完毕!
Hello,你好啊!
客户端发送给服务器的信息是:Hello,你好啊!
客户端从服务器接收到的回应是:OK!我已经收到了:Hello,你好啊!
服务器端输出为:
服务器启动完毕!
客户端已经连接上服务器啦!
客户端发来的信息是:Hello,你好啊!
服务器发送给客户端的回应:OK!我已经收到了:Hello,你好啊!
四、数据报
Java语言可以使用TCP和UDP两种通信协议实现网络通信,其中TCP通信由Socket套接字实现,而UDP通信需要使用DatagramSocket类实现。
和TCP通信不同,UDP传递信息的速度更快,但是没有TCP的高可靠性,当用户通过UDP发送信息之后,无法确定能否正确地传送到目的地。虽然UDP是一种不可靠的通信协议,但是大多数场合并不需要严格的、高可靠性的通信,它们需要的是快速的信息发送,并能容忍一些小的错误,那么使用UDP通信来实现会更合适一些。
UDP将数据打包,也就是通信中所传递的数据包,然后将数据包发送到指定目的地,对方会接收数据包,然后查看数据包中的数据。
1.DatagramPacket
该类是UDP锁传递的数据包,即打包后的数据。数据包用来实现无连接包投递的服务,每个数据包仅根据包中包含的信息从一台计算机传送到另一台计算机,传送的多个包可以选择不同的路由,也可能按不同的顺序到达。
(1)DatagramPacket(byte[] buf, int length)
该构造方法用来创建数据包实例,这个数据包实例将接收长度为length的数据包。
语法格式为:
DatagramPacket(byte[] buf, int length)
(2)DatagramPacket(byte[] buf, int length, InetAddress address, int port)
创建数据包实例,用来将长度为length的数据包发送到address参数指定地址和port参数指定端口号的主机。length参数必须小于等于buf数组的长度。
语法格式为:
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
2.DatagramSocket
该类是用于发送和接收数据的数据包套接字。数据包套接字是数据包传送服务的发送或接收点。要实现UDP通信的数据就必须创建数据包套接字。
常用的构造方法有3个:
(1)DatagramSocket()
默认的构造方法,该构造方法将使用本机任何可用的端口创建数据包套接字实例。在创建DatagramSocket类的实例时,有可能会产生SocketException异常,所以,在创建数据包套接字时,应该捕获并处理该异常。
一般格式为:
try { DatagramSocket datagramSocket = new DatagramSocket(); } catch (SocketException e) { e.printStackTrace(); }
(2)DatagramSocket(int port)
创建数据包套接字并将其绑定到port参数指定的本机端口,端口号取值必须在0~65535.
一般格式为:
try { DatagramSocket datagramSocket = new DatagramSocket(8023); } catch (SocketException e) { e.printStackTrace(); }
(3)DatagramSocket(int port, InetAddress Iaddr)
绑定数据包套接字,将其绑定到Iaddr参数指定的本机地址和port参数指定的本机端口号。本机端口号取值必须在0~65535之间。
一般格式为:
try { InetAddress localHost = InetAddress.getLocalHost(); DatagramSocket datagramSocket = new DatagramSocket(8023, localHost); } catch (SocketException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); }
3.数据报通信程序
示例:使用两个雷实现UDP通信程序设计。
创建服务器端程序:
package bigjunoba.bjtu.UDPTest; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class Server { public static void main(String[] args) { byte[] buf = new byte[1024]; DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length); try { @SuppressWarnings("resource") DatagramSocket datagramSocket = new DatagramSocket(8023); System.out.println("服务器启动完毕!"); datagramSocket.receive(datagramPacket); int length = datagramPacket.getLength(); String message = new String(datagramPacket.getData(), 0, length); String ip = datagramPacket.getAddress().getHostAddress(); System.out.println("从" + ip + "发送来了信息:" + message); } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
创建客户端程序:
package bigjunoba.bjtu.UDPTest; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; public class Client { public static void main(String[] args) { try { InetAddress address = InetAddress.getByName("127.0.0.1"); @SuppressWarnings("resource") DatagramSocket datagramSocket = new DatagramSocket(); System.out.println("客户端启动完毕!"); byte[] data = "hello,我是客户端,我来访问服务器了!".getBytes(); DatagramPacket datagramPacket = new DatagramPacket(data, data.length, address, 8023); datagramSocket.send(datagramPacket); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
先启动服务器程序,然后再启动客户端程序,服务器控制台的输出为:
服务器启动完毕!
从127.0.0.1发送来了信息:hello,我是客户端,我来访问服务器了!
五、网络聊天程序开发
使用Swing设置程序UI界面,并结合Java语言多线程技术使网络聊天程序更加符合实际需求,即可以不间断地收发多条信息。
(1)创建ClientFrame类,该类包含多个成员变量。