java socket编程(2)——利用socket实现聊天之单聊

上一篇 java socket编程(1)——利用socket实现聊天之消息推送

中我们讲到如何使用socket让服务器和客户端之间传递消息,达到推送消息的目的,接下来我将写出如何让服务器建立客户端与客户端之间的通讯。

其实就是建立一个一对一的聊天通讯。

与上一篇实现消息推送的代码有些不同,在它上面加以修改的。

如果没有提到的方法或者类则和上一篇一模一样。

1,修改实体类(服务器端和客户端的实体类是一样的)

1,UserInfoBean 用户信息表

public class UserInfoBean implements Serializable {
    private static final long serialVersionUID = 2L;
    private long userId;// 用户id
    private String userName;// 用户名
    private String likeName;// 昵称
    private String userPwd;// 用户密码
    private String userIcon;// 用户头像
    //省略get、set方法
    }

2,MessageBean 聊天信息表

public class MessageBean implements Serializable {
    private static final long serialVersionUID = 1L;
    private long messageId;// 消息id
    private long groupId;// 群id
    private boolean isGoup;// 是否是群消息
    private int chatType;// 消息类型;1,文本;2,图片;3,小视频;4,文件;5,地理位置;6,语音;7,视频通话
    private String content;// 文本消息内容
    private String errorMsg;// 错误信息
    private int errorCode;// 错误代码
    private int userId;//用户id
    private int friendId;//目标好友id
    private MessageFileBean chatFile;// 消息附件
    //省略get、set方法
}

3,MessageFileBean 消息附件表

public class MessageFileBean implements Serializable {
    private static final long serialVersionUID = 3L;
    private int fileId;//文件id
    private String fileName;//文件名称
    private long fileLength;//文件长度
    private Byte[] fileByte;//文件内容
    private String fileType;//文件类型
    private String fileTitle;//文件头名称
    //省略get、set方法
}

2,(服务器端代码修改)ChatServer 主要的聊天服务类,加以修改

public class ChatServer {
    // socket服务
    private static ServerSocket server;

    // 使用ArrayList存储所有的Socket
    public List<Socket> socketList = new ArrayList<>();

    // 模仿保存在内存中的socket
    public Map<Integer, Socket> socketMap = new HashMap();
    // 模仿保存在数据库中的用户信息
    public Map<Integer, UserInfoBean> userMap = new HashMap();
    public Gson gson = new Gson();

    /**
     * 初始化socket服务
     */
    public void initServer() {
        try {
            // 创建一个ServerSocket在端口8080监听客户请求
            server = new ServerSocket(SocketUrls.PORT);
            createMessage();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 创建消息管理,一直接收消息
     */
    private void createMessage() {
        try {
            System.out.println("等待用户接入 : ");
            // 使用accept()阻塞等待客户请求
            Socket socket = server.accept();
            // 将链接进来的socket保存到集合中
            socketList.add(socket);
            System.out.println("用户接入 : " + socket.getPort());
            // 开启一个子线程来等待另外的socket加入
            new Thread(new Runnable() {
                public void run() {
                    // 再次创建一个socket服务等待其他用户接入
                    createMessage();
                }
            }).start();
            // 用于服务器推送消息给用户
             getMessage();
            // 从客户端获取信息
            BufferedReader bff = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 读取发来服务器信息
            String line = null;
            // 循环一直接收当前socket发来的消息
            while (true) {
                Thread.sleep(500);
                // System.out.println("内容 : " + bff.readLine());
                // 获取客户端的信息
                while ((line = bff.readLine()) != null) {
                    // 解析实体类
                    MessageBean messageBean = gson.fromJson(line, MessageBean.class);
                    // 将用户信息添加进入map中,模仿添加进数据库和内存
                    // 实体类存入数据库,socket存入内存中,都以用户id作为参照
                    setChatMap(messageBean, socket);
                    // 将用户发送进来的消息转发给目标好友
                    getFriend(messageBean);
                    System.out.println("用户 : " + userMap.get(messageBean.getUserId()).getUserName());
                    System.out.println("内容 : " + messageBean.getContent());
                }
            }
            // server.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("错误 : " + e.getMessage());
        }
    }

    /**
     * 发送消息
     */
    private void getMessage() {
        new Thread(new Runnable() {
            public void run() {
                try {
                    String buffer;
                    while (true) {
                        // 从控制台输入
                        BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
                        buffer = strin.readLine();
                        // 因为readLine以换行符为结束点所以,结尾加入换行
                        buffer += "\n";
                        // 这里修改成向全部连接到服务器的用户推送消息
                        for (Socket socket : socketMap.values()) {
                            OutputStream output = socket.getOutputStream();
                            output.write(buffer.getBytes("utf-8"));
                            // 发送数据
                            output.flush();
                        }
                    }
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     * 模拟添加信息进入数据库和内存
     * 
     * @param messageBean
     * @param scoket
     */
    private void setChatMap(MessageBean messageBean, Socket scoket) {
        // 将用户信息存起来
        if (userMap != null && userMap.get(messageBean.getUserId()) == null) {
            userMap.put(messageBean.getUserId(), getUserInfoBean(messageBean.getUserId()));
        }
        // 将对应的链接进来的socket存起来
        if (socketMap != null && socketMap.get(messageBean.getUserId()) == null) {
            socketMap.put(messageBean.getUserId(), scoket);
        }
    }

    /**
     * 模拟数据库的用户信息,这里创建id不同的用户信息
     * 
     * @param userId
     * @return
     */
    private UserInfoBean getUserInfoBean(int userId) {
        UserInfoBean userInfoBean = new UserInfoBean();
        userInfoBean.setUserIcon("用户头像");
        userInfoBean.setUserId(userId);
        userInfoBean.setUserName("admin");
        userInfoBean.setUserPwd("123123132a");
        return userInfoBean;
    }

    /**
     * 将消息转发给目标好友
     * 
     * @param messageBean
     */
    private void getFriend(MessageBean messageBean) {
        if (socketMap != null && socketMap.get(messageBean.getFriendId()) != null) {
            Socket socket = socketMap.get(messageBean.getFriendId());
            String buffer = gson.toJson(messageBean);
            // 因为readLine以换行符为结束点所以,结尾加入换行
            buffer += "\n";
            try {
                // 向客户端发送信息
                OutputStream output = socket.getOutputStream();
                output.write(buffer.getBytes("utf-8"));
                // 发送数据
                output.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

3,(客户端代码)LoginActivity 登陆页面修改可以登录多人

public class LoginActivity extends AppCompatActivity {

    private EditText chat_name_text, chat_pwd_text;
    private Button chat_login_btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        chat_name_text = (EditText) findViewById(R.id.chat_name_text);
        chat_pwd_text = (EditText) findViewById(R.id.chat_pwd_text);
        chat_login_btn = (Button) findViewById(R.id.chat_login_btn);
        chat_login_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int status = getLogin(chat_name_text.getText().toString().trim(), chat_pwd_text.getText().toString().trim());
                if (status == -1 || status == 0) {
                    Toast.makeText(LoginActivity.this, "密码错误", Toast.LENGTH_LONG).show();
                    return;
                }
                getChatServer(getLogin(chat_name_text.getText().toString().trim(), chat_pwd_text.getText().toString().trim()));
                Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                startActivity(intent);
                finish();
            }
        });
    }

    /**
     * 返回登陆状态,1为用户,2为另一个用户,这里模拟出两个用户互相通讯
     *
     * @param name
     * @param pwd
     * @return
     */
    private int getLogin(String name, String pwd) {
        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(pwd)) {
            return 0;//没有输入完整密码
        } else if (name.equals("admin") && pwd.equals("1")) {
            return 1;//用户1
        } else if (name.equals("admin") && pwd.equals("2")) {
            return 2;//用户2
        } else {
            return -1;//密码错误
        }
    }

    /**
     * 实例化一个聊天服务
     *
     * @param status
     */
    private void getChatServer(int status) {
        ChatAppliaction.chatServer = new ChatServer(status);
    }

}

4,(客户端代码)ChatServer 聊天服务代码逻辑的修改

public class ChatServer {
    private Socket socket;
    private Handler handler;
    private MessageBean messageBean;
    private Gson gson = new Gson();
    // 由Socket对象得到输出流,并构造PrintWriter对象
    PrintWriter printWriter;
    InputStream input;
    OutputStream output;
    DataOutputStream dataOutputStream;

    public ChatServer(int status) {
        initMessage(status);
        initChatServer();
    }

    /**
     * 消息队列,用于传递消息
     *
     * @param handler
     */
    public void setChatHandler(Handler handler) {
        this.handler = handler;
    }

    private void initChatServer() {
        //开个线程接收消息
        receiveMessage();
    }

    /**
     * 初始化用户信息
     */
    private void initMessage(int status) {

        messageBean = new MessageBean();
        UserInfoBean userInfoBean = new UserInfoBean();
        userInfoBean.setUserId(2);
        messageBean.setMessageId(1);
        messageBean.setChatType(1);
        userInfoBean.setUserName("admin");
        userInfoBean.setUserPwd("123123123a");
        //以下操作模仿当用户点击了某个好友展开的聊天界面,将保存用户id和聊天目标用户id
        if (status == 1) {//如果是用户1,那么他就指向用户2聊天
            messageBean.setUserId(1);
            messageBean.setFriendId(2);
        } else if (status == 2) {//如果是用户2,那么他就指向用户1聊天
            messageBean.setUserId(2);
            messageBean.setFriendId(1);
        }
        ChatAppliaction.userInfoBean = userInfoBean;
    }

    /**
     * 发送消息
     *
     * @param contentMsg
     */
    public void sendMessage(String contentMsg) {
        try {
            if (socket == null) {
                Message message = handler.obtainMessage();
                message.what = 1;
                message.obj = "服务器已经关闭";
                handler.sendMessage(message);
                return;
            }
            byte[] str = contentMsg.getBytes("utf-8");//将内容转utf-8
            String aaa = new String(str);
            messageBean.setContent(aaa);
            String messageJson = gson.toJson(messageBean);
            /**
             * 因为服务器那边的readLine()为阻塞读取
             * 如果它读取不到换行符或者输出流结束就会一直阻塞在那里
             * 所以在json消息最后加上换行符,用于告诉服务器,消息已经发送完毕了
             * */
            messageJson += "\n";
            output.write(messageJson.getBytes("utf-8"));// 换行打印
            output.flush(); // 刷新输出流,使Server马上收到该字符串
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("test", "错误:" + e.toString());
        }
    }

    /**
     * 接收消息,在子线程中
     */
    private void receiveMessage() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 向本机的8080端口发出客户请求
                    socket = new Socket(SocketUrls.IP, SocketUrls.PORT);
                    // 由Socket对象得到输入流,并构造相应的BufferedReader对象
                    printWriter = new PrintWriter(socket.getOutputStream());
                    input = socket.getInputStream();
                    output = socket.getOutputStream();
                    dataOutputStream = new DataOutputStream(socket.getOutputStream());
                    // 从客户端获取信息
                    BufferedReader bff = new BufferedReader(new InputStreamReader(input));
                    // 读取发来服务器信息
                    String line;
                    while (true) {
                        Thread.sleep(500);
                        // 获取客户端的信息
                        while ((line = bff.readLine()) != null) {
                            Log.i("socket", "内容 : " + line);
                            MessageBean messageBean = gson.fromJson(line, MessageBean.class);
                            Message message = handler.obtainMessage();
                            message.obj = messageBean.getContent();
                            message.what = 1;
                            handler.sendMessage(message);
                        }
                        if (socket == null)
                            break;
                    }
                    output.close();//关闭Socket输出流
                    input.close();//关闭Socket输入流
                    socket.close();//关闭Socket
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.e("test", "错误:" + e.toString());
                }
            }
        }).start();
    }


    public Socket getSocekt() {
        if (socket == null) return null;
        return socket;
    }
}

如此一来,代码逻辑已经从消息推送的逻辑修改成了单聊的逻辑了。
这个代码可以让用户1和用户2相互聊天,并且服务器会记录下他们之间的聊天记录。并且服务器还是拥有消息推送的功能。

想要学习的人可以下载demo来研究。

demo下载

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值