P2P Chat Based on Socket Communication

Posted by dvayne on Fri, 14 Jun 2019 20:04:51 +0200

Introduction of Socket Communication

Socket is an abstract layer between application layer and transport layer. It abstracts the complex operation of TCP/IP layer into several simple interface supply layer calls to realize process communication in the network. Socket is an implementation of "open-read/write-close" mode. Server and client maintain a "file" separately. After the connection is opened, they can write to their own files. Let the other side read or read the other side's content and close the file at the end of the communication. Take the socket which uses TCP protocol as an example, its interaction process is as follows: Tcp protocol: three handshakes protocol (service acceptance, customer service connection), four waves (customer service close, server close) server accept blocked, waiting for (multiple) client's links; the general links and communication process are as follows:

Read data from Socket, open file input stream from Socket, read data from input stream, if Socket has data to read data, Socket has no data, read blocking;

II. IM process

(1) Creating and Server-side Message Channel Socket
(2) Verify account number and password
(3) Get all contacts BuddyList
(4) The server sends the message-forwarding according to the target account of the message.

3. Server and Client of Project

Now comb the link and communication process between server and client. QQConnection Manager maintains the link library of the whole server. QQConnection is the socket link manager of the corresponding client-connected server, which is stored in QQConnection Manager. QQConnection contains the socket connected to a single server and the corresponding input and output streams. In receiving and sending messages with clients, QQMessage contains some attributes of messages, such as message type, message content, receiver and sender of messages, etc.

Let's look at the classification of server-side code packages:

The bean directory structure contains QQ messages and entity classes of users, friends and databases; core is an important implementation of the whole project; the server listens for messages sent by the client, and then judges whether it needs to be forwarded to another client according to the type of message needed; the main is that the server opens and opens a blocked thread, etc. Links to clients;

IV. Implementation of Server

QQImserver

public class QQImServer {

    public static void main(String[] args) {

        try {
            // (1) Create a connection between threads and other clients
            final ServerSocket server = new ServerSocket(5223);
            System.out.println("---Server startup---" + new Date().toString());
            new Thread() {//
                public void run() {
                    while (true) {
                        QQConnection conn = null;
                        try {
                            //Accept links from different clients and assign Socket s to different clients
                            Socket client = server.accept();
                            System.out.println("---Client access---" + client);
                            // (2) If the client connection succeeds in allocating a thread
                            conn = new QQConnection(client);
                            conn.addOnRecevieMsgListener(new LoginMsgListener(conn));
                            conn.addOnRecevieMsgListener(new ChatP2PListener());
                            conn.addOnRecevieMsgListener(new ChatRoomListener());
                            conn.addOnRecevieMsgListener(new LoginOutListener());
                            // (3) Waiting for user data in the thread
                            conn.connect();
                            // Assign a thread to the client
                        } catch (IOException e) {
                            e.printStackTrace();
                            conn.disconnect();
                        }
                    }
                };
            }.start();
        } catch (Exception e) {//
            e.printStackTrace();
        }
    }
}

QQMessageType

public class QQMessageType {

    public static final String MSG_TYPE_REGISTER = "register";      // register
    public static final String MSG_TYPE_LOGIN = "login";            // Sign in 
    public static final String MSG_TYPE_LOGIN_OUT = "loginout";     // Logout
    public static final String MSG_TYPE_BUDDY_LIST = "buddylist";   // Friends List
    public static final String MSG_TYPE_CHAT_P2P = "chatp2p";       // chat
    public static final String MSG_TYPE_CHAT_ROOM = "chatroom";     // Group chat
    public static final String MSG_TYPE_OFFLINE = "offline";        // Offline
    public static final String MSG_TYPE_SUCCESS = "success";        // Success
    public static final String MSG_TYPE_FAILURE = "failure";        // fail

}

QQMessage, where ProtocalObject encapsulates the method of transforming objects to XML, using XStream.jar implementation

public class QQMessage extends ProtocalObject {

    public String type = QQMessageType.MSG_TYPE_CHAT_P2P;// Data chat login of type
    public long from = 0L;                               // Sender account
    public String fromNick = "";                         // Nickname?
    public int fromAvatar = 1;                           // Head portrait
    public long to = 0L;                                 // Receiver account
    public String content = "";                          // Content of the message
    public String sendTime = MyTime.getTime();           // Delivery time

}

QQConnection, which is used to obtain input and output streams through socket after the link is successful, send and receive data to the client of the link, and transmit the received message to the implementer through listener to forward the message.

public class QQConnection extends Thread {

    private Socket scoket = null;
    public DataOutputStream writer = null;
    public DataInputStream reader = null;
    public QQUser who = null;
    public String ip;
    public int port;

    public QQConnection(Socket scoket) {
        super();
        try {
            this.scoket = scoket;
            writer = new DataOutputStream(this.scoket.getOutputStream());
            reader = new DataInputStream(this.scoket.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public QQConnection(String ip, int port) {
        super();
        this.ip = ip;
        this.port = port;
        init(ip, port);
    }

    private void init(String ip, int port) {
        try {
            this.scoket = new Socket(ip, port);
            writer = new DataOutputStream(this.scoket.getOutputStream());
            reader = new DataInputStream(this.scoket.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Connect
    public void connect() {
        if (this.scoket == null) {
            init(ip, port);
        }
        flag = true;
        start();
    }

    // Disconnect
    public void disconnect() {
        try {
            flag = false;
            writer.close();
            reader.close();
            // stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 1. Affirmation listener and response method
    public static interface OnRecevieMsgListener {
        public void onReceive(QQMessage msg);
    }

    // 2. Supporting multiple listeners
    private List<OnRecevieMsgListener> listeners = new ArrayList<OnRecevieMsgListener>();

    // 3. Adding listeners
    public void addOnRecevieMsgListener(OnRecevieMsgListener listener) {
        listeners.add(listener);
    }

    // 4. Delete listeners
    public void removeOnRecevieMsgListener(OnRecevieMsgListener listener) {
        listeners.remove(listener);
    }

    private boolean flag = true;
    @Override
    public void run() {
        super.run();
        // Waiting for data
        while (flag) {
            try {
                //Read messages from remote Sockets from Sockets. This method is blocked.
                String xml;
                    xml = reader.readUTF();
                    disconnect();
                System.out.println(xml);
                if (xml != null && !"".equals(xml)) {
                    QQMessage msg = new QQMessage();
                    msg = (QQMessage) msg.fromXml(xml);
                    for (OnRecevieMsgListener l : listeners) {
                        l.onReceive(msg);
                    }
                }
                } catch (EOFException e) {
                    disconnect();
                    System.out.println("=-=EOFException---");
                    if (who != null) {
                        QQConnectionManager.remove(who.account);
                    }
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                    disconnect();
                    if (who != null) {
                        QQConnectionManager.remove(who.account);
                    }
            }
        }
    }
}

QQ Connection Manager, which manages the socket of each successful client, is stored in hashMap

public class QQConnectionManager {

    public static HashMap<Long, QQConnection> conns = new HashMap<Long, QQConnection>();
    private static QQBuddyList list = new QQBuddyList();

    public static void put(long account, QQConnection conn) {
        System.out.println("====Account number"+account+"It's on line.");
        conns.put(account, conn);
        QQUser u = Db.getByAccount(account);
        QQBuddy item = new QQBuddy();
        item.account = u.account;
        item.avatar = u.avatar;
        item.nick = u.nick;
        list.buddyList.add(0, item);
    }

    public static void remove(Long account) {
        if (conns.containsKey(account)) {
            System.out.println("====Account number"+account+"Offline");
            conns.remove(account);
            QQBuddy delete = null;
            for (QQBuddy item : list.buddyList) {
                if (account==item.account) {
                    delete = item;
                    break;
                }
            }
            if (delete != null) {
                System.out.println("====Remove 0000 from the online list"+account);
                list.buddyList.remove(delete);
            }
        }
    }

    public static String getBuddyList() {
        return list.toXml();
    }

    public static QQConnection get(long account) {
        if (conns.containsKey(account)) {
            return conns.get(account);
        }
        return null;
    }
}

LoginMsgListener, here we give an example of how the server forwards the message back to the client after successful login. MessageSender here forwards the message to the client. The code will give the download link at the end.

public class LoginMsgListener extends MessageSender implements OnRecevieMsgListener {

    private QQConnection conn = null;

    public LoginMsgListener(QQConnection conn) {
        super();
        this.conn = conn;
    }

    @Override
    public void onReceive(QQMessage fromCient) {
        if (QQMessageType.MSG_TYPE_LOGIN.equals(fromCient.type)) {
            try {
                QQMessage toClient = new QQMessage();
                if (QQMessageType.MSG_TYPE_LOGIN.equals(fromCient.type)) {
                    String[] params = fromCient.content.split("#");
                    String account = params[0];
                    String pwd = params[1];
                    QQUser user = Db.getByAccount(Long.parseLong(account));
                    if (user == null) {
                        // Non-existent
                        toClient.type = QQMessageType.MSG_TYPE_FAILURE;
                        toClient.content = "Non-existent";
                        toClient(toClient, conn);
                    } else {
                        // existence
                        if (user.password.equals(pwd)) {
                            // Log in successfully and give the list of contacts to the logon
                            toClient.type = QQMessageType.MSG_TYPE_BUDDY_LIST;
                            // Return to the online list
                            // Creating Connection Objects with Identity
                            conn.who = user;
                            if (!QQConnectionManager.conns.keySet().contains(
                                    user.account)) {
                                QQConnectionManager.put(user.account, conn);
                            }
                            //Take out the list of friends
                            toClient.content = QQConnectionManager
                                    .getBuddyList();
                            toEveryClient(toClient);
                        } else {
                            toClient.type = QQMessageType.MSG_TYPE_FAILURE;
                            toClient.content = "fail";
                            toClient(toClient, conn);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Here, the main logic of the server-side code is completed. Let's look at the main logic of the client. The bean class is the same as the server, and the logic of Connection Manager is the same. Here, let it integrate the Observable class with the Observable class, and use the observer mode. Because the thread is opened to recycle the message sent by the receiving server, we need to notify the interface to get the message, and then accept the message. The Connection Manager class needs to be observed as an observer in the UI interface; it also implements sending and receiving messages.

public class ConnectionManager extends Observable implements Runnable{

        private static final String HOST = "192.168.1.144";
        private static final int PORT = 5223;
        private static ConnectionManager sInstance = new ConnectionManager();
        private Socket mSocket;
        private InetSocketAddress mAddress;
        private DataInputStream mReader;
        private DataOutputStream mWriter;
        private boolean flag;
        private long mAccount;

        public static ConnectionManager getInstance() {
            return sInstance;
        }

        public void connect() throws UnknownHostException, IOException {
            mSocket = new Socket();
            mAddress = new InetSocketAddress(HOST, PORT);
            mSocket.connect(mAddress);
            mReader = new DataInputStream(mSocket.getInputStream());
            mWriter = new DataOutputStream(mSocket.getOutputStream());
            flag = true;
            new Thread(this).start();
        }

        public long getAccount() {
            return mAccount;
        }

        public void disConnect() throws IOException {
            QQMessage message = new QQMessage();
            message.from = mAccount;
            message.type = QQMessageType.MSG_TYPE_LOGIN_OUT;
            sendMesage(message);
            mSocket.close();
            mSocket = null;
        }

        @Override
        public void run() {
            while (flag) {
                try {
                    String xml = mReader.readUTF();
                    QQMessage message = new QQMessage();
                    message = (QQMessage) message.fromXml(xml);
                    setChanged();
                    notifyObservers(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        public void sendMesage(QQMessage message) throws IOException {
            mWriter.writeUTF(message.toXml());
        }

        public void login(String account, String pwd) throws IOException {
            mAccount = Long.parseLong(account);
            QQMessage message = new QQMessage();
            message.type = QQMessageType.MSG_TYPE_LOGIN;
            message.content = account + "#" + pwd;
            sendMesage(message);
        }
    }

Topics: socket xml network