网络编程
什么是网络编程?
编写在不同计算机上进行数据传输的程序
网络编程应用场景
网络应用程序、即时通信、网游对战、金融证券、国际贸易、邮件等。
常见软件架构:
- **Client-Server(CS):**客户端-服务端模式
- **Browser/Server(BS):**浏览器-服务器模式
**网络编程三要素:**IP地址、端口号、协议
# IP地址
概念:互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给网络中的计算机进行编号 **作用:**通过IP地址可以找到网络中的某台电脑;IP地址就相当于生活中的家庭住址
分类
IPv4:是一个32位的二进制数,通常被分为4个字节,表示成 a.b.c.d 的形式,例如 192.168.65.100。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPv4地址在2011年2月分配完毕。为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,分成8组以十六进制数显示,表示成 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。
查看IP地址相关指令
- **查看本机IP地址:**在DOS命令行输入ipconfig
- **检查网络是否连通:**在DOS命令行输入ping IP地址/域名
特殊IP地址127.0.0.1:是回送地址也称本地回环地址,可以代表本机的IP地址,一般用来测试使用
Java操作IP地址的相关API(InetAddress类)
| 方法 | 说明 |
|---|---|
| static InetAddress getLocalhost() | 获取本机的IP地址对象 |
| public static InetAddress getByName(String host) | 设置主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 |
| public String getHostName() | 获取此IP地址的主机名 |
| public String getHostAddress() | 返回文本显示中的IP地址字符串 |
# 端口
概述
端口是应用程序在设备中唯一的标识。我们一台电脑上会安装很多的应用程序,内网通,微信,QQ。我们可以通过端口找到计算机中的某个程序。
**作用:**通过端口号可以找到电脑上的某个程序
端口号的取值范围
端口号一般是用两个字节表示的整数,它的取值范围是0~65535。其中0~1023之间的端口号用于一些知名的网络服务或者应用。我们自己使用1024以上的端口号就可以了。
# 协议
**定义:**计算机网络中,连接和通信的规则被称为网络通信协议。
网络通信协议的两套参考模型
- OSI参考模型:世界互联协议标准,全球通信规范,单模型过于理想化,未能在因特网上进行广泛推广。
- TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。

# UDP通信
UDP协议特点: 用户数据报协议(User Datagram Protocol)
- 不需要连接
- 速度快
- 有大小限制一次最多发送64K
- 易丢失数据
UDP协议通信场景(速度要求高,数据完整性要求不高)
- 直播
- 语音通话
- 视频会话
UDP通信流程
DatagramPacket常用方法
| 方法 | 说明 |
|---|---|
| int getLength ( ) | 返回要发送的数据的长度或接收到的数据的长度。 |
| InetAddress getAddress ( ) | 返回该数据报发送或接收数据报的计算机的InetAddress对象。 |
| int getPort ( ) | 返回发送数据报的远程主机上的端口号,或从中接收数据报的端口号。 |
**注意:**发送端无需声明IP端口信息,只有接收端需要,就像发快递一样
示例
public class UDPSender {
public static void main(String[] args) throws IOException {
System.out.println("发送端开启");
// 创建发送端
DatagramSocket socket = new DatagramSocket();
// 发送的数据
byte[] massage = "约吗?".getBytes();
// 创建数据包
DatagramPacket data = new DatagramPacket(
massage, 0, massage.length, InetAddress.getByName("192.168.73.75"), 6666);
// 发送数据
socket.send(data);
socket.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UDPReceiver {
public static void main(String[] args) throws IOException {
System.out.println("接收端开启");
// 创建接收端
DatagramSocket socket = new DatagramSocket(6666);
// 保存接收的数据
byte[] massage = new byte[1024 * 8];
// 创建数据包接收数据,将接收的数据放入数组中
DatagramPacket data = new DatagramPacket(massage, 0, massage.length);
// 接收数据
socket.receive(data);
// 获取数据包的IP地址
String ip = data.getAddress().getHostAddress();
// 获取数据包的端口号
int port = data.getPort();
// 接收了多少打印多少
System.out.println(ip + ":" + port + '\t' +
new String(massage, 0, data.getLength()));
socket.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# TCP通信
TCP协议特点: 传输控制协议 (Transmission Control Protocol)
- 需要连接
- 速度慢
- 没有大小限制
- 不易丢失数据
TCP协议通信场景(速度要求不高,数据完整性要求高)
- 下载
- 扫码支付
- 金融等数据通信。
TCP通信流程
Socket常用构造
| 方法 | 说明 |
|---|---|
| Socket (String host, int port) | 创建流套接字并将其连接到指定主机上的指定端口号。 |
| Socket (InetAddress address, int port) | 创建流套接字并将其连接到指定IP地址的指定端口号。 |
Socket常用方法
| 方法 | 说明 |
|---|---|
| OutputStream getOutputStream( ) | 返回此套接字的输出流。 |
| InputStream getInputStream( ) | 返回此套接字的输入流。 |
| InetAddress getInetAddress( ) | 返回套接字所连接的InetAddress对象。 |
| SocketAddress getRemoteSocketAddress( ) | 返回此套接字连接到的端点的地址,如果未连接,则 null |
| void shutdownInput( ) | 将此套接字的输入流放置在“流的末尾”。 |
| void shutdownOutput( ) | 断开此套接字的输出流。 |
| void close( ) | 关闭此套接字 |
**注意:**Socket的InputStream的read()方法,如果read读不到数据,不会返回-1,而是一直等待,直到客户端调用shutdownOutput()断开输出流连接,相当于传输了一个-1
ServerSocket常用构造
| 方法 | 说明 |
|---|---|
| ServerSocket(int port) | 创建绑定到指定端口的服务器套接字。 |
ServerSocket常用方法
| 方法 | 说明 |
|---|---|
| Socket accept( ) | 侦听要连接到此套接字并接受它。 |
| void close( ) | 关闭此套接字 |
示例
public class TCPreceiver {
public static void main(String[] args) throws IOException {
// 创建服务端
ServerSocket serverSocket = new ServerSocket(10086);
System.out.println("服务端开启");
// 与客户端建立连接,获取客户端
Socket socket = serverSocket.accept();
// 获取客户端输入流
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024 * 8];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
// 获取客户端输出流
OutputStream os = socket.getOutputStream();
os.write("服务端收到".getBytes());
// 关闭流
os.close();
is.close();
socket.close();
serverSocket.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TCPsender {
public static void main(String[] args) throws IOException {
// 建立客户端
Socket socket = new Socket("127.0.0.1", 10086);
System.out.println("客户端开启");
// 获取输出流
OutputStream os = socket.getOutputStream();
os.write("呼叫服务端".getBytes());
// 获取输入流
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024 * 8];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
is.close();
os.close();
socket.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
多人同时文件上传案例
客户端
public class TCPsender {
public static void main(String[] args) throws IOException {
System.out.println("客户端开启");
// 建立客户端,指定连接目的地
Socket socket = new Socket("127.0.0.1", 10086);
// 获取输出流
OutputStream os = socket.getOutputStream();
// 建立文件字节输入流读取文件
FileInputStream fis = new FileInputStream("D:\\IDEAproject\\MyFileTest\\img.jpg");
byte[] bytes = new byte[1024 * 8];
int len;
while ((len = fis.read(bytes)) != -1) {
os.write(bytes, 0, len);
}
// 断开输出流与服务端的连接
socket.shutdownOutput();
// 获取输入流
InputStream is = socket.getInputStream();
len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
is.close();
os.close();
socket.close();
}
}
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
服务端
public class TCPreceiver {
public static void main(String[] args) throws IOException {
// 创建服务端
ServerSocket serverSocket = new ServerSocket(10086);
System.out.println("服务端开启");
while (true) {
// 与客户端建立连接,获取客户端
Socket socket = serverSocket.accept();
// 创建连接池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5,
10,
100,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory()
);
// 获取连接的客户端信息
System.out.println(socket.getRemoteSocketAddress()+"已连接...");
// 服务端连接到客户端后就将业务与客户端提交到线程池
UpLoadRunnable ulr = new UpLoadRunnable(socket);
pool.submit(ulr);
}
}
}
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
多线程实现
public class UpLoadRunnable implements Runnable{
Socket socket = new Socket();
public UpLoadRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 获取客户端输入流
InputStream is = socket.getInputStream();
// 文件重名措施
String uuid = UUID.randomUUID().toString();
// 创建文件字节输出流
FileOutputStream fos =
new FileOutputStream("Ex_Test\\TestFile\\upload\\" + uuid + ".jpg");
byte[] bytes = new byte[1024 * 8];
int len;
while ((len = is.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
System.out.println(uuid + "文件上传成功");
// 获取客户端输出流
OutputStream os = socket.getOutputStream();
os.write("文件上传完成".getBytes());
// 关闭流
os.close();
fos.close();
is.close();
socket.close();
} catch (IOException e) {
System.out.println("文件上传失败: "+e.toString());
}
}
}
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
使用TCP模拟B/S服务器
public class WebServer {
public static void main(String[] args) throws IOException {
// 创建服务端
ServerSocket serverSocket = new ServerSocket(9799);
while (true) {
// 服务端同意请求
Socket socket = serverSocket.accept();
// 获取输出流
OutputStream out = socket.getOutputStream();
// http协议固定需要回复浏览器的东西
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content-Type:text/html;charset=utf-8\r\n".getBytes());
out.write("\r\n".getBytes());
// 返回数据到浏览器
out.write("<h1 align='center'>欢迎使用服务器<h1>".getBytes());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# NIO
概述
JDK1.4以前: InputStream/OutputStream称为BIO(Blocking IO) 阻塞式IO JDK1.4推出了一套新的IO体系称为NIO (New IO/ Not Blocking IO) 非阻塞式IO
阻塞、非阻塞概念
阻塞和非阻塞都是处理IO数据的方式
- 阻塞:如果没有数据就一直等待
- 非阻塞:如果没有数据,不会一直等待,可以做其他事情
非阻塞的好处:不需要一直等待,当有数据来才需要处理,没有数据可以做其他操作
NIO中的三个角色
Channel(通道):可以双向传输数据
ByteBuffer: 相当于之前BIO的byte[],可以保存要发送和接收的数据,由于byte[]是基于java的创建,内存空间是在java的堆内存中,而ByteBuffer是基于操作系统的,所以效率比byte[]要高,并且ByteBuffer已经被封装成了java中的一个类,新增了很多方法,功能更强大
Selector 选择器(相等于门卫大爷):可以管理多个连接
NIO使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间,在高并发、高频段业务环境下有非常重要的优势
优点: 可以提高IO效率
缺点: 代码较为繁琐