常见架构: C/S架构:Client/Server(客户端/服务器)结构 B/S架构:Browser/Server(浏览器/服务器)结构
服务端和客户端 简单地说:一般客户端负责和用户的交互,也就是屏幕显示(UI/UE),服务端负责数据存储,也就是你的用户数据,而计算能力,客户端和服务端一般各负责一部分。 微信、qq这种聊天功能一般在用户之间通信时,用户的数据先是发送到服务器上的,然后通过服务器进行转发给指定用户,从而完成一次用户间的通信,那么如何证明这一点呢?比如一下几种场景:
当A用户不在线时,B用户给A发送,A是接收不到的,当A一上线,信息会立马发送给他,可推理出B用户发送的数据会保存在服务器中。
当A用户给B传输文件时,上传完后,B需要下载才可传输到本地,说明下载之前已经上传到服务器了,如果清空本地的该文件,在一定时间范围内仍然可以重新下载回来,说明服务器上文件还在。 对于一个简单的群聊功能,实现的基本原理就是,客户端给服务器发送数据,服务器再将请求发送给其他客户端,从而完成转发,单聊那就是在客户端传服务器的过程中加入了目标用户识别码。
TCP/IP、UDP? TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。 UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。 这里有一张图,表明了这些协议的关系。 TCP/IP协议族包括运输层、网络层、链路层。
Socket技术
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 简单来说,socket就是对tcp/ip协议的封装,在java中是一个类,方便用户去完成通信。
Socket建立通信的流程: 服务端的ServerSocket通过绑定ip和port,从而完成初始化,以保证在port下监听待接入的客户端,accept()函数使得监听到接入的客户端,这里可以采用死循环【建立新的线程】保持服务端一直在监听并接受新的客户端的连接。
1 2 ServerSocket ss = new ServerSocket(port); Socket socket = ss.accept();
客户端: 同样通过绑定ip port建立Socket
1 socket = new Socket(host, port);
服务端accept得到的Socket相当于是在客户端自建的Socket基础之上构建的新的Socket
服务端得到了与客户端连接的Socket之后便可以拿到该Socket的输入、输出流; 对于客户端也是一样,需要通过Socket拿到输入输出流
1 2 InputStream is= socket.getInputStream(); OutputStream os = socket.getOutputStream();
群聊代码: 思路:
服务端和客户端通信必须构建一个管道,管道的两端分别是InputStream、OutputStream,当服务端输出时用OutputStream,某一个客户端对应的就是InputStream来接受服务端的输出,即每个管道的输入输出一一对应。 那么对于服务端来说,他的输入流,应该是客户端的输出流,有多少个客户端,就应该有多少个线程来维护这个管道,所以每连接一个客户端,就应该启动一个输入流给服务端。 那么,服务端的输出流(输出的线程)应该有多少个呢,如果要实现转发 群发功能,那么服务端的输出流线程只能是一个 里面有多个输出流,因为它要给每一个客户端去发消息,消息可以存在消息列表里面,每次从msgQueue里面拿出第一个消息,然后发送给每一个客户端,所以对于服务端的输出线程,它理应包含一个list去装载每一个客户端的socket,这样才能在群发时 遍历每一个客户端的socket 并且拿到socket对应的输出流,将消息群发出去。 对于1个客户端来说,比服务端简单很多,输入流(输入线程)就只用构建一个,用于接受客户端输出的数据,输出流(输出线程)也是构建一个,用于发送给服务端。具体的代码架构如下图所示,将客户端和服务端代码分离,便于维护和后期拓展功能
客户端:
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 package chatroom.client;import chatroom.client.InputThread;import chatroom.client.OutputThread;import java.io.IOException;import java.net.Socket;public class Client { Socket socket = null ; private String host = "localhost" ; private int port = 8082 ; private String label = "客户端" ; private String clientName; public Client (String clientName) { this .clientName = clientName; createCient(); } private void createCient () { Socket socket = null ; try { socket = new Socket(host, port); createInput(socket,label); createOutput(socket,label); } catch (IOException e) { e.printStackTrace(); } } private void createOutput (Socket socket, String label) { OutputThread outputThread = new OutputThread(socket,label); outputThread.setClientName(clientName); new Thread(outputThread).start(); } private void createInput (Socket socket, String label) { InputThread inputThread = new InputThread(socket,label); new Thread(inputThread).start(); } public static void main (String[] args) { Client client = new Client("客户端1" ); Client client2 = new Client("客户端2" ); } }
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 package chatroom.client;import java.io.IOException;import java.io.InputStream;import java.net.Socket;import java.util.concurrent.LinkedBlockingDeque;public class InputThread implements Runnable { Socket socket = null ; private String label; public InputThread (Socket socket, String label) { this .socket = socket; this .label = label; } @Override public void run () { InputStream is = null ; try { is = socket.getInputStream(); while (true ) { byte [] b = new byte [1024 ]; int len = is.read(b); System.out.println(new String(b, 0 , len)); } } catch (IOException e) { e.printStackTrace(); } } }
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 package chatroom.client;import java.io.IOException;import java.io.OutputStream;import java.net.Socket;import java.util.HashMap;import java.util.Scanner;import java.util.concurrent.LinkedBlockingDeque;public class OutputThread implements Runnable { Socket socket = null ; private String label; private String clientName; public OutputThread (Socket socket, String label) { this .socket = socket; this .label = label; } public void setClientName (String clientName) { this .clientName = clientName; } @Override public void run () { OutputStream os = null ; try { os = socket.getOutputStream(); System.out.println("请输入要发送的内容" ); Scanner ss = new Scanner(System.in); String ans = null ; while (true ) { long curTime = System.currentTimeMillis(); curTime = curTime; ans = clientName + ":" + ss.nextLine(); os.write(ans.getBytes()); } } catch ( IOException e) { e.printStackTrace(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package chatroom.client;import chatroom.client.Client;public class ClientTest1 { public static void main (String[] args) { Client client = new Client("客户端1" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package chatroom.client;import chatroom.client.Client;public class ClientTest2 { public static void main (String[] args) { Client client = new Client("客户端2" ); } }
服务端:
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package chatroom.server;import chatroom.server.InputThread;import chatroom.server.OutputThread;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import java.util.ArrayList;import java.util.concurrent.LinkedBlockingDeque;public class Server { private int port = 8082 ; private String label = "服务端" ; Socket socket = null ; public LinkedBlockingDeque<String> msgQueue; public ArrayList<Socket> sockets; public Integer clientNum; public Server () { createServer(); this .clientNum = sockets.size(); } public void createServer () { try { ServerSocket ss = new ServerSocket(port); System.out.println(("服务器已经启动,监听端口为" + port)); this .msgQueue = new LinkedBlockingDeque<String>(); this .sockets = new ArrayList<Socket>(); createOutput(sockets, msgQueue); while (true ) { Socket socket = ss.accept(); sockets.add(socket); createInput(socket, msgQueue); System.out.println("已经接受连接" ); } } catch (IOException e) { e.printStackTrace(); } } public void createOutput (ArrayList<Socket> sockets, LinkedBlockingDeque<String> msgQueue) { OutputThread outputThread = new OutputThread(msgQueue, sockets); new Thread(outputThread).start(); } public void createInput (Socket socket, LinkedBlockingDeque<String> msgQueue) { InputThread inputThread = new InputThread(socket, msgQueue); new Thread(inputThread).start(); } public static void main (String[] args) { Server server = new 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 46 47 48 49 package chatroom.server;import java.io.IOException;import java.io.InputStream;import java.net.Socket;import java.util.concurrent.LinkedBlockingDeque;public class InputThread implements Runnable { Socket socket = null ; private String label; public LinkedBlockingDeque<String> msgQueue; public InputThread (Socket socket,LinkedBlockingDeque<String> msgQueue) { this .socket = socket; this .msgQueue = msgQueue; } public void setMsgQueue (LinkedBlockingDeque<String> msgQueue) { this .msgQueue = msgQueue; } @Override public void run () { InputStream is = null ; try { is= socket.getInputStream(); while (true ){ byte [] b = new byte [1024 ]; int len = is.read(b); System.out.println(new String(b , 0 , len)); msgQueue.add(new String(b , 0 , len)); } } catch (IOException e) { e.printStackTrace(); } } }
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 package chatroom.server;import java.io.IOException;import java.io.OutputStream;import java.net.Socket;import java.util.ArrayList;import java.util.HashMap;import java.util.Scanner;import java.util.concurrent.LinkedBlockingDeque;public class OutputThread implements Runnable { private ArrayList<Socket> sockets; public LinkedBlockingDeque<String> msgQueue; private String label; public OutputThread (LinkedBlockingDeque<String> msgQueue, ArrayList<Socket> sockets) { this .sockets = sockets; this .msgQueue = msgQueue; } @Override public void run () { OutputStream os = null ; try { while (true ) { if (msgQueue.size() > 0 ) { String poll = msgQueue.poll(); for (int i = 0 ; i < sockets.size(); i++) { Socket curSocket = sockets.get(i); os = curSocket.getOutputStream(); os.write(poll.getBytes()); } } } } catch (IOException e) { e.printStackTrace(); } } }