Java基础知识-网络编程

计算机网络知识及网络通信实现

1. 软件结构

1.1. C/S结构

全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。

img

1.2. B/S结构

全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。

img

两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。

2. 网络三要素

协议,IP,端口

2.1. 网络协议 - 交易密码

  • 网络通信协议:

    通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。

  • TCP/IP协议:

    传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。

上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。
链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
运输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。

OSI TCP/IP
应用层 应用层 Telnet/FTP/SMTP/DNS/HTTP
表示层
会话层
传输层 传输层 TCP/UDP
网络层 网络层 IP/ARP/RARP/ICMP
数据链路层 物理+数据链路层
物理层

2.2. IP - 地址

IP:唯一标识网络中的一个通信实体。 –> 住址

指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”

2.2.1. IP地址分类

  • IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,例如192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。

  • IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。

    为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

2.2.2. 常用命令

查看本机IP地址,在控制台输入:

1
ipconfig

检查网络是否连通,在控制台输入:

1
2
ping 空格 IP地址
ping 220.181.57.216

2.2.3. 特殊的IP地址

本机IP地址:127.0.0.1localhost

2.3. 端口号 - 房号

端口号:应用程序与外界交流的出入口。 –> 房间号

网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?

如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。

用两个字节表示的整数,它的取值范围是0~65535。

0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。

0-1023 公认端口 80-www,21-FTP

1024-49151 注册端口,分配给用户进程或应用程序

49152-65535 动态/私有端口

利用协议+IP地址+端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。

3. InetAddress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
try {

System.out.println("通过getLocalHost()创建InetAddress对象");
//本地
InetAddress address = InetAddress.getLocalHost();
//计算机名 LearningtekiMacBook-Air.local
System.out.println(address.getHostName());
//本机ip 192.168.0.190
System.out.println(address.getHostAddress());

System.out.println("通过域名得到InetAddress对象");
address = InetAddress.getByName("www.baidu.com");
//www.baidu.com
System.out.println(address.getHostName());
//180.101.49.12
System.out.println(address.getHostAddress());

address = InetAddress.getByName("123.56.138.186");
System.out.println("IP不存在/DNS不可解析时返回IP");
//123.56.138.186
System.out.println(address.getHostName());

} catch (UnknownHostException e) {
e.printStackTrace();
}

4. URL

%…. 当URL中包含非西欧字符的字符串时,字符串转为application/x-www-form-urlencoded MIME

1
2
String keyword = URLDecoder.decode("%....","utf-8");
String urlStr = URLEncoder.encode("讲义","utf-8");

URL 统一资源定位器(Uniform Resource Locator) 指向网络中的资源

protocol://host:port/resourceName

URI 统一资源标识符(Uniform Resource Identifiers),URL是URI的特例

URN 统一资源名称(Uniform Resource Name),通过特定命名空间中的卫衣名称或ID来标识资源,URN也是URI的特例

读取URL资源的InputSteam openStream()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
try {

URL url = new URL("https://www.baidu.com:80/index.html?uname=bjsxt#a");
//协议 https
System.out.println("协议 "+url.getProtocol());
//域名 www.baidu.com
System.out.println("域名 "+url.getHost());
//端口 80
System.out.println("端口 "+url.getPort());
//资源 /index.html?uname=bjsxt
System.out.println("资源 "+url.getFile());
//路径 /index.html
System.out.println("路径 "+url.getPath());
//锚点 a
System.out.println("锚点 "+url.getRef());
//参数 uname=bjsxt
System.out.println("参数 "+url.getQuery());

//获取资源内容
InputStream in=url.openStream();
Scanner sc = new Scanner(in,"UTF-8");

} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
URL url=new URL("https://www.dianping.com");

HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
//模拟浏览器
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36");

BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream(),"UTF-8"));

String str=null;
while((str=reader.readLine())!=null) {
System.out.println(str);
}

//获取响应头信息
Map<String,List<String>> fields = conn.getHeaderFields();

//文件大小
int fileSize = conn.getContentLength();
//读取数据
InputStream in = conn.getInputStream();

5. TCP 传输控制协议

传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。

在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。

  • 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
    • 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
    • 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
    • 第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。

完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。

客户端和服务器之间的连接包含一个IO对象,它们之间的传输是字节流

面向连接

5.1. ServerSocket服务端

ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。

5.1.1. 构造方法

public ServerSocket(int port)

使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号

1
ServerSocket server = new ServerSocket(6666);

5.1.2. 成员方法

public Socket accept()

侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接

1
2
3
4
5
6
7
8
9
10

ServerSocket server = new ServerSocket(8080);

while(true){
Socket client = server.accpet();
PrintStream ps = new PrintStream(client.getOutputStream());
ps.println("Hello");
ps.close();
client.close();
}

5.2. Socket客户端

Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点

5.2.1. 构造方法

public Socket(String host, int port)

创建套接字对象并将其连接到指定主机上的指定端口号。

该方法会一直无限期阻塞,直到建立了到达主机的初始连接为止。

如果指定的host是null ,则相当于指定地址为回送地址。

回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。

构造举例,代码如下:

1
Socket client = new Socket("127.0.0.1", 6666);

###成员方法

public InputStream getInputStream() : 返回此套接字的输入流

如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道

关闭生成的InputStream也将关闭相关的Socket

public OutputStream getOutputStream() : 返回此套接字的输出流

如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道

关闭生成的OutputStream也将关闭相关的Socket

public void close() :关闭此套接字

一旦一个socket被关闭,它不可再使用

关闭此socket也将关闭相关的InputStream和OutputStream

public void shutdownOutput() : 禁用此套接字的输出流

任何先前写出的数据将被发送,随后终止输出流

应用

1
2
3
4
5
6
7
import java.net.Socket;
//指定主机和端口号
Socket client = new Socket("localhost",8080);

BufferReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));

String str = reader.readLine(); //Hello

可以建立超时

1
client.setSoTimeout(1000); //1s

or

1
2
Socket client = new Socket();
client.connect(new InetAddress(host,port),1000);

5.3. SocketChannel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SocketChannel channel = SocketChannel.open(
new InetSocketAddress("localhost", 8888));

//通道通过缓冲区buffer来实现读写
ByteBuffer bf = ByteBuffer.allocate(1024);
int write = channel.write(bf);
int read = channel.read(bf);

//将通道转为流
InputStream in = Channels.newInputStream(channel);
OutputStream out = Channels.newOutputStream(channel);

//关闭输入
SocketChannel input = channel.shutdownInput();
//关闭输出
SocketChannel output = channel.shutdownOutput();

//public Scanner(ReadableByteChannel source, String charsetName) {}
Scanner sc = new Scanner(channel,"UTF-8");
String str = sc.next();

6. UDP 用户数据报协议

用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。

UDP通信图解

特点:数据被限制在64kb以内,超出这个范围就不能发送了。

数据报(Datagram):网络传输的基本单位

面向非连接

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
//创建服务端
DatagramSocket server = new DatagramSocket(6666);
//准备接收容器
byte[] container = new byte[1024];
//包 接收数据
DatagramPacket packet = new DatagramPacket(container,container.length);
server.receive(packet);
//分析数据
byte[] data = packet.getData();
int len = packet.getLength();
String str = new String(data,0,len);
//释放资源
server.close();

服务端

1
2
3
4
5
6
7
8
9
10
11
12
//创建客户端
DatagramSocket client = new DatagramSocket(8888);
//准备数据
String str="Hello World";
byte[] container = str.getBytes();
//打包 指定端口
DatagramPacket packet = new DatagramPacket(container,container.length,new InetAddress("localhost",6666));
//发送数据
client.send(packet);

//释放资源
client.close();

7. 手动编写webserver

Http在应用层,底层传输层为TCP协议

7.1. 请求格式Request

1
2
3
4
5
6
7
8
9
10
11
12
方法_/统一资源标识符_/协议/版本\r\n
请求标头\r\n
\r\n
实体主体(post请求才有)

POST /examples/default.jsp HTTP/1.1
Accept: text/plain; test/html
Accept-Language: en-gb
Connection: Keep-alive
Host: localhost

user=wt&&pwd=123

7.2. 响应格式Response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
协议 状态码 描述\r\n
响应标头\r\n
\r\n
实体主体

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html
Content-Length:112

<html>
<head></head>
<body></body>
</html>

7.3. web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>WebServer.LoginServlet</servlet-class>>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
<url-pattern>/g</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>reg</servlet-name>
<servlet-class>WebServer.RegisterServlet</servlet-class>>
</servlet>
<servlet-mapping>
<servlet-name>reg</servlet-name>
<url-pattern>/reg</url-pattern>
</servlet-mapping>
</web-app>

7.4. 解析XML

Entity.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package WebServer;

public class Entity{
private String servletName;
private String servletClass;

public void setServletName(String servletName){
this.servletName = servletName;
}
public String getServletName(){
return servletName;
}
public void setServletClass(String servletClass){
this.servletClass = servletClass;
}
public String getServletClass(){
return servletClass;
}
}

Mapping.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package WebServer;

public class Mapping{
private String servletName;
private Set<String> urlPattern;

public Mapping(){
urlPattern = new HashSet<>();
}

public void setServletName(String servletName){
this.servletName = servletName;
}
public String getServletName(){
return servletName;
}
public void setUrlPattern(Set<String> urlPattern){
this.urlPattern = urlPattern;
}
public Set<String> getUrlPattern(){
return urlPattern;
}
public void addPattern(String pattern){
this.urlPattern.add(pattern);
}
}

webHandler.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package WebServer;

import java.util.ArrayList;
import java.util.List;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class webHandler extends DefaultHandler{

List<Entity> entitys;
List<Mapping> mappings;
Entity entity;
Mapping mapping;
String tag;
boolean isMapping = false;
@Override
public void startDocument() throws SAXException {
entitys = new ArrayList<>();
mappings = new ArrayList<>();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName!=null)
{
tag=qName;
}
if(qName.equals("servlet"))
{
isMapping=false;
entity = new Entity();
}
if(qName.equals("servlet-mapping"))
{
isMapping = true;
mapping = new Mapping();
}
}

@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String contents = new String(ch,start,length).trim();
if(!isMapping)
{
if(tag!=null&&tag.equals("servlet-name")) {
entity.setServletName(contents);
}
if(tag!=null&&tag.equals("servlet-class")) {
entity.setServletClass(contents);
}
}else {
if(tag!=null&&tag.equals("servlet-name")) {
mapping.setSerlvetName(contents);
}
if(tag!=null&&tag.equals("url-pattern")) {
mapping.addPattern(contents);
}
}

}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if(qName.equals("servlet"))
{
this.entitys.add(entity);
}
if(qName.equals("servlet-mapping"))
{
this.mappings.add(mapping);
}
tag=null;
}

public List<Entity> getEntitys() {
return entitys;
}
public void setEntitys(List<Entity> entitys) {
this.entitys = entitys;
}
public List<Mapping> getMappings() {
return mappings;
}
public void setMappings(List<Mapping> mappings) {
this.mappings = mappings;
}
}

7.5. 匹配url-class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package WebServer;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

public class WebApp {

WebContext context = null;
public WebApp() {
//获取解析工厂
SAXParserFactory factory = SAXParserFactory.newInstance();

try {
//工厂解析器
SAXParser parse = factory.newSAXParser();
//注册处理器
webHandler handler=new webHandler();
//加载文档Document
parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("WebServer/web.xml"), handler);
//处理数据
context = new WebContext(handler.getEntitys(),handler.getMappings());

} catch (Exception e) {
e.printStackTrace();
}
}
public Servlet getServlet(String url)
{
String className = context.getClz("/"+url);
Servlet server = null;
try {
server = (Servlet) Class.forName(className).getConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return server;
}
}
本文结束  感谢您的阅读