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