基本的通信架构

  • 基本的通信架构包括两种 : CS架构(Client客户端/Server服务端), , C需要程序员开发
  • BS架构(Browser浏览器/Server架构) : B不需要程序员开发

网络通信三要素 : IP, 端口, 协议

IP

  • 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
26
package demoInetAddress;  

package demoInetAddress;

import java.net.InetAddress;

public class Net {
public static void main(String[] args) {
try {
// 1. 获取本机IP对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());

// 获取对象IP
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());

// 判断是否是通的
System.out.println(ip2.isReachable(5000));
} catch (Exception e) {
e.printStackTrace();
}
}
}

端口

用来标记计算机设备上正在运行的设备
端口分类

  • 周知端口:0 - 1023 被预先定义的端口: (HTTP是80, FTP是21)
  • 注册端口: 1024 - 49151
  • 动态端口 49152 - 65535

一般开发使用注册端口,且一个设备不能出现两个程序端口号一样

协议

事先的一些规则


C / S 架构(重实现)

UDP通信实现

  • 特点: 无连接,不可靠通信
  • 不事先建立链接: 发送端每次把要发送的数据限制在64K内
  • Java提供类包: java.net.DatagramSocket 实现UDP通信

使用方法 :

  • 创建
    • 客户端socket = new socket
    • 接收端socket = new socket(int port)
      • 接受和发送
      • socket.receive()
      • socket.send
      • 数据包的新建
      • DatagramPacket(bytes[], length, 目的地址IP, 目的地址端口) // 发送端
      • DatagramPacket(bytes[], length)

1. 实现1: 一发一收:

UDPclient:

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
package UDP;  

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPclient {
public static void main(String[] args) throws Exception {
// 创建发送端
DatagramSocket socket = new DatagramSocket();

// 创建数据包要发送的对象
byte[] bytes = "我是客户端,约你今晚打codeforce".getBytes();
/**
* public DatagramPacket(byte[] buf, int len, InetAdress address, int port) * 发送的数据,字节数组
* 发送的字节长度
* 目的地址的IP
* 目的地端口
*/
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8080);
socket.send(packet);

socket.close();
}
}

UDPserver

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
package UDP;  

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPserver {
public static void main(String[] args) throws Exception {
// 1. 创建接收端对象,组测端口
DatagramSocket socket = new DatagramSocket(8080); // 必须要表明在哪个端口接收

// 创建一个数据包对象负责接收数据
byte[] buf = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);

// 接受数据
socket.receive(packet);

// 看到数据是否是收到了
int len = packet.getLength();
String data = new String(buf, 0, len);
System.out.println(data);

// 获取对方的IP和端口
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();

System.out.println("对方IP : " + ip + " port " + port);
}
}

注意先启动服务端监听在使用客户端发送信息,即可实现通信

多发多收

UDPserver:

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 UDP;  

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPserver {
public static void main(String[] args) throws Exception {
// 1. 创建接收端对象,组测端口
DatagramSocket socket = new DatagramSocket(8080);
byte[] buf = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);

while (true) {
socket.receive(packet);

int len = packet.getLength();
String msg = new String(buf, 0, len);
System.out.println(msg);

String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();

System.out.println("对方IP : " + ip + " port " + port);
}
}
}

UDPclient:

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
package UDP;  

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class UDPclient {
public static void main(String[] args) throws Exception {
// 创建发送端
DatagramSocket socket = new DatagramSocket();
var in = new Scanner(System.in);

while (true) {
String msg = in.nextLine();
byte[] bytes = msg.getBytes();

if (msg == "exit") {
socket.close();
break;
}

System.out.println("发送成功");
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8080);
socket.send(packet);
}
}
}


TCP 通信实现

发送端:
使用Socket创建和发送信息,将管道包装成OutputStream方便传输各种数据

1
Socket(int IP, int port) // 发送地址,当前端口

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

import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
public static void main(String[] args) throws Exception {
// 1. 常见的Socket管道对象,请求与服务端Socket链接,可靠链接
Socket socket = new Socket("127.0.0.1", 9999);

// 2. 从socket通信管道得到一个字节输出流
OutputStream os = socket.getOutputStream();

// 3. 特殊数据流
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(1);
dos.writeUTF("今日一别,不知何时与君再相见");

socket.close();
}
}

服务端:
ServerSocket :

1
public ServerSocket(int port)

方法:

1
publicSocket accept() // 阻塞等待客户端链接,一旦有客户端链接放回Socket对象代表服务端管道

实现: 一发一收

Server:

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
package TCP;  

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
public static void main(String[] args) {
try {
// 创建ServerSocket对象,绑定端口号,监听客户端链接
ServerSocket ss = new ServerSocket(9999);

// 调用accept方法,阻塞等待客户端链接,一旦有客户端链接会返回一个Socket类型对象
Socket socket = ss.accept();

//获取输入流,读取客户端发送数据
InputStream is = socket.getInputStream();

// 把字节输入流包装成特殊数据流
DataInputStream dis = new DataInputStream(is);
int id = dis.readInt();
String msg = dis.readUTF();
System.out.println("id = " + id + " 收到一条消息: " + msg);

// 拿到IP和端口
System.out.println("IP" + socket.getInetAddress().getHostAddress());
System.out.println("端口:" + socket.getPort());
} catch (Exception e) {
e.printStackTrace();
}
}
}

Client:

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

import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
public static void main(String[] args) throws Exception {
// 1. 常见的Socket管道对象,请求与服务端Socket链接,可靠链接
Socket socket = new Socket("127.0.0.1", 9999);

// 2. 从socket通信管道得到一个字节输出流
OutputStream os = socket.getOutputStream();

// 3. 特殊数据流
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(1);
dos.writeUTF("今日一别,不知何时与君再相见");

socket.close();
}
}

多发多收

Client:

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
package TCP;  

import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
public static void main(String[] args) throws Exception {
// 1. 常见的Socket管道对象,请求与服务端Socket链接,可靠链接
Socket socket = new Socket("127.0.0.1", 9999);

// 2. 从socket通信管道得到一个字节输出流
OutputStream os = socket.getOutputStream();

// 3. 特殊数据流
DataOutputStream dos = new DataOutputStream(os);
var in = new Scanner(System.in);

while(true) {
String s;
s = in.nextLine();
if (s == "exit") {
break;
}
dos.writeUTF(s);
dos.flush();
}
socket.close();
dos.close();
}
}

Server
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
package TCP;  

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
public static void main(String[] args) {
try {
// 创建ServerSocket对象,绑定端口号,监听客户端链接
ServerSocket ss = new ServerSocket(9999);

// 调用accept方法,阻塞等待客户端链接,一旦有客户端链接会返回一个Socket类型对象
Socket socket = ss.accept();

//获取输入流,读取客户端发送数据
InputStream is = socket.getInputStream();

// 把字节输入流包装成特殊数据流
DataInputStream dis = new DataInputStream(is);

while (true) {
String msg = dis.readUTF();
System.out.println(" 收到一条消息: " + msg);

// 拿到IP和端口
System.out.println("IP" + socket.getInetAddress().getHostAddress());
System.out.println("端口:" + socket.getPort());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

注意到以上只能有一个接收端,因为只接受了一个socket,想实现多个接收,可以使用多线程:使用主线程接受客户链接,对于每个客户新开一个线程。
可以接受多个Client的Server
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
package TCP;  

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
class K extends Thread {
private Socket socket;
public K(Socket socket) {
this.socket = socket;
}
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);

while (true) {
String msg = dis.readUTF();
System.out.println(" 收到一条消息: " + msg);

// 拿到IP和端口
System.out.println("IP" + socket.getInetAddress().getHostAddress());
System.out.println("端口:" + socket.getPort());
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("下线了"); // 或者写在Final
}
}
}
public class Server {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(9999);
// 调用accept方法,阻塞等待客户端链接,一旦有客户端链接会返回一个Socket类型对象
while (true) {
Socket socket = ss.accept();
System.out.println(socket.getInetAddress().getHostAddress() + "上线了");
new K(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}


B / S架构

waiting for learning