网络编程三要素

IP地址:设备在网络中的唯一标识

  1. IPv4,32位的IP地址,常用点分十进制方便书写
  2. IPv6,128位的IP地址,常用冒分十六进制方便书写
  3. 常用命令,在cmd中可以用ipcofig查看本机IP地址,用ping IP地址/域名可以检测当前与某个设备是否可以连通
  4. 特殊的IP地址:127.0.0.1称为回送地址又叫本地回环地址,可以代表本机IP地址,一般用作测试
  5. inetAddress
    • 不提供构造方法,可以通过提供的一些静态方法获取一个对象,这些静态方法需要传入主机名或IP地址
    • 常用方法有String getHostName()String getHostAddress()分别获取主机名和IP地址
  • 端口号:应用程序在设备中唯一标识
    1. 两个字节表示的整数,范围时0~65535
    2. 其中0~1023之间的端口号用于知名网络服务或应用,自用的需要在1024以上
    3. 注意一个端口号只能被一个应用程序使用
  • 协议:数据在传输过程中遵守的规则
    1. UDP,用户数据报协议,面向无连接的通信协议,速度快一次最多发送64K的数据,数据不安全易丢失
    2. TCP,传输控制协议,面向连接的通信协议,速度慢,没有大小限制,数据安全

UDP通信程序

发送数据步骤

  1. 创建发送端DatagramSocket对象
  2. 创建数据并打包DatagramPacket对象
  3. 调用DatagramSocket对象的方法发送数据
  4. 释放资源

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 无参构造表示不指定端口,而是使用随机端口
DatagramSocket ds = new DatagramSocket();

String data = "这是我发送的数据";
byte[] bytes = data.getBytes();

InetAddress address = InetAddress.getByName("127.0.0.1");

// 10000表示接收端的端口
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,10000);

ds.send(dp);

ds.close();

接受数据步骤

  1. 创建接收端DatagramSocket对象

  2. 创建DatagramPacket对象接收数据

  3. 调用DategramSocket的方法将数据装入DatagramPacket对象

  4. 解析数据

  5. 释放资源

Service

1
2
3
4
5
6
7
8
9
10
11
12
// 指定端口
DatagramSocket ds = new DatagramSocket(10000);

// 接收数据
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ds.receive(dp);

byte[] data = dp.getData();
System.out.println(new String(data,0,dp.getLength()));

ds.close();

三种通信方式

  1. 单播,一对一

  2. 组播,一对多

    • 组播地址是224.0.0.0~ 239.255.255.255,其中224.0.0.0~224.0.0.255是预留的不能使用,只能从224.0.1.0开始使用

    • 组播发送端与单薄类似,但是发送的地址应该是组播地址

    • 组播接收端使用的是MulticastSocket对象而不是DatagramSocket

    • 接收端需要将本机添加入组播中才能收到数据

  3. 广播,一对全部

    • 广播地址是255.255.255.255
    • 广播的发送端与单播一样,只是将地址换成广播地址即可
    • 广播的接收端和单播一样

TCP通信程序

TCP通信协议是可靠的网络协议,,它在通信的两端各建立一个Socket对象,通信之前要保证连接已经建立,通过Socket产生IO流来进行网络通信

客户端发送数据步骤

  1. 创建客户端Socket对象,与指定的服务端连接,public Socket(String host,int port)
  2. 利用Socket对象获取输出流,OutputStream getOutputStream(),利用获取到的输出流对象的write()方法写数据
  3. 释放资源,void close()

Client

1
2
3
4
5
6
7
8
9
10
11
12
// 创建Socket对象
Socket socket = new Socket("127.0.0.1",10001);

// 获取输出流
OutputStream outputStream = socket.getOutputStream();

// 写数据
outputStream.write("helloworld".getBytes());

// 关闭资源
outputStream.close();
socket.close();

服务端接收数据步骤

  1. 创建ServerSocket对象,public ServerSocket(int port)
  2. 监听客户端连接,获取Socket对象,Socket accept()
  3. 获取输入流,读数据,InputStream getyInputStream()
  4. 释放资源,void close()

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建ServerSocket对象
ServerSocket serverSocket = new ServerSocket(10001);

// 等待客户端连接
Socket accept = serverSocket.accept();

// 获取输入流对象
InputStream inputStream = accept.getInputStream();

// 开始读数据,当输入流没有数据时,read方法会阻塞,-1是当客户端关闭资源时发送过来的结束标记
int b;
while((b = inputStream.read()) != -1){
System.out.print((char)b);
}

// 关闭资源
inputStream.close();
serverSocket.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
import java.io.*;
import java.net.Socket;

public class Client {
public static void main(String[] args) throws IOException {

Socket socket = new Socket("127.0.0.1",10001);

// 本机的文件输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("image.jpg"));

// 网络输出流
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);

int b;
while((b = bis.read()) != -1){
bos.write(b);
}
// 发送结束标记
socket.shutdownOutput();
// 接收服务端消息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
// 关闭资源
bis.close();
br.close();
socket.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
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
public static void main(String[] args) throws IOException {

ServerSocket ss = new ServerSocket(10001);
Socket accept = ss.accept();

// 网络输入流
InputStream is = accept.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);

// 本机输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.jpg"));

int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}

// 给客户端发送消息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));

bw.write("文件上传成功");
bw.newLine();
bw.flush();

bos.close();
accept.close();

ss.close();
}
}

使用while循环,UUID,多线程和线程池来优化服务端

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
public class Server {
public static void main(String[] args) throws IOException {

ServerSocket ss = new ServerSocket(10001);
ThreadPoolExecutor tpe = new ThreadPoolExecutor(
3,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
while (true) {
Socket accept = ss.accept();
ThreadSocket ts = new ThreadSocket(accept);
tpe.submit(ts);
}
}
}

class ThreadSocket implements Runnable{

private Socket accept;
public ThreadSocket(Socket accept) {
this.accept = accept;
}

@Override
public void run() {
BufferedOutputStream bos = null;
try {
InputStream is = accept.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
//UUID确保每次文件名是随机的
bos = new BufferedOutputStream(new FileOutputStream(UUID.randomUUID().toString() + ".jpg"));

int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("文件上传成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(accept != null){
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}