16 网络编程
约 12026 字大约 40 分钟
2025-08-22
网络编程概述
Java
是Internet
上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。Java
提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在Java
的本机安装系统里,由JVM
进行控制,并且Java
实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。
软件架构
C/S
架构:全称为Client/Server
结构,是指客户端和服务器结构。常见程序有QQ
、美团、360
等软件
B/S
架构:全称为Browser/Server
结构,是指浏览器和服务器结构。常见浏览器有IE
、谷歌、火狐等
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。
网络基础
- 计算机网络:把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源
- 网络编程的目的:直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯
- 网络编程中有三个主要的问题:
- 问题
1
:如何准确地定位网络上一台或多台主机?IP
- 问题
2
:如何定位主机上的特定的应用? - 问题
3
:找到主机后,如何可靠、高效地进行数据传输?
- 问题
网络通信要素
如何实现网络中的主机互相通信
IP
- 端口号
- :不同的硬件、操作系统之间的通信,所有的这一切都需要一种规则,这种规则称为协议,即:网络通信协议
生活类比:
通信要素1
:IP
地址和域名
IP
地址
IP
地址:指互联网协议地址(Internet Protocol Address),俗称IP
,IP
地址用来给网络中的一台计算机设备做唯一的编号。假如把“个人电脑”比作“电话”,那“IP
地址”就相当于“电话号码”
IP
地址分类方式1
IPv4
:是一个32
位的二进制数,通常被分为4
个字节,表示成a.b.c.d
的形式,以表示,其中a、b、c、d
都是0~255
之间的十进制整数。例如:192.168.65.100
这种方式最多可以表示
42亿
个。其中30亿
都在北美,亚洲4亿
,中国2.9亿
,2011
年初IP
地址 = 网络地址 +主机地址- :标识计算机或网络设备所在的网段
- :标识特定主机或网络设备
- 其中,
E
类用于科研
IPv6
:由于互联网的蓬勃发展,IP
地址的需求量愈来愈大,但是网络地址资源有限,使得IP
的分配越发紧张- 为了扩大地址空间,拟通过
IPv6
重新定义地址空间,采用128
位地址长度,共16
个字节,写成8
个无符号整数,每个整数用4
个十六进制位表示,数之间用冒号:
分开- 比如:
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
,按保守方法估算IPv6
实际可分配的地址,整个地球的每平方米面积上仍可分配1000
多个地址,这样就解决了网络地址资源数量不够的问题。2012
年6
月6
日,国际互联网协会举行了世界IPv6
启动纪念日,这一天,全球IPv6
网络正式启动,多家知名网站,如:Google
、Facebook
和Yahoo
等,于当天全球标准时间0
点(北京时间8
点整)开始永久性支持IPv6
访问。2018
年6
月,三大运营商联合阿里云宣布,将全面对外提供IPv6
服务,并计划在2025
年前助推中国互联网真正实现IPv6 Only
- 比如:
- 在
IPv6
的设计过程中除了一劳永逸地解决了地址短缺问题以外,还考虑了在IPv4
中解决不好的其它问题,主要有端到端IP
连接、服务质量QoS
、安全性、多播、移动性、即插即用等
- 为了扩大地址空间,拟通过
IP
地址分类方式2
公网地址(万维网使用)和私有地址(局域网使用)
192.168
开头的就是私有地址,范围即为192.168.0.0~192.168.255.255
,专门为组织机构内部使用
常用命令:
- 查看本机
IP
地址,在控制台输入:
ipconfig
- 检查网络是否连通,在控制台输入:
ping 空格 IP地址
ping 220.181.57.216
特殊的IP
地址:
- 本地回环地址(
hostAddress
):127.0.0.1
- 主机名(
hostName
):localhost
域名
Internet
上的主机有两种方式表示地址:
- 域名(
hostName
):[www.atguigu.com](http://www.atguigu.com)
IP
地址(hostAddress
):202.108.35.210
域名解析: 因为IP
地址数字不便于记忆,因此出现了域名。域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS
:Domain Name System
,域名系统)负责将域名转化成IP
地址,这样才能和主机建立连接。
简单理解:
详细理解:
1.
在浏览器中输入www.qq.com
域名,操作系统会先检查自己本地的hosts
文件是否有这个网址映射关系,如果有,就先调用这个IP
地址映射,完成域名解析2.
如果hosts
里没有这个域名的映射,则查找本地DNS
解析器缓存,是否有这个网址映射关系,如果有,直接返回,完成域名解析3.
如果hosts
与本地DNS
解析器缓存都没有相应的网址映射关系,首先会找TCP/IP
参数中设置的首选DNS
服务器,在此我们叫它本地DNS
服务器,此服务器收到查询时,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性4.
如果要查询的域名,不由本地DNS
服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP
地址映射,完成域名解析,此解析不具有权威性5.
如果本地DNS
服务器本地区域文件与缓存解析都失效,则根据本地DNS
服务器的设置(是否设置转发器)进行查询,如果未用转发模式,本地DNS
就把请求发至13
台根DNS
,根DNS
服务器收到请求后会判断这个域名(.com
)是谁来授权管理,返回一个负责该顶级域名服务器的一个IP
。本地DNS
服务器收到IP
信息后,将会联系负责.com
域的这台服务器。这台负责.com
域的服务器收到请求后,如果自己无法解析,它就会找一个管理.com
域的下一级DNS
服务器地址(http://qq.com
)给本地DNS
服务器。当本地DNS
服务器收到这个地址后,就会找(http://qq.com
)域服务器,重复上面的动作,进行查询,直至找到www.qq.com
主机6.
如果用的是转发模式,此DNS
服务器就会把请求转发至上一级DNS
服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS
或把请求转至上上级,以此循环。不管是本地DNS
服务器用是是转发,还是根提示,最后都是把结果返回给本地DNS
服务器,由此DNS
服务器再返回给客户机
通信要素2
:端口号
网络的通信,本质上是两个进程(应用程序)的通信。 每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
- 如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)
- 不同的进程,设置不同的端口号
- 端口号:用两个字节表示的整数,它的取值范围是
0~65535
- 公认端口:
0~1023
,预先定义的服务通信占用,如:HTTP
80
、FTP
21
、Telnet
23
- 注册端口:
1024~49151
,分配给用户进程或应用程序,如:Tomcat
8080
、MySQL
3306
、Oracle
1521
- 动态/私有端口:
49152~65535
- 公认端口:
- 如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
通信要素3
:网络通信协议
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样
- 网络通信协议:在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤、出错控制等做了统一规定,通信双方必须同时遵守才能完成数据交换
问题:
计算机网络通信涉及内容很多,比如指定源地址和目标地址、加密解密、压缩解压缩、差错控制、流量控制、路由控制,如何实现如此复杂的网络协议呢?通信协议分层思想。
在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即:同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系,各层互不影响,利于系统的开发和扩展。
OSI
参考模型:模型过于理想化,未能在因特网上进行广泛推广TCP/IP
参考模型(TCP/IP
协议):事实上的国际标准
TCP/IP
TCP/IP
传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocol
),TCP/IP
以其两个主要协议:传输控制协议(TCP
)和网络互联协议(IP
)而得名,,是 Internet
最基本、最广泛的协议。
TCP/IP
协议中的四层介绍:
- :。主要协议有:
HTTP
协议、FTP
协议、SNMP
(简单网络管理协议)、SMTP
(简单邮件传输协议)和POP3
(Post Office Protocol 3
,邮局协议的第3 个版本)等 - :
TCP
UDP
TCP
(Transmission Control Protocol
)协议,即:传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议;UDP
(User Datagram Protocol
)协议,即:用户数据报协议,是一个无连接的传输层协议、提供面向事务的简单不可靠的信息传送服务 - :
TCP/IP
。主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。IP
(Internet Protocal
)协议是一种非常重要的协议,又称为互联网协议。IP
的责任就是把数据从源传送到目的地,它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求 - :,例如:针对光纤、网线提供的驱动
传输层协议:TCP
与UDP
协议
通信的协议是比较复杂的,java.net
包中包含的类和接口,它们提供低层次的通信细节,我们可以直接使用这些类和接口来专注于网络程序开发,而不用考虑通信的细节。
java.net
包中提供了两种常见的网络协议的支持:
UDP
:用户数据报协议(User Datagram Protocol
)TCP
:传输控制协议(Transmission Control Protocol
)
TCP
协议与UDP
协议
TCP
协议
TCP
协议进行通信的两个应用进程:客户端、服务端- 使用
TCP
协议前,须先建立TCP
连接,形成基于字节流的传输数据通道
- 使用
- 传输前,采用“三次握手”方式,点对点通信,是可靠的
TCP
协议使用重发机制,当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体确认信息,如果没有收到另一个通信实体确认信息,则会再次重复刚才发送的消息
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
UDP协议
UDP
协议进行通信的两个应用进程:发送端、接收端- 将数据、源、目的封装成数据报(传输的基本单位),不需要建立连接
- 发送不管对方是否准备好,接收方收到也不确认,不能保证数据的完整性,故是不可靠的
- 每个数据报的大小限制在
64K
内 - 发送数据结束时无需释放资源,开销小,通信效率高
- 适用场景:音频、视频和普通数据的传输。例如:视频会议
TCP
生活案例:打电话
UDP
生活案例:发送短信、发电报
三次握手
TCP
协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
- 第一次握手:客户端向服务器端发起
TCP
连接的请求 - 第二次握手:服务器端发送针对客户端
TCP
连接请求的确认 - 第三次握手:客户端发送确认的确认
- 详细过程:
1.
客户端会随机一个初始序列号seq=x
,设置SYN=1
,表示这是SYN
握手报文。然后把这个SYN
报文发送给服务端,表示向服务端发起连接,之后2.
服务端收到客户端的SYN
报文后,也随机一个初始序列号seq=y
,设置ack=x+1
,表示收到客户端的x
之前的数据,希望客户端下次发送数据从x+1
开始,设置SYN=1
和ACK=1
,表示这是一个SYN
握手和ACK
确认应答报文,最后把该报文发给客户端,该报文也不包含应用层数据,之后3.
客户端收到服务端报文后,还要向服务端回应最后一个应答报文,将ACK
置为1
,表示这是一个应答报文ack=y+1
,表示收到了服务器的y
之前的数据,希望服务器下次发送的数据从y+1
开始,最后把报文发送给服务端,这次报文可以携带数据,之后4.
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP
协议可以保证传输数据的安全, 所以应用十分广泛,例如:下载文件、浏览网页等
四次挥手
TCP
协议中,在发送数据结束后,释放连接时需要经过四次挥手
- 第一次挥手:客户端向服务器端提出结束连接,让服务器做最后的准备工作。此时,,即表示不再向服务器发送数据了,但是还可以接受数据
- 第二次挥手:服务器接收到客户端释放连接的请求后,会将最后的数据发给客户端,并
- 第三次挥手:,会给客户端发送一个释放连接的报文,
- 第四次挥手:客户端接收到服务器最后的释放连接报文后,。客户端发送完最后的报文后,会等待
2MSL
,因为有可能服务器没有收到最后的报文,那么服务器迟迟没收到,就会再次给客户端发送释放连接的报文,此时客户端在等待时间范围内接收到,会重新发送最后的报文,并重新计时。如果等待2MSL
后,没有收到,那么彻底断开
- 详细过程:
1.
客户端打算断开连接,向服务器发送FIN
报文(FIN
标记位被设置为1
,1
表示为FIN
,0
表示不是),FIN
报文中会指定一个序列号,之后FINWAIT1
。也就是客户端发出连接释放报文段(FIN
报文),指定序列号seq = u
,主动关闭TCP
连接,等待服务器确认2.
服务器收到连接释放报文段(FIN
报文)后,就向客户端发送ACK
应答报文,以客户端的FIN
报文的序列号seq+1
作为ACK
应答报文段的确认序列号ack = seq+1 = u + 1
。接着CLOSEWAIT
,此时的TCP
处于半关闭状态,客户端到服务器的连接释放,ACK
FINWAIT_2
3.
服务器也打算断开连接,向客户端发送连接释放(FIN
)报文段,之后服务器进入LASK_ACK
,等待客户端的确认。服务器的连接释放(FIN
)报文段的FIN=1
、ACK=1
,序列号seq=m
,确认序列号ack=u+1
4.
客户端收到来自服务器的连接释放(FIN
)报文段后,会向服务器发送一个ACK
应答报文段,以连接释放(FIN
)报文段的确认序号ack
作为ACK
应答报文段的序列号seq
,以连接释放(FIN
)报文段的序列号seq+1
作为确认序号ack
5.
之后TIMEWAIT
,服务器收到ACK
应答报文段后,服务器就进入CLOSE
(关闭)状态, 到此服务器的连接已经完成关闭。客户端处于TIMEWAIT
状态时,此时的TCP
还未释放掉,需要等待2MSL
后,客户端才进入CLOSE
状态
网络编程API
InetAddress
类
InetAddress
IP
Inet4Address
Inet6Address
InetAddress
类没有提供公共的构造器,InetAddress
public static InetAddress getLocalHost()
public static InetAddress getByName(String host)
public static InetAddress getByAddress(byte[] addr)
InetAddress
提供了如下几个常用的方法
public String getHostAddress()
:返回IP
地址字符串(以文本表现形式)public String getHostName()
:获取此IP
地址的主机名public boolean isReachable(int timeout)
:测试是否可以达到该地址
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressTest {
public static void main(String[] args) throws IOException {
// getLocalHost()
InetAddress localHost = Inet4Address.getLocalHost();
System.out.println(localHost);
// getByName(String host)
InetAddress byName = Inet4Address.getByName("www.baidu.com");
System.out.println(byName);
// getByAddress(byte[] addr)
InetAddress byAddress = Inet4Address.getByAddress(new byte[]{((byte) 183), ((byte) 2), ((byte) 172), ((byte) 42)});
System.out.println(byAddress);
// getHostAddress()
String hostAddress = byName.getHostAddress();
System.out.println(hostAddress);
// getHostName()
String hostName = byName.getHostName();
System.out.println(hostName);
// isReachable(int timeout)
boolean reachable = byName.isReachable(200);
System.out.println(reachable);
}
}
Socket
类
IP
Socket
- 利用套接字
Socket
开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。网络通信其实就是Socket
间的通信 - 通信的两端都要有
Socket
,是两台机器间通信的端点 Socket
允许程序把网络连接当成一个流,数据在两个Socket
间通过IO
传输- 一般主动发起通信的应用程序属,等待通信请求的为
Socket
分类:
stream socket
:使用TCP
提供可靠的字节流服务ServerSocket
:实现TCP
服务器套接字,服务器套接字等待请求通过网络传入Socket
:实现客户端套接字,套接字是两台机器间通信的端点
datagram socket
:使用UDP
提供尽力而为的数据报服务DatagramSocket
:表示用来发送和接收UDP
数据报的套接字
Socket
相关类API
ServerSocket
类
构造方法
ServerSocket(int port)
:创建绑定到特定端口的服务器套接字
常用方法
Socket accept()
:侦听并接受到此套接字的连接
Socket
类
构造方法
public Socket(InetAddress address,int port)
:创建一个流套接字并将其连接到指定IP
地址的指定端口号public Socket(String host,int port)
:创建一个流套接字并将其连接到指定主机上的指定端口号
常用方法
public InputStream getInputStream()
:返回此套接字的输入流,可以用于接收消息public OutputStream getOutputStream()
:返回此套接字的输出流,可以用于发送消息public InetAddress getInetAddress()
:此套接字连接到的远程 IP 地址,如果套接字是未连接的,则返回null
public InetAddress getLocalAddress()
:获取套接字绑定的本地地址public int getPort()
:此套接字连接到的远程端口号,如果尚未连接套接字,则返回0
public int getLocalPort()
:返回此套接字绑定到的本地端口,尚未绑定套接字,则返回-1
public void close()
:关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用,即:无法重新连接或重新绑定,需要创建新的套接字对象,关闭此套接字也将会关闭该套接字的InputStream
和OutputStream
public void shutdownInput()
:在套接字上调用shutdownInput()
后从套接字输入流读取内容,则流将返回EOF
文件结束符,即:不能再从此套接字的输入流中接收任何数据public void shutdownOutput()
:禁用此套接字的输出流。对于TCP
套接字,任何以前写入的数据都将被发送,并且后跟TCP
的正常连接终止序列。如果在套接字上调用shutdownOutput()
后写入套接字输出流,则该流将抛出IOException
,即:不能通过此套接字的输出流再发送任何数据
:先后调用Socket
的shutdownInput()
和shutdownOutput()
方法,仅仅关闭了输入流和输出流,并不等于调用Socket
的close()
方法。在通信结束后,仍然要调用Socket
的close()
方法,因为只有该方法才会释放Socket
占用的资源,比如:占用的本地端口号等。
DatagramSocket
类
常用方法
public DatagramSocket(int port)
:创建数据报套接字并将其绑定到本地主机上的指定端口,套接字将被绑定到通配符地址,IP
地址由内核来选择public DatagramSocket(int port,InetAddress laddr)
:创建数据报套接字,将其绑定到指定的本地地址,本地端口必须在0
到65535
之间(包括两者),如果IP
地址为0.0.0.0
,套接字将被绑定到通配符地址,IP
地址由内核选择public void close()
:关闭此数据报套接字public void send(DatagramPacket p)
:从此套接字发送数据报包,DatagramPacket
包含的信息:将要发送的数据、其长度、远程主机的IP
地址和远程主机的端口号public void receive(DatagramPacket p)
:从此套接字接收数据报包,当此方法返回时,DatagramPacket
的缓冲区填充了接收的数据。数据报包也包含:发送方的IP
地址和发送方机器上的端口号。此方法在接收到数据报前一直阻塞。数据报包对象的length
字段包含所接收信息的长度,public InetAddress getLocalAddress()
:套接字绑定的本地地址public int getLocalPort()
:此套接字绑定的本地主机上的端口号public InetAddress getInetAddress()
:此套接字连接的地址,套接字未连接则返回null
public int getPort()
:此套接字的端口,套接字未连接则返回-1
DatagramPacket
类
常用方法
public DatagramPacket(byte[] buf,int length)
:构造DatagramPacket
,用来接收长度为length
的数据包,length
buf.length
public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
:构造数据报包,用来将长度为length
的包发送到指定主机上的指定端口号,length
buf.length
public InetAddress getAddress()
:返回某台机器的IP
地址,此数据报将要发往该机器或者是从该机器接收到的public int getPort()
:返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的public byte[] getData()
:返回数据缓冲区,接收到的或将要发送的数据从缓冲区中的偏移量offset
处开始,持续length
长度public int getLength()
:返回将要发送或接收到的数据的长度
TCP网络编程
通信模型
Java
语言的基于套接字TCP
编程分为:服务端编程和客户端编程,其通信模型如图所示:
开发步骤
1.
创建Socket
:根据指定服务端的IP
地址和端口号构造Socket
类对象。若服务器端响应,则建立客户端到服务器的通信线路;若连接失败,会出现异常2.
打开连接到Socket
的输入/ 出流:使用getInputStream()
方法获得输入流,使用getOutputStream()
方法获得输出流,进行数据传输3.
按照一定协议对Socket
进行读/ 写操作:通过输入流读取服务器放入线路的信息,但不能读取自己放入线路的信息,通过输出流将信息写入线路4.
关闭Socket
:断开客户端到服务器的连接,释放线路
1.
调用ServerSocket(int port)
:创建一个服务器端套接字,并绑定到指定端口上,用于监听客户端的请求2.
调用accept()
:监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象3.
调用该Socket
类对象的getOutputStream()
和getInputStream ()
:获取输出流和输入流,开始网络数据的发送和接收4.
关闭Socket
对象:客户端访问结束,关闭通信套接字
练习
- 客户端发送内容给服务端,服务端将内容打印到控制台上
- 客户端发送文件给服务端,服务端将文件保存在本地
- 从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接
- 服务端读取图片并发送给客户端,客户端保存图片到本地
- 客户端给服务端发送文本,服务端会将文本转成大写在返回给客户端
案例:单个客户端与服务器通信
需求:客户端连接服务器,连接成功后给服务发送
lalala
,服务器收到后,给客户端返回欢迎登录
,客户端接收消息后,断开连接
- 服务器端代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPCommunicateDemo1Server {
public static void main(String[] args) {
try {
// 1. 创建ServerSocket对象 绑定 8888 端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("启动服务端,监听8888...");
// 2. 监听端口连接请求
Socket client = serverSocket.accept();
InetAddress clientInetAddress = client.getInetAddress();
System.out.println(clientInetAddress.getHostAddress() + "客户端连接成功...");
// 3. 获取输入流 接受用户发来的数据
InputStream clientInputStream = client.getInputStream();
byte[] buff = new byte[1024];
StringBuilder stringBuilder = new StringBuilder();
int len;
while ((len = clientInputStream.read(buff)) != -1) {
stringBuilder.append(new String(buff, 0, len));
}
System.out.println(clientInetAddress.getHostAddress() + ":" + stringBuilder);
// 4. 获取输出流 发送数据给用户
OutputStream clientOutputStream = client.getOutputStream();
// 发送数据到客户端
clientOutputStream.write("欢迎登录".getBytes());
clientOutputStream.flush();
// 5. 关闭Socket 不再与客户端通信
client.close();
// 6. 关闭ServerSocket 不再接受客户端的连接请求
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 客户端代码
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TCPCommunicateDemo1Client {
public static void main(String[] args) {
try {
// 1. 创建Socket对象 连接到指定服务器的IP和指定端口
Socket socket = new Socket("127.0.0.1", Integer.parseInt("8888"));
// 2. 获取输出流 发送数据给服务器
OutputStream socketOutputStream = socket.getOutputStream();
// 发送数据到服务端
socketOutputStream.write("lalala".getBytes());
// 在输出流末尾加入结束标记 服务端读取到之后(-1) 不会再阻塞等待这边输入数据
socket.shutdownOutput();
// 3. 获取输入流 接受服务器发来的数据
InputStream socketInputStream = socket.getInputStream();
byte[] buff = new byte[1024];
StringBuilder stringBuilder = new StringBuilder();
int len;
while ((len = socketInputStream.read(buff)) != -1) {
stringBuilder.append(new String(buff, 0, len));
}
System.out.println("服务端消息:" + stringBuilder);
// 4. 关闭Socket 不再与服务器通信
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
案例:多个客户端与服务器通信
通常情况下服务器不应该只接受一个客户端请求,而应该不断接受来自客户端的所有请求,所以Java
程序通常会通过循环,不断地调用ServerSocket
的accept()
方法。
如果服务器端要同时处理多个客户端的请求,服务器端需要为每个客户端单独分配一个线程来处理,否则无法实现同时处理的效果。
- 每个客户端连接成功后,从键盘输入英文单词或中文,并发送给服务器
- 服务器收到客户端的消息后,把词语反转后返回给客户端
- 客户端接收服务器返回的词语,打印显示
- 当客户端输入
stop
时断开与服务器的连接 - 多个客户端可以同时给服务器发送词语,服务器可以同时处理多个客户端的请求
- 服务器端代码
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPCommunicateDemo2Server {
public static void main(String[] args) throws IOException {
// 1. 创建ServerSocket
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器端启动,监听8888...");
// 2. 监听客户端连接请求
int count = 0;
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("第[" + count + "]个客户端连接成功...");
count++;
ClientHandlerThread clientHandlerThread = new ClientHandlerThread(clientSocket);
clientHandlerThread.start();
}
}
/**
* 处理客户端连接的线程
*/
static class ClientHandlerThread extends Thread {
/**
* 客户端套接字
*/
private Socket socket;
/**
* 客户端ip
*/
private String ip;
public ClientHandlerThread(Socket socket) {
this.socket = socket;
ip = socket.getInetAddress().getHostAddress();
}
/**
* 业务逻辑 处理与客户端的通信
*/
@Override
public void run() {
try {
// 1. 获取输入流 接受客户端发来的数据
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
// 2. 获取输出流 发送数据给客户端
PrintStream printStream = new PrintStream(socket.getOutputStream());
// 3. 接受数据
String line;
StringBuilder stringBuilder = new StringBuilder();
while ((line = bufferedReader.readLine()) != null) {
// 4. 反转 返回给客户端
stringBuilder.delete(0, stringBuilder.length());
printStream.println(stringBuilder.append(line).reverse());
}
System.out.println(ip + " 客户端正常断开...");
} catch (Exception e) {
System.out.println(ip + " 客户端异常断开...");
} finally {
// 5. 断开连接
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 客户端代码
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TCPCommunicateDemo2Client {
public static void main(String[] args) throws IOException {
// 1、准备Socket,连接服务器,需要指定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 8888);
// 2、获取输出流,用来发送数据给服务器
PrintStream printStream = new PrintStream(socket.getOutputStream());
// 3、获取输入流,用来接收服务器发送给该客户端的数据
InputStream input = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
// 4、 发送数据
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("输入发送给服务器的单词或成语:");
String message = scanner.nextLine();
if (message.equals("stop")) {
socket.shutdownOutput();
break;
}
printStream.println(message);
// 接收数据
System.out.println("从服务器收到的反馈是:" + bufferedReader.readLine());
}
//5、关闭socket,断开与服务器的连接
scanner.close();
socket.close();
}
}
案例:聊天室
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
public class TCPCommunicateDemo3ChatServer {
/**
* 集合 存储在线的客户端
*/
static LinkedList<Socket> clients = new LinkedList<>();
public static void main(String[] args) throws IOException {
// 1. 启动服务器
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端上线,监听8888...");
// jil
// 2. 接受多个客户端连接
while (true) {
Socket client = serverSocket.accept();
clients.add(client);
// 3. 处理客户端连接
ClientHandlerThread clientHandlerThread = new ClientHandlerThread(client);
clientHandlerThread.start();
}
}
/**
* 客户端处理线程
*/
static class ClientHandlerThread extends Thread {
/**
* 套接字
*/
private Socket socket;
/**
* 客户端ip
*/
private String ip;
public ClientHandlerThread(Socket socket) {
this.socket = socket;
this.ip = socket.getInetAddress().getHostAddress();
}
/**
* 业务逻辑
*/
@Override
public void run() {
// 上线提醒
try {
sendToOther("客户端上线...", false);
// 1. 接受客户端的输入数据
InputStreamReader inputStreamReader = new InputStreamReader(socket.getInputStream());
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
// 先判断是否意外下线 再读取数据
while (socket.isConnected() && (line = bufferedReader.readLine()) != null) {
// 2. 转发给其他用户
sendToOther(line, true);
}
} catch (IOException e) {
System.out.println("客户端意外下线了...");
} finally {
// 3. 从在线列表移除客户端
clients.remove(socket);
// 4. 发送下线消息
try {
sendToOther("客户端下线了...", true);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 提醒其他用户 当前客户端上线
*
* @param message 要发送的消息
* @param jumpSelf 是否跳过当前客户端自己
*/
public void sendToOther(String message, boolean jumpSelf) throws IOException {
// 遍历所有在线用户 并转发信息
for (Socket client : clients) {
// 如果开启了跳过当前客户端 则不发送对应消息个当前客户端
if (jumpSelf && client == socket) {
continue;
}
PrintStream printStream = new PrintStream(client.getOutputStream());
printStream.println(ip + " " + message);
}
}
}
}
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class TCPCommunicateDemo3ChatClient {
public static void main(String[] args) throws IOException, InterruptedException {
// 1. 连接服务器
Socket socket = new Socket("127.0.0.1", 8888);
// 2. 开启发送和接受线程
SendHandlerThread sendHandlerThread = new SendHandlerThread(socket);
ReceiveHandlerThread receiveHandlerThread = new ReceiveHandlerThread(socket);
receiveHandlerThread.start();
sendHandlerThread.start();
// 不再发送数据 就关闭Socket
sendHandlerThread.join();
// 3. 关闭Socket
socket.close();
}
}
/**
* 发送线程
*/
class SendHandlerThread extends Thread {
/**
* 套接字
*/
private Socket socket;
public SendHandlerThread(Socket socket) {
this.socket = socket;
}
/**
* 接收业务逻辑
*/
@Override
public void run() {
try {
PrintStream printStream = new PrintStream(socket.getOutputStream());
Scanner scanner = new Scanner(System.in);
// 不断输入消息
while (true) {
String line = scanner.nextLine();
if ("bye".equals(line)) {
socket.shutdownOutput();
break;
}
// 发送
printStream.println(line);
}
scanner.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 接受线程
*/
class ReceiveHandlerThread extends Thread {
/**
* 套接字
*/
private Socket socket;
public ReceiveHandlerThread(Socket socket) {
this.socket = socket;
}
/**
* 发送业务逻辑
*/
@Override
public void run() {
try {
// 获取输入流
InputStreamReader inputStreamReader = new InputStreamReader(socket.getInputStream());
Scanner scanner = new Scanner(inputStreamReader);
// 读取数据
while (scanner.hasNextLine()) {
// 输出
System.out.println(scanner.nextLine());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
理解客户端、服务端
- 客户端:
- 自定义
- 浏览器(
browser
<--->
server
)
- 服务端:
- 自定义
- Tomcat服务器
UDP
网络编程
UDP
(User Datagram Protocol
:用户数据报协议),是一个无连接的传输层协议、提供面向事务的简单、不可靠的信息传送服务,类似于短信。
通信模型
UDP
协议是一种面向非连接的协议,面向非连接指的是:在正式通信前不必与对方先建立连接,不管对方状态就直接发送,至于对方是否可以接收到这些数据内容,UDP
协议无法控制。因此,UDP
协议是一种不可靠的协议。。UDP
,没有TCP
的确认机制、重传机制。如果因为网络原因没有传送到对端,UDP
也不会给应用层返回错误信息UDP
协议是面向数据报文的信息传送服务,UDP
IP
。比如:要发送100
个字节的报文,调用一次send()
方法就会发送100
字节,接收方也需要用receive()
方法一次性接收100
字节,10
10
UDP
协议没有拥塞控制,UDP
UDP
。这个对实时应用来说很重要,比如:视频通话、直播等应用UDP
64K
类DatagramSocket
和DatagramPacket
实现了基于UDP
协议网络程序
UDP
数据报通过数据报套接字DatagramSocket
发送和接收,系统不保证UDP
数据报一定能够安全送到目的地,也不能确定什么时候可以抵达DatagramPacket
对象封装了UDP
数据报,在数据报中包含了发送端的IP
地址和端口号以及接收端的IP
地址和端口号UDP
,如同发快递包裹一样
开发步骤
1.
创建DatagramSocket
:默认使用系统随机分配端口号2.
创建DatagramPacket
:将要发送的数据用字节数组表示,并指定要发送的数据长度,接收方的IP
地址和端口号3.
调用该DatagramSocket
类对象的send
方法:发送数据报DatagramPacket
对象4.
关闭DatagramSocket
对象:发送端程序结束,关闭通信套接字
1.
创建DatagramSocket
:指定监听的端口号2.
创建DatagramPacket
:指定接收数据用的字节数组,起到临时数据缓冲区的效果,并指定最大可以接收的数据长度3.
调用该DatagramSocket
类对象的receive
方法:接收数据报DatagramPacket
对象4.
关闭DatagramSocket
:接收端程序结束,关闭通信套接字
发送和接收消息
基于UDP
协议的网络编程仍然需要在通信实例的两端各建立一个Socket
,但Socket
Socket
,Java
提供了DatagramSocket
对象作为基于UDP
协议的Socket
,使用DatagramPacket
代表DatagramSocket
发送、接收的数据报。
举例
案例1
import java.io.IOException;
import java.net.*;
public class UDPCommunicateDemo1Send {
public static void main(String[] args) {
DatagramSocket ds = null;
try {
// 创建套接字
ds = new DatagramSocket();
byte[] bytes = "hello, this is a string ...".getBytes();
// 要发送的数据
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, Inet4Address.getByName("localhost"), 9999);
// 发送
ds.send(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (ds != null) {
ds.close();
}
}
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPCommunicateDemo1Receive {
public static void main(String[] args) {
DatagramSocket ds = null;
try {
// 创建套接字
ds = new DatagramSocket(9999);
// 接收数据
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
ds.receive(datagramPacket);
// 输出
String string = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
System.out.println(string + " from :" + datagramPacket.getAddress());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (ds != null) {
ds.close();
}
}
}
}
案例2
import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
public class UDPCommunicateDemo2Send {
public static void main(String[] args) throws IOException {
// 1. 创建DatagramSocket
DatagramSocket datagramSocket = new DatagramSocket();
// 2. 要发送的数据
ArrayList<String> strings = new ArrayList<>();
strings.add("字符串1");
strings.add("字符串2");
strings.add("字符串3");
// 3. 发送多个数据报
// 接收方ip 接收放端口
InetAddress ip = Inet4Address.getByName("localhost");
int targetPort = 9999;
for (int i = 0; i < strings.size(); i++) {
// 创建DatagramPacket
byte[] bytes = strings.get(i).getBytes();
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, ip, targetPort);
// 调用send发送
datagramSocket.send(datagramPacket);
}
// 4. 关闭DatagramSocket
datagramSocket.close();
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPCommunicateDemo2Receive {
public static void main(String[] args) throws IOException {
// 1. 创建DatagramSocket
DatagramSocket datagramSocket = new DatagramSocket(9999);
// 一直监听
while (true) {
// 2. 创建DatagramPacket
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
// 3. 接收数据
datagramSocket.receive(datagramPacket);
// 拆分数据
String string = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
System.out.println(string);
}
}
}
URL
编程
URL
类
URL
(Uniform Resource Locator
):,它表示Internet
上某一资源的地址- 通过
URL
可以访问Internet
上的各种网络资源,比如:www
、ftp
站点,浏览器通过解析给定的URL
可以在网络上查找相应的文件或其他资源 URL
的基本结构由5
部分组成:<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
- 例如:
http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
- 片段名:即锚点,例如:看小说,直接定位到章节
- 参数列表格式:
参数名=参数值&参数名=参数值
...
- 为了表示
URL
,java.net
中实现了类URL
- 构造器:
public URL(String spec)
:通过一个表示URL
地址的字符串可以构造一个URL
对象URL url = new URL("http://www. atguigu.com/")
public URL(URL context, String spec)
:通过基URL
和相对URL
构造URL
对象URL downloadUrl = new URL(url, "download.html")
public URL(String protocol, String host, String file)
URL url = new URL("http", "www.atguigu.com", "download. html")
public URL(String protocol, String host, int port, String file)
URL gamelan = new URL("http", "www.atguigu.com", 80, "download.html")
- 构造器:
URL
类的构造器都声明 ,必须要对这一异常进行处理,通常是用try-catch
语句进行捕获
URL
类常用方法
一个URL
对象生成后:
public String getProtocol()
:获取该URL
的协议名public String getHost()
:获取该URL
的主机名public String getPort()
:获取该URL
的端口号public String getPath()
:获取该URL
的文件路径public String getFile()
:获取该URL
的文件名public String getQuery()
:获取该URL
的查询
import java.net.MalformedURLException;
import java.net.URL;
public class URLTest {
public static void main(String[] args) throws MalformedURLException {
// 构建URL
URL url = new URL("https://www.example.com:8080/path/to/resource?query=value¶m=123");
System.out.println(url);
// 协议 https
System.out.println("getProtocol: " + url.getProtocol());
// 主机 www.example.com
System.out.println("getHost: " + url.getHost());
// 端口 8080
System.out.println("getPort: " + url.getPort());
// 路径 /path/to/resource
System.out.println("getPath: " + url.getPath());
// 文件 /path/to/resource?query=value¶m=123
System.out.println("getFile: " + url.getFile());
// 查询字符串 query=value¶m=123
System.out.println("getQuery: " + url.getQuery());
}
}
针对HTTP
协议的URLConnection
类
URL
的方法openStream()
:能从网络上读取数据- 若希望输出数据,例如:向服务器端的
CGI
(公共网关接口,Common Gateway Interface
,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一些数据,则必须先与URL
建立连接,然后才能对其进行读写,此时需要使用URLConnection
URLConnection
:表示到URL
所引用的远程对象的连接。当与一个URL
建立连接时,首先要在一个URL
对象上通过方法openConnection()
生成对应的URLConnection
对象,如果连接过程失败,将产生IOException
URL netchinaren = new URL("http://www.atguigu.com/index.shtml");
URLConnectonn u = netchinaren.openConnection();
- 通过
URLConnection
对象获取的输入流和输出流,即可以与现有的CGI
程序进行交互public Object getContent() throws IOException
public int getContentLength()
public String getContentType()
public long getDate()
public long getLastModified()
public InputStream getInputStream () throws IOException
public OutputSteram getOutputStream()throws IOException
小结
- 位于网络中的计算机具有唯一的
IP
地址,这样不同的主机可以互相区分 客户端-服务器
是一种最常见的网络应用程序模型。服务器是一个为其客户端提供某种特定服务的硬件或软件。客户机是一个用户应用程序,用于访问某台服务器提供的服务。端口号是对一个服务的访问场所,它用于区分同一物理计算机上的多个服务。套接字用于连接客户端和服务器,客户端和服务器之间的每个通信会话使用一个不同的套接字。TCP
协议用于实现面向连接的会话Java
中有关网络方面的功能都定义在java.net
程序包中。Java
用InetAddress
表示IP
地址,该对象里有两个字段:主机名(String
) 和IP
地址(int
)- 类
Socket
和ServerSocket
实现了基于TCP
协议的客户端-服务器
程序。Socket
是客户端和服务器之间的一个连接,连接创建的细节被隐藏了,这个连接提供了一个安全的数据传输通道,这是因为TCP
协议可以解决数据在传送过程中的丢失、损坏、重复、乱序以及网络拥挤等问题,它保证数据可靠的传送 - 类
URL
和URLConnection
提供了高级网络应用,通过URL
的网络资源的位置来统一表示Internet
上各种网络资源。通过URL
对象可以创建当前应用程序和URL
表示的网络资源之间的连接,这样当前程序就可以读取网络资源数据或者把自己的数据传送到网络上去