Java最初是作为网络编程语言出现的,其对网络提供了高度的支持,使得客户端和服务器的沟通成为了现实。
而在网络编程中使用最多的就是Socket。Java提供了一组功能强大的网络开发类。
一.网络基础知识:
1.两台计算机通过网络进行通信,需要满足一些必备的条件:
(1)IP地址:为实现网络中不同计算机之间的通信,每台机器都必须有一个唯一的标识。用来表示所处的身份,所在的位置。
IP地址格式:数字型,如:192.168.0.1
(2)协议:共同的语言用来交流。
(3)端口号:一台主机上可以同时运行多个应用程序,用于区分不同应用程序的通信。
端口号范围为:0~65535,其中0~1023为系统所保留。
常用端口号:http 80 ftp 21 telnet 23
2.TCP/IP协议
- TCP/IP是目前世界上应用最为广泛的协议。是以TCP和IP为基础的不同层次上多个协议的集合。也称TCP/IP协议族或TCP/IP协议栈;
- TCP:Transmission Control Protocol 传输控制协议
- IP:Internet Protocol 互联网协议
3.TCP/IP模型
5层模型:
应用层:如HTTP超文件传输协议、FTP文件传输协议、SMTP简单邮件传输协议、Telnet远程登录服务
传输层:如TCP/IP协议
网络层
数据链路层
物理层:用户最直观接触到的,如网线、网卡等
4.Socket
IP地址和端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础。
5.JAVA中的网络支持
针对网络通信的不同层次,JAVA提供的网络功能有四大类:
(1)InetAddress:用于标识网络上的硬件资源(标识IP地址相关信息)。
(2)URL:统一资源定位符 ,通过URL可以直接读取或写入网络上的数据(用来表示网络上的资源)。
(3)Sockets:使用TCP协议实现网络通信的Socket相关的类。
(4)Datagram:使用UDP协议,将数据保存在数据报中,通过网络进行通信。
二.Java中网络相关的API的应用
1.InetAddress类的应用
InetAddress类用于标识网络上的硬件资源,表示互联网协议 (IP)地址。
- 可以查看jdk8文档:http://www.matools.com/api/java8 查看更多信息。
InetAddress类没有构造方法,所以不能直接new对象,但可以根据其提供的静态方法获取实例。
package function.socket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
/**
* InetAddress类的使用
*
* @author kimtian
*/
public class InetAddressOperate {
public static void main(String[] args) throws UnknownHostException {
//获取本机的InetAddress实例
InetAddress localAddress = InetAddress.getLocalHost();
System.out.println("获取当前计算机的名称:" + localAddress.getHostName());
System.out.println("获取主机的IP地址:" + localAddress.getHostAddress());
//获取字节数组形式的IP地址
byte[] bytes = localAddress.getAddress();
System.out.println("字节数组形式的IP:" + Arrays.toString(bytes));
//直接输出InetAddress对象--会输出用户名和IP地址
System.out.println(localAddress);
//根据主机名获取InetAddress实例
InetAddress inetAddress = InetAddress.getByName("KIMTIAN-MC0");
System.out.println(inetAddress);
//根据IP地址获取相应的实例信息
InetAddress inetAddress2 = InetAddress.getByName("10.43.19.128");
System.out.println(inetAddress2);
}
}
2.URL的应用
(1)URL(Uniform Resource Locator):统一资源定位符,表示Internet上某一资源的地址。
- URL由两部分组成:协议名称和资源名称,中间用冒号隔开。
- 在java.net包中,提供了URL类来表示URL。
- URL类表示统一资源定位符,指向互联网上的“资源”的指针。
package function.socket;
import java.net.MalformedURLException;
import java.net.URL;
/**
* URL类的基本操作
*
* @author kimtian
*/
public class UrlOperateOne {
public static void main(String[] args) {
try {
//创建一个URL实例
URL gdt = new URL("https://e.qq.com");
//?号后面表示参数 #号后面表示锚点
URL url = new URL(gdt, "/resources?username=kimtian#test");
//获取相关信息====================
System.out.println("协议信息:" + url.getProtocol());
System.out.println("主机信息:" + url.getHost());
//没有指定端口号,则则根据协议的不同使用默认的端口号,此时getPort()方法返回值为-1
System.out.println("端口信息:" + url.getPort());
System.out.println("文件路径:" + url.getPath());
//文件路径
System.out.println("文件名称:" + url.getFile());
//锚点
System.out.println("相对路径:" + url.getRef());
//参数
System.out.println("查询字符串:" + url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
(2)使用URL读取网页内容
- 通过URL对象的openStream()方法可以得到指定资源的输入流。
- 通过输入流可以读取、访问网络上的数据。
package function.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
/**
* 使用URL读取网页内容
*
* @author kimtian
*/
public class UrlOperateTwo {
public static void main(String[] args) {
try {
//创建一个URL实例
URL url = new URL("https://e.qq.com/ads/");
//通过URL的openStream方法获取URL对象所表示的资源的字节输入流
InputStream inputStream = url.openStream();
//将字节输入流转化为字符输入流,可以指定编码
InputStreamReader isr = new InputStreamReader(inputStream, "utf-8");
//为字符输入流添加缓冲
BufferedReader br = new BufferedReader(isr);
//读取数据
String data = br.readLine();
while (data != null) {
//输出数据
System.out.println(data);
//读取下一行数据
data = br.readLine();
}
//关闭资源
br.close();
isr.close();
inputStream.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
三.Socket
1.通过Socket实现TCP编程
(1)TCP协议:TCP协议是面向连接、可靠的、有序的,以字节流的方式发送数据。在数据通讯前需要建立连接,保证数据通讯的有效性。
(2)基于TCP协议实现网络通信的类:
- 客户端Socket类
- 服务器端的ServerSocket类
(3)Socket通信实现原理:
1)服务端倾听Socket:建立一个ServerSocket(服务器Socket),绑定相应的端口,并且在指定端口进行倾听,等待客户端的连接。
2)客户端创建Socket,并向服务器端发送请求。
3)服务器收到请求,并接受客户端请求信息。
4)接收请求后,建立连接,创建连接Socket,用来与客户端Socket进行通信。
5)通过相关的InpuStream和OutputStream进行数据的交换、数据的发送、接受以及数据的响应等。
6)分别关闭两端的Socket以及相关资源,进行通信的断开。
(4)Socket通信实现步骤:
1)创建ServerSocket和Socket;
2)打开连接到Socket的输入/输出流;
3)按照协议对Socket进行读/写操作;
4)关闭输入、输出流,关闭Socket。
(5)ServerSocket类:此类实现服务器套接字,服务器套接字等待请求通过网络传入。
package function.socket.tcp;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 基于TCP协议的Socket通信,实现用户登录
* 服务器端
*
* @author kimtian
*/
public class Server {
/**
* 服务器端实现步骤:
* 1.创建ServerSocket对象,绑定监听端口
* 2.通过accept()方法监听客户端请求
* 3.建立连接后,通过输入流读取客户端发送的请求信息
* 4.通过输出流向客户端发送响应信息
* 5.关闭相关资源
*/
public static void main(String[] args) {
try {
//1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
ServerSocket serverSocket = new ServerSocket(8888);
//2.调用accept()方法开始监听,等待客户端的连接
System.out.println("********服务器即将启动,等待客户端的连接**********");
//一旦调用后处于阻塞情况,等待客户端监听
Socket socket = serverSocket.accept();
//3.获取输入流,并读取客户端信息
//字节输入流
InputStream is = socket.getInputStream();
//将字节流转化为字符流
InputStreamReader isr = new InputStreamReader(is);
//为输入流添加缓冲
BufferedReader br = new BufferedReader(isr);
String info = null;
//循环读取客户端信息
while ((info = br.readLine()) != null) {
System.out.println("我是服务器,客户端说:" + info);
}
//关闭输入流
socket.shutdownInput();
//4.获取输出流,响应客户端的请求
OutputStream os = socket.getOutputStream();
//将输出流包装成打印流
PrintWriter pw = new PrintWriter(os);
//要发送的信息
pw.write("欢迎您!我已经接受到了你的请求了。");
//调用flush()方法将缓存输出
pw.flush();
//关闭当前Socket的输出流
socket.shutdownOutput();
//5.关闭相关资源
pw.close();
os.close();
br.close();
is.close();
isr.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
(6)Socket类:此类实现客户端的套接字。套接字是两台机器间通信的端点。
package function.socket.tcp;
import java.io.*;
import java.net.Socket;
/**
* 客户端
*
* @author kimtian
*/
public class Client {
/**
* 客户端:
* 1.创建Socket对象,指明需要连接的服务器的地址和端口号
* 2.建立连接后,通过输出流向服务器端发送请求信息
* 3.通过输入流获取服务器响应的信息
* 4.关闭相关资源
*/
public static void main(String[] args) {
try {
//1.创建客户端Socket,指定服务器地址和端口
Socket socket = new Socket("127.0.0.1", 8888);
//2.获取输出流,向服务器端发送信息
//获取的是字节输出流
OutputStream os = socket.getOutputStream();
//将输出流包装成打印流
PrintWriter pw = new PrintWriter(os);
//要发送的信息
pw.write("username:kimtian;password:123");
//去刷新缓存,向服务器端发送信息
pw.flush();
//关闭当前Socket的输出流
socket.shutdownOutput();
//3.获取输入流,并读取服务端响应信息
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String info = null;
//循环读取服务器端信息
while ((info = br.readLine()) != null) {
System.out.println("我是客户端,服务器端说:" + info);
}
//4.关闭相关资源
br.close();
is.close();
isr.close();
//对于同一个socket,如果关闭了输出流,则与该输出流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket即可
//pw.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端和服务器端通信运行结果如下图:
在运行时一定要先运行服务器端,再运行客户端。
服务器端结果:
客户端结果:
(7)使用多线程实现多客户端之间的通信
多线程服务器实现类:
package function.socket.tcp;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 多线程服务器
*
* @author kimtian
*/
public class MultiThreadedServer {
/**
* 1.服务器端创建ServerSocket,循环调用accept()等待客户端连接
* 2.客户端创建一个socket并请求和服务器端连接
* 3.服务器端接受客户端请求,创建socket与该客户建立专线连接
* 4.建立连接的两个socket在一个单独的线程上对话
* 5.服务器端继续等待新的连接
*/
public static void main(String[] args) {
try {
//1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
ServerSocket serverSocket = new ServerSocket(8888);
//2.调用accept()方法开始监听,等待客户端的连接
System.out.println("********服务器即将启动,等待客户端的连接**********");
Socket socket = null;
//定义一个变量记录客户端的数量
int count = 0;
//循环监听等待客户端监听
while (true) {
socket = serverSocket.accept();
//创建一个新的线程
ServerThread serverThread = new ServerThread(socket);
//设置线程的优先级,范围为[1,10],默认为5
//未设置优先级可能会导致运行时速度非常慢,可降低优先级
serverThread.setPriority(4);
//启动线程
serverThread.start();
//统计客户端的数量
count++;
System.out.println("客户端的数量为:" + count);
InetAddress inetAddress = socket.getInetAddress();
System.out.println("当前客户端的ip为:"+inetAddress);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器端线程处理类:
package function.socket.tcp;
import java.io.*;
import java.net.Socket;
/**
* 服务器端线程处理类
*
* @author kimtian
*/
public class ServerThread extends Thread {
/**
* 和本线程相关的Socket
*/
Socket socket = null;
public ServerThread(Socket socket) {
this.socket = socket;
}
/**
* 线程执行的操作,响应客户端的请求
*/
@Override
public void run() {
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
OutputStream os = null;
PrintWriter pw = null;
try {
//获取输入流,并读取客户端信息
//字节输入流
is = socket.getInputStream();
//将字节流转化为字符流
isr = new InputStreamReader(is);
//为输入流添加缓冲
br = new BufferedReader(isr);
String info = null;
//循环读取客户端信息
while ((info = br.readLine()) != null) {
System.out.println("我是服务器,客户端说:" + info);
}
//关闭输入流
socket.shutdownInput();
//获取输出流,响应客户端的请求
os = socket.getOutputStream();
//将输出流包装成打印流
pw = new PrintWriter(os);
//要发送的信息
pw.write("欢迎您!我已经接受到了你的请求了。");
//调用flush()方法将缓存输出
pw.flush();
//关闭当前Socket的输出流
socket.shutdownOutput();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭相关资源
if (pw != null) {
pw.close();
}
if (os != null) {
os.close();
}
if (br != null) {
br.close();
}
if (is != null) {
is.close();
}
if (isr != null) {
isr.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果如下:
2.通过Socket实现UDP编程
(1)UDP协议:用户数据报协议是无连接、不可靠、无序的。
(2)UDP协议以数据报作为数据传输的载体。进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram),在数据报中指明数据所要达到的Socket(主机地址和端口号),然后再将数据报发送出去。
(3)相关操作类:
- DatagramPacket:表示数据报包,表示UDP通信中的数据单元。
- DatagramSocket:进行端到端通信的类。实现基于UDP的Socket通信。
(4)服务器端实现:
package function.socket.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP服务器端,实现基于UDP的用户登录
*
* @author kimtian
*/
public class UDPServer {
/**
* 服务器端实现方式:
* 1.创建DatagranSocket,指定端口号。
* 2.创建DatagramPacket。
* 3.接收客户端发送的数据信息。
* 4.读取数据。
*
* @param args
*/
public static void main(String[] args) {
try {
//1.创建服务器端DatagranSocket,指定端口号。
DatagramSocket datagramSocket = new DatagramSocket(9999);
//2.创建数据报,用于接收客户端发送的数据
//创建字节数组,指定接收的数据包的大小
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
//3.接收客户端发送的数据
//在阻塞之前进行提示,服务器端已经启动
System.out.println("******UDP服务器端已经启动,等待客户端发送数据******");
//此方法在接收到数据报之前一直处于阻塞状态,等待接收客户端发送的数据
datagramSocket.receive(datagramPacket);
//4.读取数据
//长度为接收到的长度datagramPacket.getLength()
String info = new String(bytes, 0, datagramPacket.getLength());
System.out.println("我是服务器,客户端说" + info);
/**
* 向客户端响应数据
*/
//1.定义客户端的地址、端口号、数据
InetAddress address = datagramPacket.getAddress();
int port = datagramPacket.getPort();
byte[] data = "欢迎您,客户端".getBytes();
//2.创建数据报,包含响应的数据信息
DatagramPacket datagramPacket1 = new DatagramPacket(data, data.length, address, port);
//3.响应客户端
datagramSocket.send(datagramPacket1);
//4.关闭资源
datagramSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
(5)客户端实现:
package function.socket.udp;
import java.io.IOException;
import java.net.*;
/**
* UDP客户端
*
* @author kimtian
*/
public class UDPClient {
/**
* 1.定义发送信息。
* 2.创建DatagramPacket,包含将要发送的信息。
* 3.创建DatagranSocket对象,实现数据对象的发送。
* 4.发送数据。
*
* @param args
*/
public static void main(String[] args) throws IOException {
//1.定义服务器的地址、端口号、数据
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
int port = 9999;
byte[] bytes = "username:kimtian;password:123".getBytes();
//2.创建数据报,包含发送的数据信息
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, inetAddress, port);
//3.创建DatagramSocket对象
DatagramSocket datagramSocket = new DatagramSocket();
//4.向服务器端发送数据报
datagramSocket.send(datagramPacket);
/**
* 接收服务器端响应信息
*/
//1.创建数据报,用于接收服务器端响应的数据
byte[] data = new byte[1024];
DatagramPacket datagramPacket1 = new DatagramPacket(data, data.length);
//2.接收服务器响应的数据
datagramSocket.receive(datagramPacket1);
//3.读取服务器端响应的数据信息
String reply = new String(data, 0, datagramPacket1.getLength());
System.out.println("我是客户端,服务器说:" + reply);
//4.关闭资源
datagramSocket.close();
}
}
(6)使用多线程实现多客户端之间的通信