常见架构:

C/S架构:Client/Server(客户端/服务器)结构在这里插入图片描述
B/S架构:Browser/Server(浏览器/服务器)结构
在这里插入图片描述

服务端和客户端

        简单地说:一般客户端负责和用户的交互,也就是屏幕显示(UI/UE),服务端负责数据存储,也就是你的用户数据,而计算能力,客户端和服务端一般各负责一部分。
        微信、qq这种聊天功能一般在用户之间通信时,用户的数据先是发送到服务器上的,然后通过服务器进行转发给指定用户,从而完成一次用户间的通信,那么如何证明这一点呢?比如一下几种场景:

  1. 当A用户不在线时,B用户给A发送,A是接收不到的,当A一上线,信息会立马发送给他,可推理出B用户发送的数据会保存在服务器中。
  2. 当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;

/**
* @author :erickun
* @date :Created in 2021/8/1 10:42 上午
* @description
* @modified By:
* @version: $
*/
public class Client {

//定义Socket对象
Socket socket = null;
//服务器的Ip地址和服务器所使用的端口号
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;

/**
* @author :erickun
* @date :Created in 2021/8/1 1:24 下午
* @description
* @modified By:
* @version: $
*/
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;

/**
* @author :erickun
* @date :Created in 2021/8/1 1:26 下午
* @description
* @modified By:
* @version: $
*/
public class OutputThread implements Runnable {
Socket socket = null;
private String label; // 记录是客户端 or 服务端

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;

/**
* @author :erickun
* @date :Created in 2021/8/1 3:42 下午
* @description:客户端1
* @modified By:
* @version: $
*/
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;

/**
* @author :erickun
* @date :Created in 2021/8/1 3:42 下午
* @description
* @modified By:
* @version: $
*/
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;

/**
* @author :erickun
* @date :Created in 2021/8/1 10:43 上午
* @description
* @modified By:
* @version: $
*/
public class Server {

private int port = 8082;
private String label = "服务端";
Socket socket = null;
public LinkedBlockingDeque<String> msgQueue;
public ArrayList<Socket> sockets;//维护每一个与客户端连接的socket
public Integer clientNum;

public Server() {
createServer();
this.clientNum = sockets.size();
}

public void createServer() {

try {
//服务器套接字
ServerSocket ss = new ServerSocket(port);
System.out.println(("服务器已经启动,监听端口为" + port));
//初始化一个msgQueue用于存放客户端传来的数据
this.msgQueue = new LinkedBlockingDeque<String>();
this.sockets = new ArrayList<Socket>();

createOutput(sockets, msgQueue);
// 给客户端发送信息 只需要维护一个OutputThread即可 以保证每次从msgQueue里取一次msg
// 都能将该msg群发个多个客户端

while (true) {

Socket socket = ss.accept();
sockets.add(socket);
createInput(socket, msgQueue);//每一个与客户端之间的socket都需要建立一个inputThread去接收

//服务器套接字等待一个客服端socket连入,如果连接成功的话,就会创建一个套接字,不然在这里一直等待
System.out.println("已经接受连接");

}
} catch (IOException e) {
e.printStackTrace();
}

}

//线程 OutputThread:服务端输出到客户端
public void createOutput(ArrayList<Socket> sockets, LinkedBlockingDeque<String> msgQueue) {

OutputThread outputThread = new OutputThread(msgQueue, sockets);
new Thread(outputThread).start();

}

//线程 InputThread:服务端接受客户端的输入
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;

/**
* @author :erickun
* @date :Created in 2021/8/1 1:24 下午
* @description
* @modified By:
* @version: $
*/
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));
//再把信息传到集合中去,再在outputThread中输出给其他客户端
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;

/**
* @author :erickun
* @date :Created in 2021/8/1 1:26 下午
* @description
* @modified By:
* @version: $
*/
public class OutputThread implements Runnable {
private ArrayList<Socket> sockets;
public LinkedBlockingDeque<String> msgQueue;
private String label; // 记录是客户端 or 服务端

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();
}
}
}