目录
软件结构
-
C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、红蜘蛛、飞秋等软件,相对更安全。
程序员:需要开发客户端和服务端程序,升级更新
用户:需要安装客户端
B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有IE、谷歌、火狐等。
程序员:只需要开发服务端程序,无序开发客户端程序。
用户:只需要有浏览器就可以访问服务器
网络编程三要素
网络通信协议
TCP/IP协议参考模型
-
网络通信协议:通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
-
TCP/IP协议: 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
IP地址
IP地址:指互联网协议地址(Internet Protocol Address)
IP地址分类方式:
-
IPv4:是一个32位的二进制数,通常被分为4个字节,例如
192.168.65.100
。 -
IPv6:互联网的蓬勃发展,但是网络地址资源有限,使得IP的分配越发紧张。为扩大空间,拟通过IPv6重新定义地址空间。表示成
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
-
公网地址
-
局域网地址比如192.168.1.53
-
id地址 = 网络地址+主机地址
- 子网掩码:255.255.255.0 配合ip地址使用来标识网络地址。
查看本机IP地址,在控制台输入:
ipconfig /all
检查网络是否连通,在控制台输入:
ping 空格 IP地址 ping 220.181.57.216
特殊的IP地址:
-
本地回环地址(hostAddress):
127.0.0.1
-
主机名(hostName):
localhost
域名:
因为IP地址数字不便于记忆,因此出现了域名,域名容易记忆,但IP地址还是网络中计算机的唯一标识,最终还是通过IP地址找到计算机,所以需要把域名解析成IP地址。可通过DNS解析,得到IP地址。(www.baidu.com--> 220.181.38.150)
———DNS: Domain Name System
端口号
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
-
端口号:用两个字节表示的整数,它的取值范围是0~65535。
-
公认端口:0~1023。被预先定义的服务通信占用,如:HTTP(80),FTP(21),Telnet(23)。1024之前的端口不建议使用
-
注册端口:1024~49151。分配给用户进程或应用程序。如:Tomcat(8080),MySQL(3306),Oracle(1521)。
-
动态/ 私有端口:49152~65535。
-
网络编程概述
Java实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。
-
网络编程目的:直接或间接的通过网络协议与其他计算机实现数据交换,进行通讯。
-
网络编程中有两个主要问题:
1、如何准确地定位网络上一台或多台主机:定位主机上的特定应用
2、找到主机后如何进行可靠高效的数据传输 -
网络编程中对应的两个要素:
1、IP和端口号
2、提供网络通信协议: TPC/IP参考模型(应用层、传输层、物理+数据链路层)
InetAddress类 和 端口号
-
通信要素一:IP和端口号
- IP:唯一的Internet上的计算机(通信实体)
- IP分类:IPv4 和 IPv6 ;万维网和局域网
- 域名:可通过DNS解析,得到IP地址。(www.baidu.com--> 220.181.38.150)
———DNS: Domain Name System - 本机回路地址:127.0.0.1 对应着:Localhost
- InetAddress类常用方法
static InetAddress | getLocalHost() 返回本地主机。 |
String | getHostName() 获取此 IP 地址的主机名(域名)。 |
static InetAddress | getByName(String host) 获取指定的IP对象。 |
String | getHostAddress() 返回 IP 地址字符串(以文本表现形式)。 |
//创建InetAddress对象(类似File类)
InetAddress inet1 = InetAddress.getByName("192.168.10.14");
System.out.println(inet1); ///192.168.10.14
//填入域名
InetAddress inet2 = InetAddress.getByName("www.baidu.com");
System.out.println(inet2); //www.baidu.com/220.181.38.149
//获取本机ip
InetAddress inet3 = InetAddress.getByName("127.0.0.1");
System.out.println(inet3); ///127.0.0.1
InetAddress inet4 = InetAddress.getLocalHost();
System.out.println(inet4); //DESKTOP-071BL0K/192.168.1.3
//常用方法
//获取InetAddress 域名
System.out.println(inet2.getHostName());
//获取InetAddress ip地址
System.out.println(inet2.getHostAddress());
-
端口号:表示正在计算机上运行的进程(程序);
- 不同进程有不同的端口号
- 被规定为 16 位的整数 : 0~65535
- 端口号与IP地址的组合得出一个网络套接字:Socket。
- 端口分类:
- 公认端口:0~1023 被预先定义的服务器通信占用(HTTP80、FTP21)
- 注册端口:1024~49151 分配给用户进程或应用程序(MySQL3306、Oracle1521)
- 动态\私有端口:49152~65535
网络协议 TCP\UDP\URL
计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
TCP:面向链接、数据可靠、传输数据流无限制,相对效率低 (打电话)
UDP:非面向链接、不可靠、传输的数据量有限,相对效率高(发短信)
实现TCP的网络编程
三次握手、四次挥手
-
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
-
第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
-
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
-
第三次握手,客户端再次向服务器端发送确认信息,确认连接。
-
-
Socket、ServerSocket类常用方法
Socket类
Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 |
OutputStream | getOutputStream() 返回此套接字的输出流。 |
InputStream | getInputStream() 返回此套接字的输入流。 |
void | shutdownOutput() 禁用此套接字的输出流。 |
void | shutdownInput() 此套接字的输入流置于“流的末尾”。 |
void | setSoTimeout(int timeout) // 启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位。 |
InetAddress | getInetAddress() 返回套接字连接的地址。 |
InetAddress | getLocalAddress() 获取套接字绑定的本地地址。 |
int | getLocalPort() 返回此套接字绑定到的本地端口。 |
void | close() 关闭此套接字。 |
ServerSocket类
ServerSocket(int port) 创建绑定到特定端口的服务器套接字。 |
Socket | accept() 侦听并接受到此套接字的连接。 |
InetAddress | getInetAddress() 返回此服务器套接字的本地地址。 |
String | getHostAddress() 返回 IP 地址字符串(以文本表现形式)。 |
例子1:客户端发送信息给服务端,服务端显示在服务台上
//客户端
public class Client {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
//1.创建socket,传入发送到的ip和端口号
Socket socket = new Socket("",9999);
//2.通过socket获取输出流
OutputStream os = socket.getOutputStream();
//3.发送数据
System.out.println("请输入您要发送的话:");
os.write(scanner.next().getBytes());
//传输完毕,shotdown停止输出
socket.shutdownOutput();
//4.接收服务端反馈
InputStream is = socket.getInputStream();
byte[] bys = new byte[1024];
int readCount = is.read(bys);
System.out.println(new String(bys,0,readCount));
//关闭流
is.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务端
public class Setver {
public static void main(String[] args) {
System.out.println("服务端已启动....");
try {
//1.创建serverSocket,填入端口号
ServerSocket serverSocket = new ServerSocket(9999);
//2.调用accept(),监听客户端连接 请求接收客户端Socket
Socket socket = serverSocket.accept(); //阻塞方法
System.out.println("接受了来自"+socket.getInetAddress().getHostAddress()+"的消息:");
//3.通过socket 获取输入流
InputStream is = socket.getInputStream();
//4.读取输入流中内容
byte[] bys = new byte[1024];
int readCount;
while ((readCount = is.read(bys)) != -1) {
System.out.println(new String(bys,0,readCount));
}
//5.反馈给客户端
OutputStream os = socket.getOutputStream();
os.write("成功接收数据!".getBytes());
socket.shutdownOutput();
//关闭流
os.close();
is.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
例子2:客户端发送文件给服务端,服务端保存在本地
//客户端
public class Clinet {
public static void main(String[] args) {
try {
//1.创建socket,传入服务端的ip和端口号
Socket socket = new Socket("127.0.0.1",9999);
//2.通过socket获取输出流
OutputStream os = socket.getOutputStream();
//3.创建要传输的文件 的File实例
File file = new File("C:\\Users\\Plisetsky\\Desktop\\好康的.jpg");
String name = file.getName();
//4.传输文件
//先发送文件名 使用数据流,其他流可能导致数据冲突
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF(name);//注意此处,Data数据流中 writeUTF比较特殊,
//读取要发送的文件,并发送文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(os);
byte[] bys = new byte[1024];
int rc;
//一边读一边写
while ((rc=bis.read(bys))!=-1){
bos.write(bys,0,rc);
bos.flush(); //刷新,输出缓存中的文件
}
//通知服务器,发送完毕,否则服务端段bis.read(bys)将一直阻塞
socket.shutdownOutput();//关闭输出流
bis.close();
//4.接收服务端反馈
InputStream is = socket.getInputStream();
byte[] bys1 = new byte[1024];
int readCount = is.read(bys1);
System.out.println(new String(bys1,0,readCount));
//关闭流
is.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务端
public class Server {
public static void main(String[] args) {
System.out.println("服务端已启动....");
try {
//1.创建serverSocket,填入端口号
ServerSocket serverSocket = new ServerSocket(9999);
//2.调用accept(),监听客户端连接 请求接收客户端Socket
Socket socket = serverSocket.accept();
System.out.println("接受了来自"+socket.getInetAddress().getHostAddress()+"的消息:");
//3.通过socket 获取输入流
InputStream is = socket.getInputStream();
//4.读取输入流中内容
//读入文件名
DataInputStream dis = new DataInputStream(is);
String name = dis.readUTF();//读取到文件名
System.out.println("文件名:"+name);
//读取传入的文件
BufferedInputStream bis = new BufferedInputStream(is);
//输出文件,保存到本地
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(name));
byte[] bys = new byte[1024];
int readCount1;
//一遍读一遍写
while ((readCount1 = bis.read(bys))!=-1){ //read会等待,直到有数据传输或流关闭
bos.write(bys,0,readCount1);
bos.flush(); //刷新流,写出缓存中数据
}
//关闭bos流,停止读入
bos.close();
socket.shutdownInput();
//获取客户端ip
InetAddress inet = socket.getInetAddress();
String ip = inet.getHostAddress();
//控制台输出查看对方ip和上传的文件
System.out.println(ip + "-正在上传文件:" + name);
//5.反馈给客户端
OutputStream os = socket.getOutputStream();
os.write("成功接收数据!".getBytes());
//关闭流
os.close();
is.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
关闭流注意事项
- 背景
笔者在编写基于BIO实现服务器复读客户端信息的小实验时,使用了read方法来遍历读取inputStream中的内容,诸如FileInputstream中,当流读取完毕时,read便会返回-1以表示读取完毕,笔者想当然的也使用该逻辑来判断流读取完毕。代码运行之后,客户端发送到服务端的数据之后,迟迟未收到返回的数据,经排查是堵塞在了读取流的地方。经过查阅资料,socketInputStream的read方法,在没有数据读取时,并不会返回-1,而是会一直堵塞,等待新数据的传输。
那么何时关闭流呢?在网络编程中,流何时进行关闭,有以下几种方法。
1)发送完后调用Socket的shutdownOutput()方法关闭输出流,这样对端的输入流上的read操作就会返回-1。
注意不能调用socket.getInputStream().close()。这样会导致socket被关闭。
当然如果不需要继续在socket上进行读操作,也可以直接关闭socket。
但是这个方法不能用于通信双方需要多次交互的情况。
2)发送数据时,约定数据的首部固定字节数为数据长度。这样读取到这个长度的数据后,就不继续调用read方法。
3)为了防止read操作造成程序永久挂起,还可以给socket设置超时。
如果read()方法在设置时间内没有读取到数据,就会抛出一个java.net.SocketTimeoutException异常。
例如下面的方法设定超时3秒。
socket.setSoTimeout(3000);
实现UDP的网络编程
- 常用方法
DatagramPacket(byte[] buf, int length) 构造 DatagramPacket ,用来接收长度为 length 的数据包。 |
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。 |
void | receive(DatagramPacket p) 从此套接字接收数据报包。 |
void | send(DatagramPacket p) 从此套接字发送数据报包。 |
void | connect(InetAddress address, int port) 将套接字连接到此套接字的远程地址。 |
void | connect(SocketAddress addr) 将此套接字连接到远程套接字地址(IP 地址 + 端口号)。 |
void | disconnect() 断开套接字的连接。 |
InetAddress | getInetAddress() 返回此套接字连接的地址。 |
int | getLocalPort() 返回此套接字绑定的本地主机上的端口号。 |
SocketAddress | getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null 。 |
int | getPort() 返回此套接字的端口。 |
- UDP代码测试
/*
* UDP的网络编程
* */
public class UDPTest {
//发送端
@Test
public void send() throws IOException {
//数据报套接字
DatagramSocket socket = new DatagramSocket();
InetAddress inet = InetAddress.getLocalHost();
//封装数据报
String str = "我是UPD方式发送的导弹";
byte[] data = str.getBytes();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
//发送数据报
socket.send(packet);
socket.close();
}
//接收端
@Test
public void receive() throws IOException {
DatagramSocket socket = new DatagramSocket(9090);
//接收数据报
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
//拆包
String st = new String(packet.getData(),0,packet.getLength())
System.out.println(st);
socket.close();
}
}
URL: 统一资源定位符
-
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。
-
它是一种具体的URL,即 URL 可以用来表示一个资源,而且还指明了如何 locate 这个资源。
-
通过URL我们可以访问 Internet 上的各种网路资源,比如最常见的 www,ftp 站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。
-
URL的基本结构由 5 部分组成:
-
<传续协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
-
例如: http://192.168.1.100:8080//helloworld/index.jsp#a?usermame=shkstart&password=123
-
#片段名:即错点,例如看小说,直接定位到章节
-
参数列表格式:参数名=参数值&参数名=参数值....
-
- 常用方法:
URL(String spec) 根据 String 表示形式创建 URL 对象。 |
String | getProtocol() 获取此 URL 的协议名称。 |
String | getHost() 获取此 URL 的主机名(如果适用)。 |
int | getPort() 获取此 URL 的端口号。 |
String | getPath() 获取此 URL 的路径部分。 |
String | getFile() 获取此 URL 的文件名。 |
String | getQuery() 获取此 URL 的查询部分。 |
- URL代码测试
/*
* URL网络编程
* 1.URL:统一资源定位符,对应着互联网的某一资源地址
* 2.格式:
* http://Localhost:8080/exampLes/beauty.jpg?username=Tom
* 协议 主机名 端口号 资源地址 参数列表
*
* */
public class URLTest {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://Localhost:8080/exampLes/beauty.jpg?username=Tom");
//获取协议名 http
System.out.println(url.getProtocol());
//获取主机名 Localhost
System.out.println(url.getHost());
//获取端口号 8080
System.out.println(url.getPort());
//获取文件路径 /exampLes/beauty.jpg
System.out.println(url.getPath());
//获取文件名 /exampLes/beauty.jpg?username=Tom
System.out.println(url.getFile());
//获取查询名 username=Tom
System.out.println(url.getQuery());
}
}
- 实现数据下载
//下载服务器中的文件
public class URLTest02 {
public static void main(String[] args){
HttpURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try{
URL url = new URL("https://img1.baidu.com/it/u=1875739781,4152007440&fm=253&fmt=auto&app=120&f=JPEG?w=1024&h=576");
urlConnection = (HttpURLConnection) url.openConnection();
//获取链接
urlConnection.connect();
is = urlConnection.getInputStream();
//保存本地
fos = new FileOutputStream("E:\\JavaSE\\test\\11\\URLTest.jpg");
byte[] buffer = new byte[1024];
int lenth;
while ((lenth = is.read(buffer))!=-1){
fos.write(buffer,0,lenth);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(urlConnection!=null){
urlConnection.disconnect(); //断开连接
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}