本文是课上资料的总结非原创没有转载地址
Java网络编程
什么是网络
- 由点和线构成,表示诸多对象间的相互联系。
计算机网络
- 为实现资源共享和信息传递,通过通信线路连接起来的若干主机(Host)。
- 网络分类:广域网、城域网、局域网
- 互联网:(Internet)点与点相连;
- 万维网:(WWW - World Wide Web)端与端相连;
- 物联网:(IoT - Internet of things)物与物相连;
- 网络编程:让计算机与计算机之间建立连接、进行通信。
网络模型
OSI参考模型(七层)
- ISO组织发布的OSI(Oper System Interconnection)开放式系统互联。
- 层层独立、下层为上层服务
- 第七层(应用层):应用层负责文件访问和管理、可靠运输服务、远程操作服务。(HTTP、FTP、SMTP)
- 第六层(表示层):表示层负责定义转换数据格式及加密,允许选择以二进制或ASCII格式传输。
- 第五层(会话层):会话层负责使应用建立和维持会话,使通信在失效时继续恢复通信。(断点续传)
- 第四层(传输层):传输层负责是否选择差错恢复协议、数据流重用、错误顺序重排。(TCP、UDP)
- 第三层(网络层):网络层负责定义了能够标识所有网络节点的逻辑地址。(IP地址)
- 第二层(链路层):链路层在物理层上,通过规程或协议(差错控制)来控制传输数据的正确性。(MAC)
- 第一层(物理层):物理层为设备之间的数据通信提供传输信号和物理介质。(双绞线、光导纤维)
TCP/IP参考模型(四层)
- 一组用于实现网络互连的通信协议,将协议分层四个层次。
- 第四层(应用层):应用层负责传送各种最终形态的数据,是直接与用户打交道的层,典型协议是HTTP、FTP等。
- 第三层(传输层):传输层负责传送文本数据,主要协议是TCP、UDP协议。
- 第二层(网络层):网络层负责分配地址和传送二进制数据,主要协议是IP协议。
- 第一层(接口层):接口层负责建立电路连接,是整个网络的物理基础典型的协议包括以太网、ADSL等等。
OSI与TCP/IP区别
- TCP/IP支持快层封装;OSI不支持
- TCP/IP仅仅支持IP网络协议;OSI支持多种网络层协议(IP、IPX、APPLE、TALK、NOVELLNSAP)
通信协议
TCP协议(Transmission Control Protocol):传输控制协议【重点】
是一种面向连接的、可靠的、基于字节流的传输层通信协议。数据大小无限制。建立连接的过程需要三次握手,断开连接的过程需要四次挥手。
- 三次握手【重点】
- 四次挥手【重点】
UDP协议(User Datagram Protocol):用户数据报协议
- 是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,每个包的大小64KB。
- UDP支持单播、广播、多播发送。
- 单播Unicast:每次只向一台主机发送;
- 广播Broadcast:每次向同一网络中所有主机发送;
- 多播Multicast:只向属于同一组中的主机发送。
TCP与UDP区别
- TCP提供面向连接的传输,通信前要先建立连接(三次握手机制);UDP提供无连接的传输,通信前不需要建立连接。
- TCP提供可靠的传输(有序、无差错、不丢失、不重复);UDP提供不可靠的传输。
- TCP面向字节流的传输,因此它能将信息分割成组,并在接收端将其重组;UDP是面向数据报的传输,没有分组开销。
- TCP传输速度慢, UDP传输数据快(实际上现在TCP并不比UDP慢多少)
- TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
- TCP提供拥塞控制和流量控制机制;UDP不提供拥塞控制和流量控制机制。
IP与端口
IP
-
IP协议:Internet Protocol Address 互联网协议地址/网际协议地址
- 分配给互联网设备的数字标签(唯一标识),IP地址。
-
IP地址分为两种:
- IPV4:4字节32位整数,并分成4段8位的二进制数,每8位之间用圆点隔开,每8位整数可以转换为一个0~255的十进制整数。
格式:D.D.D.D 例如:255.255.255.255- IPV4的应用分类:
- A类:政府机构,1.0.0.1~126.255.255.254
- B类:中型企业,128.0.0.1~191.255.255.254
- C类:个人用户,192.0.0.1~223.255.255.254
- D类:用于组播,224.0.0.1~239.255.255.254
- E类:用于实验,240.0.0.1~255.255.255.254
- 回环地址:127.0.0.1,指本机,一本用于测试使用。
- IPV4的应用分类:
- IPV6:16字节128位整数,并分成8段十六进制数,每16位之间用圆点隔开,每16位整数可以转换为一个0~65535的十进制数。
格式:X.X.X.X.X.X.X.X 例如:FFFF.FFFF.FFFF.FFFF.FFFF.FFFF.FFFF.FFFF - 查看IP命令:ipconfig(windows)、ifconfig(liunx)
- 测试IP命令:ping D.D.D.D
- IPV4:4字节32位整数,并分成4段8位的二进制数,每8位之间用圆点隔开,每8位整数可以转换为一个0~255的十进制整数。
Post(端口号)
- 端口号:在通信实体上进行网络通信的程序的唯一标识。
- 区分不同的网络程序,一个端口号同一时刻只能被一个网络程序使用。
- 端口分类:
- 公认端口:0~1023
- 注册端口:1024~49151
- 动态或私有端口:49152~65535
- 常用端口:
- MySQL:3306
- Oracle:1521
- Tomcat:8080
- SMTP:25
- Web服务器:80
- FTP服务器:21
- 查看端口号的命令:
netstat -ano
网络编程
InetAddress类
- JavaAPI中介绍:此类表示互联网协议 (IP) 地址。
- 概念:表示互联网协议(IP)地址对象,封装了与该IP地址相关的所有信息,
- 并提供获取信息的常用方法。
- 方法:
方法声明 | 方法描述 |
---|---|
boolean equals(Object obj) | 将此对象与指定对象比较。 |
byte[] getAddress() | 返回此 InetAddress 对象的原始 IP 地址。 |
static InetAddress[] getAllByName(String host) | 在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。 |
static InetAddress getByAddress(byte[] addr) | 在给定原始 IP 地址的情况下,返回 InetAddress 对象。 |
static InetAddress getByAddress(String host, byte[] addr) | 根据提供的主机名和 IP 地址创建 InetAddress。 |
static InetAddress getByName(String host) | 在给定主机名的情况下确定主机的 IP 地址。 |
String getCanonicalHostName() | 获取此 IP 地址的完全限定域名。 |
String getHostAddress() | 返回 IP 地址字符串(以文本表现形式)。 |
String getHostName() | 获取此 IP 地址的主机名。 |
static InetAddress getLocalHost() | 返回本地主机。 |
int hashCode() | 返回此 IP 地址的哈希码。 |
boolean isAnyLocalAddress() | 检查 InetAddress 是否是通配符地址的实用例行程序。 |
boolean isLinkLocalAddress() | 检查 InetAddress 是否是链接本地地址的实用例行程序。 |
boolean isLoopbackAddress() | 检查 InetAddress 是否是回送地址的实用例行程序。 |
boolean isMCGlobal() | 检查多播地址是否具有全局域的实用例行程序。 |
boolean isMCLinkLocal() | 检查多播地址是否具有链接范围的实用例行程序。 |
boolean isMCNodeLocal() | 检查多播地址是否具有节点范围的实用例行程序。 |
boolean isMCOrgLocal() | 检查多播地址是否具有组织范围的实用例行程序。 |
boolean isMCSiteLocal() | 检查多播地址是否具有站点范围的实用例行程序。 |
boolean isMulticastAddress() | 检查 InetAddress 是否是 IP 多播地址的实用例行程序。 |
boolean isReachable(int timeout) | 测试是否可以达到该地址。 |
boolean isReachable(NetworkInterface netif, int ttl, int timeout) | 测试是否可以达到该地址。 |
boolean isSiteLocalAddress() | 检查 InetAddress 是否是站点本地地址的实用例行程序。 |
String toString() | 将此 IP 地址转换为 String。 |
public class InetAddressDemo {
public static void main(String[] args) throws Exception {
// 1.获得本地主机地址对象
// 1.1getLocalHost();
InetAddress localAddress = InetAddress.getLocalHost();
System.out.println("利用getLocalHost()查询本机的IP地址:" + localAddress.getHostAddress() + " 本机的主机名:" + localAddress.getHostName());
// 1.2根据本机ip地址查询本机
//InetAddress localAddress1 = InetAddress.getByName("192.168.169.1");
InetAddress localAddress1 = InetAddress.getByAddress(new byte[]{(byte) 192, (byte) 168, (byte) 169, 1});
System.out.println("根据IP地址查询本机的IP地址:" + localAddress1.getHostAddress() + " 本机的主机名:" + localAddress1.getHostName());
// 1.3根据本机名查询本机
InetAddress localAddress2 = InetAddress.getByName("DESKTOP-AL2FJ06");
System.out.println("根据本机名查询本机的IP地址:" + localAddress2.getHostAddress() + " 本机的主机名:" + localAddress2.getHostName());
// 1.4 127.0.0.1查询本机
//InetAddress localAddress3 = InetAddress.getByName("127.0.0.1");
InetAddress localAddress3 = InetAddress.getByAddress(new byte[]{127, 0, 0, 1});
System.out.println("127.0.0.1查询本机的IP地址:" + localAddress3.getHostAddress() + " 本机的主机名:" + localAddress3.getHostName());
// 1.5 localhost查询本机
InetAddress localAddress4 = InetAddress.getByName("localhost");
System.out.println("localhost查询本机的IP地址:" + localAddress4.getHostAddress() + " 本机的主机名:" + localAddress4.getHostName());
System.out.println("-------------------");
// 2.局域网(同一局域网)
// 2.1根据ip
InetAddress remoteAddress1 = InetAddress.getByAddress(new byte[]{10, 9, 31, (byte) 141});
System.out.println("根据IP地址查询的IP地址:" + remoteAddress1.getHostAddress() + " 主机名:" + remoteAddress1.getHostName());
// 2.2根据主机名
InetAddress remoteAddress2 = InetAddress.getByName("打虎滴武松");
System.out.println("根据主机名查询的IP地址:" + remoteAddress2.getHostAddress() + " 主机名:" + remoteAddress2.getHostName());
System.out.println("-------------------");
// 3.外网
// 3.1根据IP地址查询
//InetAddress remoteAddress3 = InetAddress.getByName("61.135.169.141);
InetAddress remoteAddress3 = InetAddress.getByAddress(new byte[]{61, (byte) 135, (byte) 169, (byte) 141});
System.out.println("根据IP地址查询的IP地址:" + remoteAddress3.getHostAddress() + " 主机名:" + remoteAddress3.getHostName());
// 3.2根据域名查询
InetAddress remoteAddress4 = InetAddress.getByName("www.baidu.com");
System.out.println("根据域名查询的IP地址:" + remoteAddress4.getHostAddress() + " 主机名:" + remoteAddress4.getHostName());
System.out.println("-------------------");
// 3.3根据域名查询所有IP地址
InetAddress[] inetAddresses = InetAddress.getAllByName("www.baidu.com");
for (InetAddress inetAddress : inetAddresses) {
System.out.println(inetAddress.getHostAddress());
}
}
}
基于TCP的网络编程(Transmission Control Protocol)
- Socket编程:
- Socket(套接字)是网络中的一个通信节点、是操作系统提供的一种通信机制。
- 分为客服端Socket与服务器ServerSocket。
- 通信要求:IP地址 + 端口号。
Java通信是在操作心态的Socket基础上的一种封装。
- TCP的Socket:分为服务器套接字和客户端套接字
- ServerSocket:服务器套接字,监听客户端的连接,不能实现通信
- Socket:客户端套接字,实现通信
Socket(套接字)与ServerSocket(服务器套接字)
ServerSocket
- JavaAPI中介绍:此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。
服务器套接字的实际工作由 SocketImpl 类的实例执行。应用程序可以更改创建套接字实现的套接字工厂来配置它自身,从而创建适合本地防火墙的套接字。 - 构造方法
方法 | 描述 |
---|---|
ServerSocket() | 创建非绑定服务器套接字。 |
ServerSocket(int port) | 创建绑定到特定端口的服务器套接字。 |
ServerSocket(int port, int backlog) | 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。 |
ServerSocket(int port, int backlog, InetAddress bindAddr) | 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。 |
- 方法
方法声明 | 方法描述 |
---|---|
Socket accept() | 侦听并接受到此套接字的连接。 |
void bind(SocketAddress endpoint) | 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。 |
void bind(SocketAddress endpoint, int backlog) | 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。 |
void close() | 关闭此套接字。 |
ServerSocketChannel getChannel() | 返回与此套接字关联的唯一 ServerSocketChannel 对象(如果有)。 |
InetAddress getInetAddress() | 返回此服务器套接字的本地地址。 |
int getLocalPort() | 返回此套接字在其上侦听的端口。 |
SocketAddress getLocalSocketAddress() | 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。 |
int getReceiveBufferSize() | 获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是将用于从此 ServerSocket 接受的套接字的建议缓冲区大小。 |
boolean getReuseAddress() | 测试是否启用 SO_REUSEADDR。 |
int getSoTimeout() | 获取 SO_TIMEOUT 的设置。 |
protected void implAccept(Socket s) | ServerSocket 的子类使用此方法重写 accept() 以返回它们自己的套接字子类。 |
boolean isBound() | 返回 ServerSocket 的绑定状态。 |
boolean isClosed() | 返回 ServerSocket 的关闭状态。 |
void setPerformancePreferences(int connectionTime, int latency, int bandwidth) | 设置此 ServerSocket 的性能首选项。 |
void setReceiveBufferSize(int size) | 为从此 ServerSocket 接受的套接字的 SO_RCVBUF 选项设置默认建议值。 |
void setReuseAddress(boolean on) | 启用/禁用 SO_REUSEADDR 套接字选项。 |
static void setSocketFactory(SocketImplFactory fac) | 为应用程序设置服务器套接字实现工厂。 |
void setSoTimeout(int timeout) | 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。 |
String toString() | 作为 String 返回此套接字的实现地址和实现端口。 |
Socket
- JavaAPI中介绍:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
套接字的实际工作由 SocketImpl 类的实例执行。应用程序通过更改创建套接字实现的套接字工厂可以配置它自身,以创建适合本地防火墙的套接字。 - 构造方法
方法 | 描述 |
---|---|
Socket() | 通过系统默认类型的 SocketImpl 创建未连接套接字 |
Socket(InetAddress address, int port) | 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 |
Socket(InetAddress host, int port, boolean stream) | 已过时。 Use DatagramSocket instead for UDP transport. |
Socket(InetAddress address, int port, InetAddress localAddr, int localPort) | 创建一个套接字并将其连接到指定远程地址上的指定远程端口。 |
Socket(Proxy proxy) | 创建一个未连接的套接字并指定代理类型(如果有),该代理不管其他设置如何都应被使用。 |
protected Socket(SocketImpl impl) | 使用用户指定的 SocketImpl 创建一个未连接 Socket。 |
Socket(String host, int port) | 创建一个流套接字并将其连接到指定主机上的指定端口号。 |
Socket(String host, int port, boolean stream) | 已过时。 使用 DatagramSocket 取代 UDP 传输。 |
Socket(String host, int port, InetAddress localAddr, int localPort) | 创建一个套接字并将其连接到指定远程主机上的指定远程端口。 |
- 方法
方法声明 | 方法描述 |
---|---|
void bind(SocketAddress bindpoint) | 将套接字绑定到本地地址。 |
void close() | 关闭此套接字。 |
void connect(SocketAddress endpoint) | 将此套接字连接到服务器。 |
void connect(SocketAddress endpoint, int timeout) | 将此套接字连接到服务器,并指定一个超时值。 |
SocketChannel getChannel() | 返回与此数据报套接字关联的唯一 SocketChannel 对象(如果有)。 |
InetAddress getInetAddress() | 返回套接字连接的地址。 |
InputStream getInputStream() | 返回此套接字的输入流。 |
boolean getKeepAlive() | 测试是否启用 SO_KEEPALIVE。 |
InetAddress getLocalAddress() | 获取套接字绑定的本地地址。 |
int getLocalPort() | 返回此套接字绑定到的本地端口。 |
SocketAddress getLocalSocketAddress() | 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。 |
boolean getOOBInline() | 测试是否启用 OOBINLINE。 |
OutputStream getOutputStream() | 返回此套接字的输出流。 |
int getPort() | 返回此套接字连接到的远程端口。 |
int getReceiveBufferSize() | 获取此 Socket 的 SO_RCVBUF 选项的值,该值是平台在 Socket 上输入时使用的缓冲区大小。 |
SocketAddress getRemoteSocketAddress() | 返回此套接字连接的端点的地址,如果未连接则返回 null。 |
boolean getReuseAddress() | 测试是否启用 SO_REUSEADDR。 |
int getSendBufferSize() | 获取此 Socket 的 SO_SNDBUF 选项的值,该值是平台在 Socket 上输出时使用的缓冲区大小。 |
int getSoLinger() | 返回 SO_LINGER 的设置。 |
int getSoTimeout() | 返回 SO_TIMEOUT 的设置。 |
boolean getTcpNoDelay() | 测试是否启用 TCP_NODELAY。 |
int getTrafficClass() | 为从此 Socket 上发送的包获取 IP 头中的流量类别或服务类型。 |
boolean isBound() | 返回套接字的绑定状态。 |
boolean isClosed() | 返回套接字的关闭状态。 |
boolean isConnected() | 返回套接字的连接状态。 |
boolean isInputShutdown() | 返回是否关闭套接字连接的半读状态 (read-half)。 |
boolean isOutputShutdown() | 返回是否关闭套接字连接的半写状态 (write-half)。 |
void sendUrgentData(int data) | 在套接字上发送一个紧急数据字节。 |
void setKeepAlive(boolean on) | 启用/禁用 SO_KEEPALIVE。 |
void setOOBInline(boolean on) | 启用/禁用 OOBINLINE(TCP 紧急数据的接收者) 默认情况下,此选项是禁用的,即在套接字上接收的 TCP 紧急数据被静默丢弃。 |
void setPerformancePreferences(int connectionTime, int latency, int bandwidth) | 设置此套接字的性能偏好。 |
void setReceiveBufferSize(int size) | 将此 Socket 的 SO_RCVBUF 选项设置为指定的值。 |
void setReuseAddress(boolean on) | 启用/禁用 SO_REUSEADDR 套接字选项。 |
void setSendBufferSize(int size) | 将此 Socket 的 SO_SNDBUF 选项设置为指定的值。 |
static void setSocketImplFactory(SocketImplFactory fac) | 为应用程序设置客户端套接字实现工厂。 |
void setSoLinger(boolean on, int linger) | 启用/禁用具有指定逗留时间(以秒为单位)的 SO_LINGER。 |
void setSoTimeout(int timeout) | 启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位。 |
void setTcpNoDelay(boolean on) | 启用/禁用 TCP_NODELAY(启用/禁用 Nagle 算法)。 |
void setTrafficClass(int tc) | 为从此 Socket 上发送的包在 IP 头中设置流量类别 (traffic class) 或服务类型八位组 (type-of-service octet)。 |
void shutdownInput() | 此套接字的输入流置于“流的末尾”。 |
void shutdownOutput() | 禁用此套接字的输出流。 |
String toString() | 将此套接字转换为 String。 |
开发步骤
- 建立通信连接(会话):
- 创建ServerSocket,指定端口号
- 调用accept等待客户端接入
- 客户端请求服务器:
- 创建Socket,指定服务器IP + 端口号
- 使用输出流,发送请求数据给服务器
- 使用输入流,接收响应数据到客户端(等待)
- 服务器响应客户端:
- 使用输入流,接受请求数据到服务器(等待)
- 使用输出流,发出响应数据给客户端
案例一 使用TCP实现客户端发送数据给服务器并回复
- Tcp协议服务器端程序代码
/**
* 基于Tcp协议的服务器端程序
* 步骤:
* (1)创建服务器套接字,并指定端口号
* (2)服务器侦听(监听)客户端的连接,返回客户端套接字
* (3)获取输入输出流,实现通信
* (4)处理数据并回复
* (5)关闭
* Socket:套接字(通信的端点(IP地址、端口号)),是操作系统提供的一种通信机制
* Java通信是在操作心态的Socket基础上的一种封装。
* Socket:分为服务器套接字和客户端套接字
* ServerSocket:服务器套接字,监听客户端的连接,不能实现通信
* Socket:客户端套接字,实现通信
*/
public class TcpServer {
public static void main(String[] args) throws Exception {
//(1)创建服务器套接字,并指定端口号
ServerSocket listener = new ServerSocket(10010);
//(2)服务器侦听(监听)客户端的连接,返回客户端套接字,调用accept()方法接受请求,阻塞方法,如果没有客户端请求,则阻塞
System.out.println("服务器已启动...");
Socket socket = listener.accept();
//(3)获取输入输出流,实现通信
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
//(4)处理数据并回复
String date = br.readLine(); // 读取一行(必须要读取到换行符,才认为读取了一行)阻塞
System.out.println("客户说:" + date);
bw.write("您好!");
bw.newLine();
bw.flush();
//(5)关闭
br.close();
bw.close();
socket.close();
listener.close();
System.out.println("服务器关闭...");
}
}
注意:
BufferedWriter
中的readLine();
方法必须读到换行符
- 客户端程序代码
/**
* Tcp客户端的编码步骤
* (1)创建客户端Socket,并指定服务器的IP地址和端口号,建立三次握手
* (2)获取输入输出流
* (3)发送数据并处理响应数据
* (4)关闭
*/
public class TcpClient {
public static void main(String[] args) throws Exception {
//(1)创建客户端Socket,并指定服务器的IP地址和端口号,建立三次握手
Socket socket = new Socket(InetAddress.getLocalHost(), 10010);
//(2)获取输入输出流
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
//(3)处理数据
bw.write("好久不见");
bw.newLine();
bw.flush();
String replay = br.readLine();
System.out.println("客服说:" + replay);
//(4)关闭
br.close();
bw.close();
socket.close();
}
}
案例二 使用TCP实现客户端上传文件到服务器
- 服务器端程序代码
/**
* 文件客户端
* 接受文件
*/
public class FileServer {
public static void main(String[] args) throws Exception {
// 1.创建服务器套接字,并指定端口号
ServerSocket listener = new ServerSocket(9999);
// 2.监听,并返回客户端套接字
System.out.println("准备接收");
Socket socket = listener.accept();
// 3.获取输入输出流
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream("22.jpg");
byte[] buf = new byte[1024];
int len = 0;
// 4.读取并写入硬盘
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
// 5.关闭
fos.close();
is.close();
socket.close();
listener.close();
System.out.println("接收完毕");
}
}
- 客户端程序代码
/**
* 文件客户端
* 发送文件
*/
public class FileClient {
public static void main(String[] args) throws Exception {
// 1.创建Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
// 2.获取输出流
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream("11.png");
// 3.读取并输出
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
os.write(buf, 0, len);
}
// 4.关闭
fis.close();
os.close();
socket.close();
System.out.println("发送完毕");
}
}
案例三 使用TCP实现服务器多线程接受客户端
- 服务器端程序代码
/**
* 使用Tcp实现聊天服务器
*/
public class ChatServer {
public static void main(String[] args) {
ServerSocket listener = null;
// 创建线程池
//ExecutorService es = new ThreadPoolExecutor(10, 10,0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2));
ExecutorService es = Executors.newCachedThreadPool();
try {
// 1.创建服务器套接字
listener = new ServerSocket(8888);
System.out.println("聊天室已启动...");
// 2.监听
while (true) {
Socket socket = listener.accept();
System.out.println(socket.getInetAddress().getHostAddress() + "进入聊天室");
// 开启线程
es.submit(new SocketThread(socket));
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器出现异常...");
} finally {
try {
listener.close();
es.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 服务器端客户线程池
public class SocketThread implements Runnable {
private Socket socket;
public SocketThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
if (socket != null) {
BufferedReader br = null;
try {
InputStream is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
while (true) {
String str = br.readLine();
if (str == null) {
System.out.println(socket.getInetAddress().getHostAddress() + "退出了...");
break;
}
System.out.println(socket.getInetAddress().getHostAddress() + "说:" + str);
if (str.equals("886") || str.equals("over")) {
System.out.println(socket.getInetAddress().getHostAddress() + "退出了...");
break;
}
}
} catch (IOException e) {
System.out.println(socket.getInetAddress().getHostAddress() + "异常退出了...");
} finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 客户端程序代码
public class ChatClient {
public static void main(String[] args) throws Exception {
// 1.创建Socket,并指定服务器的地址和端口号
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 2.获取流
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
// 3.循环发送
Scanner sc = new Scanner(System.in);
while (true) {
String str = sc.next();
bw.write(str);
bw.newLine();
bw.flush();
if (str.equals("886") || str.equals("over")) {
break;
}
}
bw.close();
socket.close();
}
}
案例四 用户注册登录
-
注册
- 注册信息保存在users.bin文件中。
- 注册成功后返回字符串“注册成功”。
-
登录
- 获取users.bin文件中的用户信息,进行用户名与密码的校验。
- 校验成功后返回字符串“登陆成功”。
-
实体类代码
public class User implements Serializable{
private static long serivalVersionUID = 10000L; // 设置序列化版本号
private int type; // 操作数:1-注册、2-登录、0-退出
private String username;
private String password;
private int age;
private String sex;
private String address;
/*
无参、有参构造方法
get()、set()方法等
toString()方法
已省略,请手动生成
*/
}
- 服务器端代码
public class UserServer{
public static void main(String[] args) {
ConcurrentHashMap<String, User> userMap = null;
ExecutorService es = null;
ServerSocket listener = null;
try {
// 1.判断本地是否存在users.bin文件
File file = new File("users.bin");
if (file.exists()) {
// 对文件进行反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
userMap = (ConcurrentHashMap<String, User>) ois.readObject();
ois.close();
} else {
userMap = new ConcurrentHashMap<>();
}
// 2.创建线程池
es = Executors.newCachedThreadPool();
// 3.创建服务套接字
listener = new ServerSocket(8989);
System.out.println("服务器已开启...");
// 4.循环监听
while (true) {
Socket socket = listener.accept();
System.out.println(socket.getInetAddress().getHostAddress() + "进入服务器...");
// 5.线程池提交线程
es.submit(new UserThread(socket, userMap));
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("服务器已退出...");
} finally {
es.shutdown();
try {
listener.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 服务器端线程类
public class UserThread implements Runnable{
private Socket socket;
private ConcurrentHashMap<String, User> userMap;
public UserThread(Socket socket, ConcurrentHashMap<String, User> userMap) {
this.socket = socket;
this.userMap = userMap;
}
@Override
public void run() {
ObjectInputStream ois = null;
BufferedWriter bw = null;
try {
if (socket != null) {
// 获取输入输出流
InputStream is = socket.getInputStream();
ois = new ObjectInputStream(is);
OutputStream os = socket.getOutputStream();
bw = new BufferedWriter(new OutputStreamWriter(os));
while (true) {
User user = (User) ois.readObject();
if (user.getType() == 1) {
if (userMap.containsKey(user.getUsername())) {
bw.write("用户名已存在!");
} else {
userMap.put(user.getUsername(), user);
bw.write("注册成功!");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users.bin"));
oos.writeObject(userMap);
oos.close();
}
} else if (user.getType() == 2) {
if (userMap.containsKey(user.getUsername())) {
if (user.getPassword().equals(userMap.get(user.getUsername()).getPassword())) {
bw.write("登陆成功!");
} else {
bw.write("密码错误!");
}
} else {
bw.write("用户名不存在!");
}
} else {
bw.write("再见");
System.out.println(socket.getInetAddress().getHostAddress() + "已退出...");
break;
}
bw.newLine();
bw.flush();
}
}
} catch (Exception e) {
System.out.println(socket.getInetAddress().getHostAddress() + "异常退出了...");
} finally {
try {
ois.close();
bw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 客户端代码
public class UserClient{
public static void main(String[] args) throws Exception {
Socket socket = new Socket(InetAddress.getLocalHost(), 8989);
System.out.println("已成功连接" + socket.getInetAddress().getHostAddress())
System.out.println("---------欢迎--------");
InputStream is = socket.getInputStream();
BUfferedReader br = new BufferedReader(new InputStreamReader(is));
OutputStream os = socket.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(is);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("---1.注册 2.登录 0.退出---");
int choice = sc.nextInt();
String username = null;
String password = null;
int age = 0;
String sex = null;
String address = null;
User user = null;
String replay = null;
switch () {
case 1:
System.out.println("请输入用户名:");
username = sc.next();
System.out.println("请输入密码:");
password = sc.next();
System.out.println("请输入年龄:");
age = sc.nextInt();
System.out.println("请输入性别:");
sex = sc.next();
System.out.println("请输入地址:");
address = sc.next();
user = new User(1, username, password, age, sex, address);
oos.writeObject(user);
replay = bw.readLine();
System.out.println("服务器回复:" + replay);
break;
case 2:
System.out.println("请输入用户名:");
username = sc.next();
System.out.println("请输入密码:");
password = sc.next();
user = new User(2, username, password, age, sex, address);
oos.writeObject(user);
replay = bw.readLine();
System.out.println("服务器回复:" + replay);
break;
case 0:
user = new User(0, username, password, age, sex, address);
replay = bw.readLine();
System.out.println("服务器回复" + replay);
break;
default:
System.out.println("输入错误!请重新输入:");
break;
}
}
}
}
基于UDP的网络编程(User Datagram Protocol)
UDP相关的类
DatagramSocket
- 用来发送和接收数据报包的套接字
- JavaAPI中介绍:
此类表示用来发送和接收数据报包的套接字。
数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。
在 DatagramSocket 上总是启用 UDP 广播发送。为了接收广播包,应该将 DatagramSocket 绑定到通配符地址。在某些实现中,将 DatagramSocket 绑定到一个更加具体的地址时广播包也可以被接收。 - 构造方法
方法 | 描述 |
---|---|
DatagramSocket() | 构造数据报套接字并将其绑定到本地主机上任何可用的端口。 |
protected DatagramSocket(DatagramSocketImpl impl) | 创建带有指定 DatagramSocketImpl 的未绑定数据报套接字。 |
DatagramSocket(int port) | 创建数据报套接字并将其绑定到本地主机上的指定端口。 |
DatagramSocket(int port, InetAddress laddr) | 创建数据报套接字,将其绑定到指定的本地地址。 |
DatagramSocket(SocketAddress bindaddr) | 创建数据报套接字,将其绑定到指定的本地套接字地址。 |
- 方法
方法声明 | 方法描述 |
---|---|
void bind(SocketAddress addr) | 将此 DatagramSocket 绑定到特定的地址和端口。 |
void close() | 关闭此数据报套接字。 |
void connect(InetAddress address, int port) | 将套接字连接到此套接字的远程地址。 |
void connect(SocketAddress addr) | 将此套接字连接到远程套接字地址(IP 地址 + 端口号)。 |
void disconnect() | 断开套接字的连接。 |
boolean getBroadcast() | 检测是否启用了 SO_BROADCAST。 |
DatagramChannel getChannel() | 返回与此数据报套接字关联的唯一 DatagramChannel 对象(如果有)。 |
InetAddress getInetAddress() | 返回此套接字连接的地址。 |
InetAddress getLocalAddress() | 获取套接字绑定的本地地址。 |
int getLocalPort() | 返回此套接字绑定的本地主机上的端口号。 |
SocketAddress getLocalSocketAddress() | 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。 |
int getPort() | 返回此套接字的端口。 |
int getReceiveBufferSize() | 获取此 DatagramSocket 的 SO_RCVBUF 选项的值,该值是平台在 DatagramSocket 上输入时使用的缓冲区大小。 |
SocketAddress getRemoteSocketAddress() | 返回此套接字连接的端点的地址,如果未连接则返回 null。 |
boolean getReuseAddress() | 检测是否启用了 SO_REUSEADDR。 |
int getSendBufferSize() | 获取此 DatagramSocket 的 SO_SNDBUF 选项的值,该值是平台在 DatagramSocket 上输出时使用的缓冲区大小。 |
int getSoTimeout() | 获取 SO_TIMEOUT 的设置。 |
int getTrafficClass() | 为从此 DatagramSocket 上发送的包获取 IP 数据报头中的流量类别或服务类型。 |
boolean isBound() | 返回套接字的绑定状态。 |
boolean isClosed() | 返回是否关闭了套接字。 |
boolean isConnected() | 返回套接字的连接状态。 |
void receive(DatagramPacket p) | 从此套接字接收数据报包。 |
void send(DatagramPacket p) | 从此套接字发送数据报包。 |
void setBroadcast(boolean on) | 启用/禁用 SO_BROADCAST。 |
static void setDatagramSocketImplFactory(DatagramSocketImplFactory fac) | 为应用程序设置数据报套接字实现工厂。 |
void setReceiveBufferSize(int size) | 将此 DatagramSocket 的 SO_RCVBUF 选项设置为指定的值。 |
void setReuseAddress(boolean on) | 启用/禁用 SO_REUSEADDR 套接字选项。 |
void setSendBufferSize(int size) | 将此 DatagramSocket 的 SO_SNDBUF 选项设置为指定的值。 |
void setSoTimeout(int timeout) | 启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位。 |
void setTrafficClass(int tc) | 为从此 DatagramSocket 上发送的数据报在 IP 数据报头中设置流量类别 (traffic class) 或服务类型八位组 (type-of-service octet)。 |
DatagramPacket
- 表示数据报包
- JavaAPI中介绍:
此类表示数据报包。
数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。 - 构造方法:
方法 | 描述 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造 DatagramPacket,用来接收长度为 length 的数据包。 |
DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。 |
DatagramPacket(byte[] buf, int offset, int length) | 构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。 |
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) | 构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。 |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) | 构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。 |
DatagramPacket(byte[] buf, int length, SocketAddress address) | 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。 |
- 方法:
方法声明 | 方法描述 |
---|---|
InetAddress getAddress() | 返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。 |
byte[] getData() | 返回数据缓冲区。 |
int getLength() | 返回将要发送或接收到的数据的长度。 |
int getOffset() | 返回将要发送或接收到的数据的偏移量。 |
int getPort() | 返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。 |
SocketAddress getSocketAddress() | 获取要将此包发送到的或发出此数据报的远程主机的 SocketAddress(通常为 IP 地址 + 端口号)。 |
void setAddress(InetAddress iaddr) | 设置要将此数据报发往的那台机器的 IP 地址。 |
void setData(byte[] buf) | 为此包设置数据缓冲区。 |
void setData(byte[] buf, int offset, int length) | 为此包设置数据缓冲区。 |
void setLength(int length) | 为此包设置长度。 |
void setPort(int iport) | 设置要将此数据报发往的远程主机上的端口号。 |
void setSocketAddress(SocketAddress address) | 设置要将此数据报发往的远程主机的 SocketAddress(通常为 IP 地址 + 端口号)。 |
开发步骤
案例一 使用UDP发送数据给接收者
- 接收方代码
/**
* UDP接收方
* 步骤:
* (1)创建DatagramSocket,指定端口号
* (2)创建DatagramPacket,接收报包
* (3)接收数据
* (4)处理
* (5)关闭
*/
public class Receiver {
public static void main(String[] args) {
// 1.创建一个DatagramSocket对象,用于接收数据
DatagramSocket ds = new DatagramSocket(8888);
// 2.创建一个DatagramPacket对象,用于接收数据报包
byte[] buf = new byte[63*1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
// 3.接收
ds.receive(dp);
// 4.处理数据
String str = new String(dp.getData(), 0, dp.getLength()) + " from " + dp.getAddress().getHostAddress() + ":" + dp.getPort();
System.out.println(str);
// 5.关闭
ds.close();
}
}
- 发送方代码
/**
* UDP传输发的步骤
* (1)创建DatagramSocket,不需要指定端口号,使用随机端口号(相当于快递点)
* (2)创建DatagramPacket,发送数据报包,指定对方的地址和端口号
* (3)发送
* (4)关闭
*/
public class Sender {
public static void main(String[] args) throws Exception {
// 1.创建一个DatagramSocket对象
DatagramSocket ds = new DatagramSocket(3000);
String str = "hello world"; // 要发送的数据
byte[] buf = str.getBytes(); // 将定义的字符串转为字节数组
// 2.创建一个要发送的数据包,数据包包括发送的数据,数据长度,接收端的IP地址以及端口号
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 8954);
System.out.println("发送信息");
// 3.发送
ds.send(dp); // 发送数据
// 4.关闭
ds.close(); // 释放资源
}
}
案例二 使用UDP实现广播聊天
- 接收方代码
public class ChatReceiver {
public static void main(String[] args) {
DatagramSocket ds = null;
try {
// 1.创建DatagramSocket
ds = new DatagramSocket(8888);
// 2.创建DatagramPacket
byte[] buf = new byte[63 * 1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
System.out.println("已启动...");
while (true) {
// 3.接收
ds.receive(dp);
// 4.处理
String str = new String(dp.getData(), 0, dp.getLength());
System.out.println(dp.getAddress().getHostAddress() + "说:" + str);
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("出现异常");
} finally {
// 5.关闭
ds.close();
}
}
}
- 发送方代码
public class ChatSender {
public static void main(String[] args) throws Exception {
// 1.创建DatagramSocket
DatagramSocket ds = new DatagramSocket();
// 2.创建DatagramPacket
Scanner sc = new Scanner(System.in);
while (true) {
String str = sc.next();
// 受限广播地址 255.255.255.255
// 指定广播地址 10.9.31.255
DatagramPacket dp = new DatagramPacket(str.getBytes(), str.getBytes().length, InetAddress.getByName("10.9.31.255"), 8888);
ds.send(dp);
if(str.equals("886")){
break;
}
}
//3关闭
ds.close();
}
}