常见架构:
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()函数使得监听到接入的客户端,这里可以采用死循环【建立新的线程】保持服务端一直在监听并接受新的客户端的连接。
ServerSocket ss = new ServerSocket(port);
Socket socket = ss.accept();
客户端: 同样通过绑定ip port建立Socket
socket = new Socket(host, port);
服务端accept得到的Socket相当于是在客户端自建的Socket基础之上构建的新的Socket
服务端得到了与客户端连接的Socket之后便可以拿到该Socket的输入、输出流; 对于客户端也是一样,需要通过Socket拿到输入输出流
InputStream is= socket.getInputStream();
OutputStream os = socket.getOutputStream();
群聊代码:
思路:
??????? 服务端和客户端通信必须构建一个管道,管道的两端分别是InputStream、OutputStream,当客户端输出时用OutputStream,某一个客户端对应的就是InputStream来接受客户端的输出,即每个管道的输入输出一一对应。 ??????? 那么对于服务端来说,他的输入流,应该是客户端的输出流,有多少个客户端,就应该有多少个线程来维护这个管道,所以每连接一个客户端,就应该启动一个输入流给服务端。 ??????? 那么,服务端的输出流(输出的线程)应该有多少个呢,如果要实现转发 群发功能,那么服务端的输出流只能是一个,因为它要给每一个客户端去发消息,消息可以存在消息列表里面,每次从msgQueue里面拿出第一个消息,然后发送给每一个客户端,所以对于服务端的输出线程,它理应包含一个list去装载每一个客户端的socket,这样才能在群发时 遍历每一个客户端的socket 并且拿到socket对应的输出流,将消息群发出去。 ??????? 对于1个客户端来说,比服务端简单很多,输入流(输入线程)就只用构建一个,用于接受客户端输出的数据,输出流(输出线程)也是构建一个,用于发送给服务端。具体的代码架构如下图所示,将客户端和服务端代码分离,便于维护和后期拓展功能
客户端:
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");
}
}
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();
}
}
}
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();
}
}
}
package chatroom.client;
import chatroom.client.Client;
public class ClientTest1 {
public static void main(String[] args) {
Client client = new Client("客户端1");
}
}
package chatroom.client;
import chatroom.client.Client;
public class ClientTest2 {
public static void main(String[] args) {
Client client = new Client("客户端2");
}
}
服务端:
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();
}
}
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();
}
}
}
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();
}
}
}
|