一、概述
基本的通信架构有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代码如下
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代码如下
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代码如下
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代码如下
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。