网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用。网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。中间最主要的就是数据包的组装,数据包的过滤,数据包的捕获,数据包的分析,当然最后再做一些处理,代码、开发工具、数据库、服务器架设和网页设计这5部分你都要接触。

原文链接:Java基础+进阶

概念

计算机网络:计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。

网络编程:指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。

网络编程三要素

  • IP地址:要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收类据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识
  • 端口:网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程了。也就是应用程序的标识
  • 协议:通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规贝被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵才能完成数据交换。常见的协议有UDP协议和TCP协议

IP地址

IP地址:是网络中设备的唯一标识
IP地址分为两大类

  • IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个P地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000101010000000000101000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号.分隔不同的字节。于是上面的IP地址可表示为“192.168.1.66”。IP地址的这种表示法叫做点分十进制表示法,这显然比1和0容易记忆得多
  • IPv6:IP地址的需求量愈来愈大,但网络地址资源有限,致使IP的分配越发紧张。为扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这就解决了网络地址资源数量不够的问题。

查看主机名和ip地址:控制面板–系统和安全-系统。

常用命令:

ipconfig(查看计算机IP地址)

ping IP地址(测试连通性)

特殊IP地址:192.0.0.1是回送地址,可以代表本机地址,一般用来测试使用。

InetAddress 类

这个类表示互联网协议(IP)地址。没有构造方法。

Return Method Description
static InetAddress getByName(String host) 确定主机名称的IP地址。主机名称可以是ip地址,也可以是机器名称
String getHostAddress() 返回文本显示中的IP地址字符串
String getHostName() 获取此IP地址的主机名
1
2
3
InetAddress administrator = InetAddress.getByName("diannaomingcheng");
System.out.println(administrator.getHostAddress());
System.out.println(administrator.getHostName());

端口

端口:设备上应用程序的唯一标识
端口号:用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败


UDP协议接收发送数据

概念

协议:计算机网络中,连接和通信的规则被称为网络通信协议
UDP协议:用户数据报协议(UserDatagram Protocol)

  • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
  • 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
  • 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。

UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象
因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
Java提供了DatagramSocket类作为基于UDP协议的Socket

发送数据

  1. **创建发送端的Socket对象(DatagramSocket)**:DatagramSocket()
  2. 创建数据,并把数据打包:DatagramPacket(bytel] buf, int length, inetAddress address, int port)
  3. 调用DatagramSocket对象的方法发送数据:void send(DatagramPacket p)
  4. 关闭发送端:void close()

接收数据

  1. **创建接收端的Socket对象(DatagramSocket)**:DatagramSocket(int port)
  2. 创建一个数据包,用于接收数据:DatagramPacket(bytel] buf, int length)
  3. 调用DatagramSocket对象的方法接收数据:void receive(DatagramPacket p)
  4. 解析数据包,并把数据在控制台显示:bytell getData(),int getLength()
  5. 关闭接收端:void close()

DatagramSocket构造方法

Method Descrption
DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口
DatagramSocket(int port) 构造数据报套接字并将其绑定到本地主机上的指定端口

一般方法

Return Method Description
void receive(DatagramPacket p) 从此套接字接收数据报包
void send(DatagramPacket p) 从此套接字发送数据报包
void close() 关闭此数据报套接字

DatagramPacket构造方法

Method Descrption
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号
DatagramPacket(byte[] buf, int length) 构造一个 DatagramPacket用于接收长度的数据包 length

一般方法

Return Method Description
byte[] getData() 返回数据缓冲区
int getLength() 返回要发送的数据的长度或接收到的数据的长度

发送接收数据代码

先运行接受,再运行发送

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
// 发送数据
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
public class SendDemo {
public static void main(String[] args) throws IOException {
InetAddress ia = InetAddress.getByName("zhujiming");
DatagramSocket ds = new DatagramSocket();
byte []b = "hello世界".getBytes();
DatagramPacket dp = new DatagramPacket(b, b.length, ia, 1234);
ds.send(dp);
ds.close();
}
}

// 接受数据
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(1234);
byte []b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b,1024);
ds.receive(dp);
System.out.println("接受到的数据:" + new String(dp.getData(), 0, dp.getLength()));
}
}

TCP协议

概念

  • 传输控制协议(Transmission Control Protocol)
  • TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手“
  • 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
    • 第1次握手,客户端向服务器端发出连接请求,等待服务器确认
    • 第2次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
    • 第3次握手,客户端再次向服务器端发送确认信息,确认连接
  • 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性TCP协议可以保证传输数据的安全,应用十分广泛。例如上传文件、下载文件、浏览网页等

TCP 和 UDP 的区别

TCP 和 UDP 是 OSI 模型中的运输层中的协议。
TCP 提供可靠的通信传输,而 UDP 则常被用于让广播和细节控制交给应用的通信传输。两者的区别如下:

  • TCP 面向连接,UDP 面向非连接即发送数据前不需要建立链接
  • TCP 提供可靠的服务(数据传输),UDP 无法保证;
  • TCP 面向字节流,UDP 面向报文;
  • TCP 数据传输,UDP 数据传输快;

TCP 为什么要三次握手

若采用两次握手,那么只要服务器发出确认数据包就会建立连接,但由于客户端此时并未响应服务器端的请求,那此时服务器端就会一直等待客户端,这样服务器端就白白浪费了资源。
若采用三次握手,服务器端没有收到来自客户端的再此确认,则就会知道客户端并没有要求建立请求,就不会浪费服务器的资源。

TCP 粘包是怎么产生的

TCP 粘包可能发生在发送端或者接收端,分别来看两端各种产生粘包的原因:
发送端粘包:发送端需要等缓冲区满才发送出去,造成粘包;
接收方粘包:接收方不及时接收缓冲区的包,造成多个包接收

发送数据

  1. **创建客户端的Socket对象(Socket)**:Socket(String host, int port)
  2. 获取输出流,写数据:OutputStreamgetOutputStream()
  3. 释放资源:void close()

套接字是两台机器之间通讯的端点。

客户端套接字类Socket方法

Method Description
Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
Socket(InetAddress address, int port) 创建流套接字并将其连接到指定IP地址的指定端口号

一般方法

Return Method Description
OutputStream getOutputStream() 返回此套接字的输出流
InputStream getInputStream() 返回此套接字的输入流
void shutdownOutput() 禁用此套接字的输出流

OutputStream类是所有输出字节流的超类

Return Method Description
void close() 关闭此输出流并释放与此流相关联的任何系统资源
void flush() 刷新此输出流并强制任何缓冲的输出字节被写出
void write(byte[] b) b.length字节从指定的字节数组写入此输出流
void write(byte[] b, int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流
abstract void write(int b) 将指定的字节写入此输出流

OutputStream封装到BufferedWriter

字节输出流OutputStream转字符输出流OutputStreamWriter转缓冲字符输出流BufferedWriter。

这里s是Socket对象:

1
2
//封装输出流对象
BufferedWriter bw = new BufferedWriter(new 0utputStreamWriter(s.getOutputStream()));

所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。

接收数据

  1. **创建服务器端的Socket对象(ServerSocket)**:ServerSocket(int port)
  2. 监听客户端连接,返回一个Socket对象:Socket accept()
  3. 获取输入流,读数据,并把数据显示在控制台:InputStream getInputStream()
  4. 释放资源:void close()

服务器套接字类ServerSocker方法

Method Description
ServerSocket(int port) 创建绑定到指定端口的服务器套接字
Socket accept() 侦听要连接到此套接字并接受它

InputStream是所有输入字节流类的超类

Return Method Description
abstract int read() 从输入流读取数据的下一个字节
int read(byte[] b) 从输入流读取一些字节数,并将它们存储到缓冲区 b,返回值为长度
int read(byte[] b, int off, int len) 从输入流读取最多 len字节的数据到一个字节数组

字节输入流InputStream封装到字符缓冲输入流BufferedWriter

这里s是Socket对象:

1
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));

这样就能每次读取一行字符串,更方便。

发送接收数据代码

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
// 客户端发送数据
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class SendDemo {
public static void main(String[] args) throws IOException {
InetAddress ia = InetAddress.getByName("mingzi");
Socket s = new Socket(ia,1234);
// 这里就是IO流了,可以把字节输出流OutputStream转为缓冲字符输出流BufferedWriter,从而发送字符
OutputStream os = s.getOutputStream();
os.write("hello世界".getBytes());
s.close();
}
}
// 服务端接受数据
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(1234);
Socket s = ss.accept();
InputStream is = s.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b);
System.out.println("data is:"+new String(b,0,len));
ss.close(); // 关ss就行了,因为s是从ss得到的。
}
}

练习,TCP键盘录入字符通讯

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
import java.io.*;
import java.net.Socket;
public class ClientDemo{
public static void main(String[] args) throws IOException {
Socket s = new Socket("xx.xx.xx.xx",1234);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = br.readLine())!=null){
if(line.equals("886")) break;
bw.write(line);
bw.newLine();
bw.flush(); // 缓冲流别忘了刷新,不然发不出去
}
s.shutdowmOutput();
// shutdowmOutput()代表停止发送数据,此处可加BufferedReader反馈代码,接收服务端传输成功的反馈
s.close(); // s是bw的源头,关s就关了bw
br.close();
}
}

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(1234);
Socket s = ss.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line;
while((line = br.readLine())!=null){
System.out.println(line);
}
ss.close();
}
}

练习,服务器多线程接收文件

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
public class ServerDemo {
public static void main(String[]args)throws IOException {
// 创建服务器Socket对象
ServerSocket ss =new ServerSocket( port: 10000);
while(true) {
// 监听客户端连接,返回一个对应的Socket对象
Socket s = ss.accept();
// 为每一个客户端开启一个线程
new Thread(new ServerThread(s)).start();
}
// ss.close();
}
}

public class ServerThread implements Runnable{
private Socket s;
public ServerThread(Socket s){
this.s=s;
}
@Override
public void run(){
try {
// 接收数据写到文本文件
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
// BufferedWriter bw = new BufferedWriter(new FileWriter("myNet|\copy.java"));
// 解决名称冲突问题
int count = 0;
File file r new File( pathname: "myNet\\copy["+count+"].java");
while(file.exists()){
count++;
file = new File( pathname: "myNet\\copy["+count+"].java");
}
BufferedWriter bw = new Bufferedwriter(new FileWriter(file));
String line;
while((line = br.readLine()) != nu11){
bw.write(line);
bw.newLine();
bw.flush();
}
// 给出反馈
BufferedWriter bwServer = new Bufferedwriter(new 0utputStreamriter(s.getOutputstream()));
bwServer.write( str:"文件上传成功");
bwServer.newLine();
bwServer.flush();
// 释放资源
s.close();
} catch (IOException e) {
// 处理异常
}
}
}

相关问题

http 响应码 301 和 302 代表分别什么及其区别

301:永久重定向。
302:暂时重定向。
区别在于301 对搜索引擎优化(SEO)更加有利,302 有被提示为网络拦截的风险

forward 和 redirect 的区别

forward 是转发 和 redirect 是重定向。

  • 地址栏 url 显示:foward url 不会发生改变,redirect url 会发生改变;
  • 数据共享:forward 可以共享 request 里的数据,redirect 不能共享;
  • 效率:forward 比 redirect 效率高。

OSI 的七层模型

物理层:利用传输介质为数据链路层提供物理连接,实现比特流的透明传输
数据链路层:负责建立和管理节点间的链路
网络层:通过路由选择算法,为报文或分组通过通信子网选择最适当的路径
传输层:向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输。
会话层:向两个实体的表示层提供建立和使用连接的方法
表示层:处理用户信息的表示问题,如编码、数据格式转换和加密解密等。
应用层:直接向用户提供服务,完成用户希望在网络上完成的各种工作。

get 和 post 请求的区别

get 请求会被浏览器主动缓存,而 post 不会。
get 传递参数有大小限制,而 post 没有。
post 参数传输更安全,get 的参数会明文限制在 url 上,post 不会。

如何实现跨域

原因:浏览器安全机制,请求访问的域名与ajax请求的域名不一致,导致无法返回结果。

实现跨域的方案:

  • 使用 jsonp 跨域:只支持 GET 请求,不支持 POST
  • 服务器端运行跨域:设置 CORS 等于 *
  • CORS(跨域资源分享)
    • 普通跨域请求:只需服务器端设置 Access-Control-Allow-Origin
    • 带 cookie 跨域请求:前后端都需要进行设置;
  • 在单个接口使用注解: @CrossOrigin
  • nginx 代理跨域:实质和 CORS 跨域原理一样,通过配置文件设置请求响应头 Access-Control-Allow-Origin 等字段
1
2
3
4
5
6
7
Vue配置
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).then((res) => {
console.log(res);
})

JSONP 实现原理

Jsonp:JSON with Padding,它是利用 script 标签的 src 连接可以访问不同源的特性,加载远程返回的“JS 函数”来执行的。