发布时间:2021-08-05 09:18:42 阅读次数:166
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 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");
}
}
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; // 记录是客户端 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();
}
}
}
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;
/**
* @author :erickun
* @date :Created in
* @description:
* @modified By:
* @version: $
*/
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;
/**
* @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();
}
}
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));
//再把信息传到集合中去,再在outputThread中输出给其他客户端
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;
/**
* @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();
}
}
}
以上这些,感兴趣的小伙伴们可以去尝试一下,开发一个自己的聊天室。