Java 网络编程
首先理清一个概念:网络编程不等于网站编程,网络编程即使用套接字来达到进程间通信,现在一般称为TCP/IP编程。
一、计算机网络
(一)概念
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
(二)计算机网络的主要功能
1.资源共享:
资源共享包括计算机硬件资源、软件资源和数据资源的共享。硬件资源的共享提高了计算机硬件资源的利用 率,由于受经济和其他因素的制约,这些硬件资派不可能所有用户都有,所以使用计算机网络不仅可以使用自身的硬件资源,也可共享网络上的资源。软件资源和数据资源的共享可以充分利用已有的信息资派.减少软件开发过程中的劳动,避免大型数据库的重复建设。
2.信息交换:
这是计算机网络最基本的功能.计算机网络中的计算机之间或计算机与终端之间,可以快速可靠地相互传递数据、程序或文件。例如,用户可以在网上传送电子邮件、交换数据,可以实现在商业部门或公司之间进行订单、发票等商业文件安全准确地交换。
3.均衡负荷与分布处理:
对于大型的任务或课题.如果都集中在一台计算机上.负荷太重,这时可以将任务分散到不同的计算机分别完成,或由网络中比较空闲的计算机分担负荷,各个计算机连成网络有利于共同协作进行重大科研课题的开发和研究。利用网络技术还可以将许多小型机或傲型机连成具有高性能的分布式计算机系统,使它具有解决复杂问题的能力,从而大大降低费用。
4.综合信息服务:
计算机网络可以向全社会提供各处经济信息、科研情报、商业信息和咨询服务,如Internet中的www就是如此。
(三)计算机网络分类
计算机网络按其覆盖的地理范围可分为如下3类:
局域网(LAN)。局域网是一种在小区域内使用的,由多台计算机组成的网络,覆盖范围通常局限在10 千米范围之内,属于一个单位或部门组建的小范围网。
城域网(MAN)。城域网是作用范围在广域网与局域网之间的网络,其网络覆盖范围通常可以延伸到整个城市,借助通信光纤将多个局域网联通公用城市网络形成大型网络,使得不仅局域网内的资源可以共享,局域网之间的资源也可以共享。
广域网(WAN) 广城网是一种远程网,涉及长距离的通信,覆盖范围可以是个国家或多个国家,甚至整个世界。由于广域网地理上的距离可以超过几千千米,所以信息衰减非常严重,这种网络一般要租用专线,通过接口信息处理协议和线路连接起来,构成网状结构,解决寻径问题。
二、网络通信协议及接口
(一)网络通信协议
计算机网络中实现通信必须有一些约定,即通信协议;包括对速率,传输代码,代码结构,传输控制步骤,出错控制等制定的标准。常见的网络通信协议有:TCP/IP协议、IPX/SPX协议、NetBEUI协议等。
TCP/IP协议:传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocal),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
(二)网络通信接口
为了使两个节点之间能进行对话,必须在他们之间建立通信工具(即接口),使彼此之间,能进行信息交换。接口包括两部分:
硬件装置:实现结点之间的信息传送
软件装置:规定双方进行通信的约定协议
(三)通信协议分层思想
1.为什么要分层
由于结点之间联系很复杂,在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式就是层次方式,及同层间可以通信,上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。
2.通信协议的分层规定
把用户应用程序作为最高层,把物理通信线路作为最底层,将其间的协议处理分为若干层,规定每层处理的任务,也规定每层的接口标准。
3.参考模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0pedjAO-1630478639617)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210901111000207.png)]
上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。
-
应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
-
传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
-
网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
-
数据链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
我们编写的程序位于应用层,因此我们的程序是和TCP/UDP打交道的。
(四)协议分类
通信的协议还是比较复杂的,java.net
包中包含的类和接口,它们提供低层次的通信细节,我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节。
java.net
包中提供了两种常见的网络协议的支持:TCP和UDP
- TCP是可靠的连接,TCP就像打电话,需要先打通对方电话,等待对方有回应后才会跟对方继续说话,也就是一定要确认可以发信息以后才会把信息发出去。TCP上传任何东西都是可靠的,只要两台机器上建立起了连接,在本机上发送的数据就一定能传到对方的机器上。
- UDP就好比发电报,发出去就完事了,对方有没有接收到它都不管,所以UDP是不可靠的。
- TCP传送数据虽然可靠,但传送得比较慢;UDP传送数据不可靠,但是传送得快。
1.UDP
用户数据报协议(User Datagram Protocol)。
数据报(Datagram):网络传输的基本单位
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时,不建议使用UDP协议。
特点:数据被限制在64kb以内,超出这个范围就不能发送了。
2.TCP
传输控制协议(Transmission Control Protocol)。
TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。
(1)三次握手
TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
- 第一次握手,客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
(客户端向服务器端发出连接请求,等待服务器确认。) - 第二次握手,服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。(服务器端向客户端回送一个响应,通知客户端收到了连接请求。)
- 三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。
(2)四次挥手
其次,TCP的客户端和服务端断开连接,需要四次挥手
- 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
- 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态。
- 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
- 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
- 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
- 服务器收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
- 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。
你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。这里一点需要注意是:主动关闭连接的,才有 TIME_WAIT 状态。
为什么挥手需要四次?
关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。参考
通俗理解:
- 三次握手
A:我要过来了!B:我知道你要过来了!A:我现在过来! - 四次挥手
A:我们分手吧!B:真的分手吗?B:真的真的要分手吗?A:是的!
由此,可以可靠地进行连接和断开。
三、IP协议
(一)概念
IP协议:网络互连协议
(二)分类
IP地址根据版本可以分类为:IPv4和IPv6
ipv4 | ipv6 | |
---|---|---|
地址长度 | IPv4协议具有32位(4字节)地址长度 | IPv6协议具有128位(16字节)地址长度 |
格式 | IPv4 地址的文本格式为 nnn.nnn.nnn.nnn,其中 0<=nnn<=255,而每个 n 都是十进制数。可省略前导零。 | IPv6 地址的文本格式为 xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx,其中每个 x 都是十六进制数可省略前导零。 |
数量 | 共有43亿,30亿在北美,4亿在亚洲,2011年就已经用尽 | 多到每一粒沙子都可以分配一个IPv6地址 |
IPv4又可以分为五类:
-
A类:在IP地址的四段号码中,第一段号码为网络号码,剩下的三段号码为本地计算机的号码;A类IP地址中网络的标识长度为8位,主机标识的长度为24位,A类网络地址数量较少,有126个网络,每个网络可以容纳主机数达1600多万(256的三次方-2)台。
-
B类:在IP地址的四段号码中,前两段号码为网络号码。B类IP地址中网络的标识长度为16位,主机标识的长度为16位,B类网络地址适用于中等规模的网络,有16384个网络,每个网络所能容纳的计算机数为6万多(256的二次方-2)台
-
C类:在IP地址的四段号码中,前三段号码为网络号码,剩下的一段号码为本地计算机的号码;此类地址中网络的标识长度为24位,主机标识的长度为8位,C类网络地址数量较多,有209万余个网络。适用于小规模的局域网络,每个网络最多只能包含254(256-2)台计算机
-
D类:此类IP地址在历史上被叫做多播地址(multicast address),即组播地址;在以太网中,多播地址命名了一组应该在这个网络中应用接收到一个分组的站点;多播地址的最高位必须是“1110”,范围从224.0.0.0到239.255.255.255
-
E类: 此类地址也不分网络地址和主机地址,它的第1个字节的前五位固定为“11110”,为将来使用保留,地址范围从240.0.0.1到255.255.255.254
(三)InetAddress类
说到IP地址,就要引入一个类:InetAddress
此类表示互联网协议 (IP) 地址。
InetAddress类无构造方法
1.常用方法摘要
byte[] getAddress()
返回此 InetAddress 对象的原始 IP 地址。
static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。
String getHostAddress()
返回 IP 地址字符串(以文本表现形式)。
String getHostName()
获取此 IP 地址的主机名。
static InetAddress getLocalHost()
返回本地主机。
127.0.0.1:本机地址,主要用于测试。别名:Localhost
案例演示1
import java.net.InetAddress;
import java.net.UnknownHostException;
public class TestIP {
public static void main(String[] args) throws UnknownHostException {
//InetAdress类表示IP地址
//获取本机IP
InetAddress ip = InetAddress.getLocalHost();// ADMINISTRATOR/192.xxx.xxx.xxx
System.out.println(ip);
//获得主机名
System.out.println(ip.getHostName());// ADMINISTRATOR
//获得IP地址
System.out.println(ip.getHostAddress());// 192.xxx.xxx.xxx
//getLocalHost=getHostName+getHostAddress
System.out.println(InetAddress.getByName("localhost"));
}
}
案例演示2
import java.net.InetAddress;
import java.net.UnknownHostException;
public class TestIP2 {
public static void main(String[] args) throws UnknownHostException {
InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
// 获取此 IP 地址的主机名。
System.out.println(inetAddress.getHostName());
//返回 IP 地址字符串(以文本表现形式)。
System.out.println(inetAddress.getHostAddress());
}
}
四、端口
- IP地址用来标识一台计算机,但是一台计算机上可能提供多种网络应用程序,如何来区分这些不同的程序呢?这就要用到端口。端口号是用来区分一台机器上不同的应用程序的。
- 我们使用IP地址加端口号,就可以保证数据准确无误地发送到对方计算机的指定软件上了。
- 端口是虚拟的概念,是一个逻辑端口。
- 当我们使用网络软件一打开,那么操作系统就会为网络软件分配一个随机的端口号,或者打开的时候向系统要指定的端口号。
- 通过端口,可以在一个主机上运行多个网络应用程序。端口的表示是一个16位的二进制整数,2个字节,对应十进制的0~65535。
- 端口号在计算机内部是占2个字节。一台机器上最多有65536个端口号。一个应用程序可以占用多个端口号。端口号如果被一个应用程序占用了,那么其他的应用程序就无法再使用这个端口号了。
- 记住一点,我们编写的程序要占用端口号的话占用1024以上的端口号,1024以下的端口号不要去占用,因为系统有可能会随时征用。端口号本身又分为TCP端口和UDP端口,TCP的8888端口和UDP的8888端口是完全不同的两个端口。TCP端口和UDP端口都有65536个
(一)分类
公有端口:0~1023
- HTTP:80
- HTTPS:443
- FTP:21
- Telnet:23
程序注册端口(分配给用户或者程序):1024~49151
- Tomcat:8080
- MySQL:3306
- Oracle:1521
- 动态、私有端口:49152~65535
(二)DOS命令查看端口
- 查看所有端口:netstat -ano
- 查看指定端口:netstat -ano|findstr “端口号”
- 查看指定端口的进程:tasklist|findstr “端口号”
(三)InetSocketAddress类
说到端口,则要引入一个类:InetSocketAddress
此类实现 IP 套接字地址(IP 地址 + 端口号)。
1.构造方法摘要
InetSocketAddress(InetAddress addr, int port)
//根据 IP 地址和端口号创建套接字地址。
InetSocketAddress(int port)
//创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值。
InetSocketAddress(String hostname, int port)
//根据主机名和端口号创建套接字地址。
2.常用方法摘要
InetAddress getAddress()
//获取 InetAddress。
String getHostName()
//获取 hostname。
int getPort()
//获取端口号。
案例演示
import java.net.InetAddress;
import java.net.InetSocketAddress;
public class TestPort {
public static void main(String[] args) {
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",8082);
System.out.println(inetSocketAddress);
//返回主机名
System.out.println(inetSocketAddress.getHostName());
//获得InetSocketAddress的端口
System.out.println(inetSocketAddress.getPort());
//返回一个InetAddress对象(IP对象)
InetAddress address = inetSocketAddress.getAddress();
System.out.println(address);
}
}
五、TCP网络编程和UDP网络编程
网络编程也叫做Socket编程,即套接字编程。套接字指的是两台设备之间通讯的端点。
(一)TCP网络编程
1.概述
TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
(1)两端通信时步骤:
- 服务端程序,需要事先启动,等待客户端的连接。
- 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
(2)在Java中,提供了两个类用于实现TCP通信程序:
- 客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
- 服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
2.Socket类
Socket
类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
(1)构造方法摘要
public Socket(String host, int port)
:创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。
回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。
案例演示
Socket client = new Socket("127.0.0.1", 6666);
(2)常用方法摘要
public InputStream getInputStream() : 返回此套接字的输入流。
//如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
//关闭生成的InputStream也将关闭相关的Socket。
public OutputStream getOutputStream() : 返回此套接字的输出流。
//如果此Socket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
//关闭生成的OutputStream也将关闭相关的Socket。
public void close() :关闭此套接字。
//一旦一个socket被关闭,它不可再使用。
//关闭此socket也将关闭相关的InputStream和OutputStream 。
public void shutdownOutput() : 禁用此套接字的输出流。
//任何先前写出的数据将被发送,随后终止输出流。
3.ServerSocket类
ServerSocket
类:这个类实现了服务器套接字,该对象等待通过网络的请求。
(1)构造方法摘要
-
public ServerSocket(int port) //使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
案例演示
ServerSocket server = new ServerSocket(6666);
(2)常用方法摘要
-
public Socket accept() //侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。
4.代码实现
案例演示1
客户端:
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args){
Socket socket = null;
OutputStream os = null;
try {
//1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet,8090);
//2、获取一个输出流,用于写出要发送的数据
os = socket.getOutputStream();
//3、写出数据
os.write("你好,我是客户端!".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {//4、释放资源
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务端:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1、创建服务端的ServerSocket,指明自己的端口号
serverSocket = new ServerSocket(8090);
//2、调用accept接收到来自于客户端的socket
socket = serverSocket.accept();//阻塞式监听,会一直等待客户端的接入
//3、获取socket的输入流
is = socket.getInputStream();
// 不建议这样写:因为如果我们发送的数据有汉字,用String的方式输出可能会截取汉字,产生乱码
// int len=0;
// byte[] buffer = new byte[1024];
// while ((len=is.read(buffer))!=-1){
// String str = new String(buffer, 0, len);
// System.out.println(str);
// }
//4、读取输入流中的数据
//ByteArrayOutputStream的好处是它可以根据数据的大小自动扩充
baos = new ByteArrayOutputStream();
int len=0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
baos.write(buffer,0,len);
}
System.out.println("收到了来自于客户端"+socket.getInetAddress().getHostName()
+"的消息:"+baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {//5、关闭资源
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(baos!=null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器是没有IO流的,服务器可以获取到请求的客户端对象socket
使用每个客户端socket中提供的IO流和客户端进行交互
服务器使用客户端的字节输入流读取客户端发送的数据
服务器使用客户端的字节输出流给客户端回写数据
案例演示2
服务端向客户端回写数据
客户端:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args){
Socket socket = null;
OutputStream os = null;
ByteArrayOutputStream baos=null;
InputStream is=null;
try {
//1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet,8888);
//2、获取一个输出流,用于写出要发送的数据
os = socket.getOutputStream();
//3、写出数据
os.write("你好,我是客户端!".getBytes());
//==========================解析回复==================================
//4、首先必须通知服务器,我已经输出完毕了,不然服务端不知道什么时候输出完毕
//服务端的while循环会一直执行,会阻塞
socket.shutdownOutput();
///5、获取输入流,用于读取服务端回复的数据
is = socket.getInputStream();
baos = new ByteArrayOutputStream();
int len=0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
baos.write(buffer,0,len);
}
System.out.println("收到了来自服务端的消息:"+baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {//6、释放资源
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos!=null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务端:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
OutputStream os=null;
try {
//1、创建服务端的ServerSocket,指明自己的端口号
serverSocket = new ServerSocket(8888);
//2、调用accept接收到来自于客户端的socket
socket = serverSocket.accept();//阻塞式监听,会一直等待客户端接入
//3、获取socket的输入流
is = socket.getInputStream();
// 不建议这样写:因为如果我们发送的数据有汉字,用String的方式输出可能会截取汉字,产生乱码
// int len=0;
// byte[] buffer = new byte[1024];
// while ((len=is.read(buffer))!=-1){
// String str = new String(buffer, 0, len);
// System.out.println(str);
// }
//4、读取输入流中的数据
//ByteArrayOutputStream的好处是它可以根据数据的大小自动扩充
baos = new ByteArrayOutputStream();
int len=0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
baos.write(buffer,0,len);
}
System.out.println("收到了来自于客户端"+socket.getInetAddress().getHostName()
+"的消息:"+baos.toString());
//===========================回复==========================================
//5、获取一个输出流,写出回复给客户端
os = socket.getOutputStream();
//6、写出数据
os.write("你好,我是服务端".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {//7、关闭资源
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(baos!=null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
案例演示3
上传文件:客户端发送文件给服务端,服务端将文件保存在本地
客户端:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) {
Socket socket = null;
FileInputStream fis = null;
OutputStream os = null;
try {
//1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet, 8888);
//2、创建一个文件输入流,读取要上传的文件
fis = new FileInputStream("D:/test/touxiang.jpg");
//3、获取一个输出流,用于写出要发送的数据
os = socket.getOutputStream();
byte[] buffer = new byte[1024];
int len=0;
while((len=fis.read(buffer))!=-1){
//4、写出数据
os.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {//5、释放资源
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务端:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
FileOutputStream fos = null;
InputStream is = null;
try {
//1、创建服务端的ServerSocket,指明自己的端口号
serverSocket = new ServerSocket(8888);
//2、调用accept接收到来自于客户端的socket
socket = serverSocket.accept();//阻塞式监听,会一直等待客户端的接入
//3、创建一个文件输出流,用于将读取到的客户端上传的文件输出
fos = new FileOutputStream("touxiang.jpg");
//4、获取socket的输入流
is = socket.getInputStream();
byte[] buffer = new byte[1024];
int len=0;
while((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);//5、写出文件
}
} catch (IOException e) {
e.printStackTrace();
} finally {//6、释放资源
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
假设大海里有许多岛屿,客户端和服务端相当于其中的两座岛屿,“客户端”岛屿生产了一种农作物要运到“服务端”岛屿,所以“客户端”要知道“服务端”确切的地址(IP),不然就运错地方了,socket就相当于运输的船只,port就相当于“服务端”岛屿的某个港口。
(二)UDP网络编程
从技术意义上来讲,只有TCP才会分Server和Client。对于UDP来说,严格意义上,并没有所谓的Server和Client。
java.net
包给我们提供了两个类DatagramSocket
(此类表示用于发送和接收数据报的套接字)和DatagramPacket
(该类表示数据报的数据包。 )
1.DatagramSocket
(1)构造方法摘要
protected DatagramSocket()
//构造数据报套接字并将其绑定到本地主机上的任何可用端口。
protected DatagramSocket(int port)
//构造数据报套接字并将其绑定到本地主机上的指定端口。
protected DatagramSocket(int port, InetAddress laddr)
//创建一个数据报套接字,绑定到指定的本地地址。
2.DatagramPacket
(1)构造方法摘要
DatagramPacket(byte[] buf, int offset, int length)
//构造一个 DatagramPacket用于接收指定长度的数据报包到缓冲区中。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
//构造用于发送指定长度的数据报包到指定主机的指定端口号上。
(2)常用方法摘要
byte[] getData()
//返回数据报包中的数据。
InetAddress getAddress()
//返回该数据报发送或接收数据报的计算机的IP地址。
int getLength()
//返回要发送的数据的长度或接收到的数据的长度。
3.代码实现
案例演示1
发送方:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPSender {
public static void main(String[] args) throws IOException {
//1、创建一个socket
DatagramSocket socket = new DatagramSocket();
InetAddress inet = InetAddress.getLocalHost();
String msg="你好,很高兴认识你!";
byte[] buffer = msg.getBytes();
//2、创建一个包(要发送给谁)
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length,inet,9090);
//3、发送包
socket.send(packet);
//4、释放资源
socket.close();
}
}
接收方:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPReceiver {
public static void main(String[] args) throws IOException {
//1、创建一个socket,开放端口
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[1024];
//2、创建一个包接收数据
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
//3、接收数据
socket.receive(packet);//阻塞式接收
//将数据包转换为字符串输出
String msg = new String(packet.getData(), 0, packet.getLength());
System.out.println(msg);
//4、释放资源
socket.close();
}
}
注意:
如果是TCP中先启动客户端会报错:
而如果是UDP中先启动发送方不会报错,但会正常退出。
案例演示2
完成在线咨询功能,学生和老师在线一对一交流(多线程)
发送方:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPSender implements Runnable{
//创建一个socket
DatagramSocket socket=null;
//创建一个流 用于录入键盘的数据
BufferedReader bfr=null;
//发送数据目的地的IP
private String toIP;
//发送数据目的地的端口
private int toPort;
public UDPSender(String toIP, int toPort) {
this.toIP = toIP;
this.toPort = toPort;
try {
socket=new DatagramSocket();//创建一个socket
} catch (SocketException e) {
e.printStackTrace();
}
bfr=new BufferedReader(new InputStreamReader(System.in));//从键盘录入数据到流中
}
@Override
public void run() {
while (true){//循环发送数据
try {
String msg = bfr.readLine();//从流中读取数据
byte[] buffer = msg.getBytes();
InetAddress inet = InetAddress.getByName(toIP);
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length, inet, toPort);
socket.send(packet);
//如果发送了拜拜,则退出发送
if(msg.equals("拜拜")){
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
//释放资源
if(socket!=null){
socket.close();
}
if (bfr!=null){
try {
bfr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
接收方:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPReceiver implements Runnable{
//创建一个socket
DatagramSocket socket=null;
//接收方自己所在的端口
private int fromPort;
//数据发送者的姓名
private String msgFrom;
public UDPReceiver(int fromPort,String msgFrom) {
this.fromPort = fromPort;
this.msgFrom=msgFrom;
try {
socket=new DatagramSocket(fromPort);//创建一个socket
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){//循环接收
try {
byte[] buffer = new byte[1024 * 8];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
socket.receive(packet);
String msg = new String(packet.getData(), 0, packet.getLength());
System.out.println(msgFrom+":"+msg);
if (msg.equals("拜拜")){//如果接收到的数据为拜拜,则退出接收
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
//释放资源
socket.close();
}
}
学生线程:
public class Student {
public static void main(String[] args) {
new Thread(new UDPSender("127.0.0.1",8888)).start();
new Thread(new UDPReceiver(7777,"老师")).start();
}
}
老师线程:
public class Teacher {
public static void main(String[] args) {
new Thread(new UDPSender("127.0.0.1",7777)).start();
new Thread(new UDPReceiver(8888,"学生")).start();
}
}xxxxxxxxxx 老师线程:public class Teacher { public static void main(String[] args) { new Thread(new UDPSender("127.0.0.1",7777)).start(); new Thread(new UDPReceiver(8888,"学生")).start(); }}
学生的窗口:
老师的窗口:
六、URL
(一)概念
URL(Uniform Resource Locator):统一资源定位符,它表示Internet上某一资源的地址。
通过URL我们可以访问Internet上的各种网络资源,比如最常见的www,ftp站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。
URI=URL+URN
- URI:Uniform Resource Identifier ,统一资源标志符。
- URL:Uniform Resource Locator,统一资源定位符。
- URN:Uniform Resource Name,统一资源命名。
网络三大基石:HTML,HTTP,URL
(二)格式
URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
- 片段名:即锚点,例如看小说,直接定位到章节
- 参数列表格式:
参数名=参数值&参数名=参数值......
例如:
http://localhost:8080/index.jsp#a?username=Tom&password=123456
(三)URL类
java.net
包下
1.构造方法摘要
URL(String spec)
//根据 String 表示形式创建 URL 对象。
URL(String protocol, String host, int port, String file)
//根据指定协议名、主机名、端口号和文件名创建 URL 对象。
URL(String protocol, String host, String file)
//根据指定的协议名、主机名和文件名创建 URL。
2.常用方法摘要
String getProtocol()
//获取此 URL的协议名称。
String getHost()
//获取此 URL 的主机名。
int getPort()
//获取此 URL 的端口号。
String getPath()
//获取此 URL 的文件路径。
String getFile()
//获取此 URL 的文件名。
String getQuery()
//获取此 URL的查询部分。
URLConnection openConnection()
//返回一个URLConnection实例,表示与URL引用的远程对象的URL 。
//URLConnection类中又有一个方法:
InputStream getInputStream()//返回从此打开的连接读取的输入流。
案例演示1
import java.net.MalformedURLException;
import java.net.URL;
public class Test {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://localhost:8080/index.jsp?username=Tom&password=123456");
System.out.println(url.getProtocol());//获取协议名
System.out.println(url.getHost());//获取主机名
System.out.println(url.getPort());//获取端口号
System.out.println(url.getPath());//获取文件路径
System.out.println(url.getFile());//获取文件名
System.out.println(url.getQuery());//获取查询名
}
}
案例演示2
URL下载网络资源
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class Test {
public static void main(String[] args) throws IOException {
//下载地址
URL url = new URL("https://img.t.sinajs.cn/t6/style/images/global_nav/WB_logo.png?id=1404211047727");
//连接到这个资源 HTTP
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
InputStream is = urlConnection.getInputStream();
FileOutputStream fos = new FileOutputStream("weibo.jpg");
byte[] buffer = new byte[1024];
int len=0;
while ((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//释放资源
urlConnection.disconnect();//断开连接
is.close();
fos.close();
}
}