源码家族
当前位置:首页 > 资讯中心

资讯中心

【 手把手教你写一个socket聊天室 】

发布时间: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();

        }

    }

}


以上这些,感兴趣的小伙伴们可以去尝试一下,开发一个自己的聊天室。


下一篇:帮你搞懂java中的classpath和jar详解