Java版-飞鸽传信

一、项目简介

      简单实现多个客户端之间的通信功能。

二、项目功能

  1. 用户上线,通知服务器注册并统计群聊人数;
  2. 所有用户可以进行群聊,以及可以指定用户进行私聊;
  3. 用户下线,通知服务器,服务器更新信息。

三、具体设计

     首先,聊天室的项目是建立在Socket编程基础上,Socket编程的操作流程如下:

1.先建立连接,直到目标服务器的IP地址与端口号

2.获取本连接的输入输出流

3.通过IO进行数据的读取与写入

4.关闭流

在此项目中需要有Socket类,一个是服务器ServerSocket,另一个是客户端Socket。

服务器操作流程:

1.建立服务端Socket,等待客户连接,通过构造方法实现-建立基站

ServetSocket server = new ServerSocket(port);

2. 等待客户端的连接

Socket cilent = server.accept();  //一直阻塞直到有客户端连接,返回客户端Socket

 3.取得输入、输出流进行通信(此方法是由客户端提供)

//取得输入输出流
readFromCilent = new Scanner(cilent.getInputStream());
writeWsgToCilent = new PrintStream(cilent.getOutputStream());

 4.关闭流,将基站关闭

server.close();
readFromCilent.close();
writeWsgToCilent.close();

 客户端操作流程:

1.连接到指定的服务器,通过构造方法实现

cilent = new Socket("127.0.0.1",6666);

2. 取得输入输出流

//取得输入输出流
readFromServer = new Scanner(cilent.getInputStream());
writeWsgToServer = new PrintStream(cilent.getOutputStream(),true,"UTF-8");

3.关闭流

cilent.close();
readFromServer.close();
writeWsgToServer.close();

 服务器:

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket server = null;
        Scanner readFromCilent = null;
        PrintStream writeWsgToCilent = null;
        try {
            server = new ServerSocket(6666);
            //等待客户端的连接
            System.out.println("等待客户端的连接");
            Socket cilent = server.accept();
            System.out.println("新的客户端的端口号为:"+cilent.getPort());
            //取得输入输出流
            readFromCilent = new Scanner(cilent.getInputStream());
            writeWsgToCilent = new PrintStream(cilent.getOutputStream());
            System.out.print("客户端发送的消息为:");
            if(readFromCilent.hasNext()){
                System.out.println(readFromCilent.nextLine());
            }
            readFromCilent.useDelimiter("\n");
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            server.close();
            readFromCilent.close();
            writeWsgToCilent.close();
        }
    }
}

客户端:

import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket cilent = null;
        Scanner readFromServer = null;
        PrintStream writeWsgToServer = null;
        try {
            //与服务端建立连接
            cilent = new Socket("127.0.0.1",6666);
            //取得输入输出流
            readFromServer = new Scanner(cilent.getInputStream());
            writeWsgToServer = new PrintStream(cilent.getOutputStream(),true,"UTF-8");
            //向服务端发送消息
            writeWsgToServer.println("I am Cilent");
            readFromServer.useDelimiter("\n");
            System.out.print("服务端发送的消息为:");
            if(readFromServer.hasNext()){
                System.out.println(readFromServer.nextLine());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            cilent.close();
            readFromServer.close();
            writeWsgToServer.close();
        }
    }
}

                                                                                                                                                                   

 以上为单线程通信实现,如果当服务器和客户端同时进行对方的读取,就会产生问题。

 

单线程通信问题:

        1.容易造成双方卡死的现象--类比电话占线

        2.发送一次数据后服务器与客户端均退出,不能持久通信

        3.不能同时进行数据的读取与写入,因为是顺序操作

                   解决--进行读写分离,作为两个不相关的线程

        4.服务器只能处理一个客户端的连接

                   解决--每当有一个客户端的连接进来,就创建一个线程,处理此客户端请求

 

多线程通信:

客户端:读写分离,读作为一个线程,写作为一个线程

服务端:存储所有连接的客户端,使用ConcurrentHashMap来存储所有注册的客户端信息 

private static Map<String,Socket> clientMap = new ConcurrentHashMap<>();

用户注册:

        userName:zhangsan

群聊实现:

        G:hello i...

私聊实现:

         P:zhangsan-hello i am

退出:

        zhangsan:byebye

 

 

服务器: 

 

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class ThreadServer {
    //使用ConcurrentHashMap来存储所有注册的客户端信息
    private static Map<String,Socket> clientMap = new ConcurrentHashMap<>();

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(6666);
        ExecutorService service = Executors.newFixedThreadPool(20);
        for(int i=0; i<20; i++){
            System.out.println("等待客户端连接...");
            Socket client = serverSocket.accept();
            System.out.println("有新的客户端连接,端口号为:"+client.getPort());
            service.submit(new ExecuteClientRequest(client));
        }
    }
    //具体处理与每个客户端通信的内部类
    static class ExecuteClientRequest implements Runnable{
        private Socket client;

        public ExecuteClientRequest(Socket client) {
            this.client = client;
        }

        @Override
        public void run() {
            //获取输入流,不断地读取用户发来的信息
            Scanner readFromClient = null;
            try {
                readFromClient = new Scanner(client.getInputStream());
                readFromClient.useDelimiter("\n");
                while(true){
                    if(readFromClient.hasNextLine()){
                        String str = readFromClient.nextLine();
                        //进行\r的过滤流程
                        //win下进行换行的过滤
                        Pattern pattern = Pattern.compile("\r");
                        Matcher matcher = pattern.matcher(str);
                        str = matcher.replaceAll("");
                        if(str.startsWith("userName")){
                            //用户注册流程
                            //userName:zhangsan
                            String userName = str.split(":")[1];
                            userRegister(userName,client);
                            continue;
                        } else if(str.startsWith("G")){
                            //群聊流程
                            //G:hello i...
                            String msg = str.split(":")[1];
                            groupChat(msg);
                            continue;
                        } else if(str.startsWith("P")){
                            //私聊流程
                            //P:zhangsan-hello i am...
                            String tempMsg = str.split(":")[1];
                            String userName = tempMsg.split("-")[0];
                            String msg = tempMsg.split("-")[1];
                            privateChat(userName,msg);
                        } else if(str.contains("byebye")){
                            //用户退出流程
                            //zhangsan:byebye
                            String userName = str.split(":")[0];
                            userExist(userName);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 用户注册方法
         * @param userName 用户名
         * @param client 对应的Socket
         */
        private void userRegister(String userName,Socket client){
            //将用户信息保存到服务器中
            clientMap.put(userName,client);
            //取到当前注册到服务器的所有人的个数
            int size = clientMap.size();
            System.out.println("当前聊天室内共有"+size+"人");
            String userOnLine = userName+"上线了!";
            groupChat(userOnLine);
        }

        /**
         * 群聊流程
         * @param msg 要发送的群聊信息
         */
        private void groupChat(String msg){
            //取出所有连接的客户端,依次拿到输出流进行遍历输出
            Collection<Socket> clients = clientMap.values();
            for(Socket client : clients){
                //取出此客户端的输出流
                try {
                    PrintStream out = new PrintStream(client.getOutputStream(),true,"UTF-8");
                    out.println("群聊信息为:"+msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * 私聊流程
         * @param userName 私聊的用户名
         * @param msg 私聊的信息
         */
        private void privateChat(String userName,String msg){
            Socket client = clientMap.get(userName);
            try {
                PrintStream out = new PrintStream(client.getOutputStream(),true,"UTF-8");
                out.println("私聊信息为:"+msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 用户退出
         * @param userName 退出的用户名
         */
        private void userExist(String userName){
            clientMap.remove(userName);
            System.out.println("当前聊天室人数为:"+clientMap.size());
            String groupChatMsg = userName+"已下线";
            groupChat(groupChatMsg);
        }
    }

}

 

 

 客户端:

 

import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * 读取服务器发来信息的线程
 */
class ReadFromServerThread implements Runnable{
    private Socket client;
    //通过构造方法传入通信的Socket
    public ReadFromServerThread(Socket client) {
        this.client = client;
    }
    @Override
    public void run() {
        Scanner in = null;
        try {
            //获取客户端的输入流,读取服务器发来的信息
            in = new Scanner(client.getInputStream());
            //碰到回车在换行
            in.useDelimiter("\n");
            //不断获取服务器信息
            while(true){
                if(in.hasNextLine()){
                    System.out.println("从服务器发来的消息为:"+in.nextLine());
                }
                //此客户端退出
                if(client.isClosed()){
                    System.out.println("客户端已关闭");
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            in.close();
        }
    }
}


class WriteToServerThread implements Runnable{
    private Socket client;

    public WriteToServerThread(Socket client) {
        this.client = client;
    }

    @Override
    public void run() {
        //获取键盘输入,向服务器发送信息
        Scanner scanner = new Scanner(System.in);
        scanner.useDelimiter("\n");
        PrintStream out = null;
        try {
            //获取客户端输出流,向服务器发送信息
            out = new PrintStream(client.getOutputStream(),true,"UTF-8");
            while(true){
                System.out.println("请输入要发送的消息...");
                String strToServer;
                if(scanner.hasNextLine()){
                    strToServer = scanner.nextLine();
                    out.println(strToServer);
                    //客户端退出标志
                    if(strToServer.contains("byebye")){
                        System.out.println("关闭客户端");
                        break;
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            scanner.close();
            out.close();
        }
    }
}


public class ThreadClient {
    public static void main(String[] args) {
        try {
            //建立与服务器的连接
            Socket client = new Socket("127.0.0.1",6666);
            //创建读写线程与服务器通信
            Thread readFromServer = new Thread(new ReadFromServerThread(client));
            Thread writeToServer = new Thread(new WriteToServerThread(client));
            readFromServer.start();
            writeToServer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

                                                                                                                                       

       

 

   

     

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gx1500291

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值