JavaSE-网络通信

JavaSE-网络通信

一、概述

基本的通信架构有2种形式:

  • CS架构( Client客户端/Server服务端)
  • BS架构(Browser浏览器/Server服务端) IPV4共32bit,4字节,形如192.168.000.001
    IPV6共128bit,16字节,形如2001:0db8:0000:0023:0008:0800:200c:417a
    IPV4形为4位十进制数,每个数字基于8bit,范围0~255
    IPV6形为8位十六进制数,每个数基于16bit,范围0000~FFFF(0 ~ 65535)
    使用Java自带的java.net.InetAddress 可以互动IP 前两个为构造器,后三个为方法

二、端口和协议

UDP

不事先建立连接,发送方直接发送,不管对面状态,是不可靠连接。但是通信效率高,适合诸如视频直播、语音通话等。单个数据包最大限制为64KB。

TCP

事先通过三次握手建立连接,可靠连接,四次挥手断开连接,在不可靠信道做到可靠连接

三次握手是要Client和Server知道对方可以’收’和’发’
第一次握手:S知道C可以发
第二次握手:C知道S可以收发
第三次握手:S知道C可以收发
之后传输数据时,C也不断向S发送确认消息,S需要回复确认,如果没有,那么C会采取重发等措施。

三、UDP编程

S和C都需要先创建数据包类,用来接收信息。
S代码如下

language-cpp
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
public class Server {

public static void main(String[] args) throws Exception {

//1. create server socket
DatagramSocket socket = new DatagramSocket(8889);
//2. create packet
byte[] bytes = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {

//3.receive
socket.receive(packet);

System.out.println("服务端收到了长度为 "+packet.getLength()+" bytes的数据");
System.out.println("数据为: "+new String(bytes,0, packet.getLength()));
System.out.println("消息来自:\nIP->"+packet.getAddress().getHostAddress());
System.out.println("Name->"+packet.getAddress().getHostName());
System.out.println("Port->"+packet.getPort());
System.out.println("-----------------------------");
}

// socket.close();
}
}

S会在socket.receive一直等待C的消息,同时通过packet也可以查看发送方的一些信息。
C代码如下

language-cpp
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
public class Client {

public static void main(String[] args) throws Exception {

//1. create client
DatagramSocket socket = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true){

System.out.println("请说:");
String s = sc.nextLine();
if ("exit".equals(s)){

System.out.println("欢迎下次使用~");
break;
}
//2. create data package
byte[] bytes = s.getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(),8889);
//3.send
socket.send(packet);
System.out.println("客户端发送了长度为 "+bytes.length+" bytes的数据");
System.out.println("数据为: "+new String(bytes));
System.out.println("----------------------");
}
socket.close();
}
}

你可能会好奇为什么S可以在循环外创建packet重复使用而C每次循环都要创建新的?
因为C每次发送的信息长度是未知的,而packet创建需要给出length所以C每次发送都需要重新创建,同时S对C发送的消息长度也是未知的,但是由于UDP对数据包有64KB上限制约,所以我们可以直接创建个64KB大的packet来接收,然后截断字节就可以。

四、TCP编程

TCP中C由Socket类创建,而S由ServerSocket类创建,然后通过accept方法再次获得Socket类。

S代码如下

language-cpp
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
    public static void main(String[] args) throws Exception {

//1.create serversocket
ServerSocket serverSocket = new ServerSocket(8889);
//2.wait client link request
Socket socket = serverSocket.accept();
//3.get input is for receive message
InputStream is = socket.getInputStream();
//4.package low is to high is
DataInputStream dis = new DataInputStream(is);
while (true) {

try {

//5.receive message
System.out.println("消息来自: "+socket.getRemoteSocketAddress());
String rs = dis.readUTF();
System.out.println("消息为:"+rs);
} catch (IOException e) {

System.out.println("客户端离线了");
break;
}
}
//close
dis.close();
socket.close();
}
}

S会在accept等待C
C代码如下

language-cpp
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
public class Client {

public static void main(String[] args) throws Exception {

//1.create socket and send link request to server
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8889);
//2.get socket output for write message to server
OutputStream os = socket.getOutputStream();
//3.package low os to high os
DataOutputStream dos = new DataOutputStream(os);

Scanner sc = new Scanner(System.in);
while (true) {

System.out.println("请说");
String s = sc.nextLine();
if (s.equals("exit")){

break;
}
//4.write message
dos.writeUTF(s);
dos.flush();//立刻发送
System.out.println("已发送~");
}

//close
dos.close();
socket.close();
}
}

通过高级流包装原始流,方便编程,这里用的是数据流,将字符串以UTF-8的编码形式传输。

五、QA

TCP怎么实现多个C对一个S?

一个S应对多个C的TCP通信,S需要线程池,每个通信都抛给子线程处理即可。在ServerSocket.accept到一个C的适合,就把这个socket抛到子线程。

TCP怎么实现群聊?

C除了需要发送信息,还需要接收信息,并且两个行为需要持续开启,所以C需要多线程,一个从用户键盘读取信息发送,一个从S接收其他C发送的信息。
S除了线程池外,还需要一个子线程都能访问共享数组,来保存所有socket,子线程收到对应的C的消息,就抄一份发给所有socket。

作者

Xiamu

发布于

2023-10-14

更新于

2024-08-11

许可协议

评论