Android聊天室

  最新在研究网络编程,感觉好好玩,然后玩得兴起就想做个聊天室来玩玩。这个聊天室叫 ChatRoom ,是一个基于 TCP 通信的聊天室,并且用到了一些 Material Design 的控件,之前一直都在赶项目都没时间学习 Material Design(其实不是没时间,是有些事情很烦,所以耽搁了),工作室的昭哥老说我,你基础工不扎实,嘿嘿。

TCP介绍

TCP 是一种可靠、必须连接才能通信的协议。

因为需要连接才能通信,所以 TCP 通信严格区分客户端和服务器端,只有客户端连接了服务器端才能实现通信,服务器端不能连接客户端并且需要事先启动,等待客户端的请求。

使用重发机制以保证数据传输的可靠性。(发送后,需要收到确认的信息,否则进行重发)

附图
附图

ChatRoom

好了,扯了辣么多,我们来看看 ChatRoom 是怎么实现的吧!先看看效果:

服务器端

/**
 * Created by kn on 2016/5/24.
 *
 * 聊天室主线程服务
 */
public class MyServerSocket {

    public static void main(String args[]){
        new ServerListener().start();
    }
}
/**
 * Created by kn on 2016/5/24.
 *
 * 聊天监听线程类
 */
public class ServerListener extends Thread {

    @Override
    public void run() {
        try {
            //端口号port:1~65535
            ServerSocket serverSocket = new ServerSocket(30000);
            while(true){
                //该方法会阻塞当前线程
                Socket socket = serverSocket.accept();
                //建立连接
                System.out.println("有客户端链接到本地的30000端口");
                //将socket传递给新的线程
                ChatSocket chatSocket = new ChatSocket(socket);
                chatSocket.start();
                ChatManager.getInstance().add(chatSocket);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * Created by kn on 2016/5/24.
 *
 * 聊天线程类
 */
public class ChatSocket extends Thread {

    Socket socket;

    public ChatSocket(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream(), "UTF-8"));
            String line = null;
            while ((line = br.readLine()) != null) {
                ChatManager.getInstance().publish(this, line);
            }
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
    * 输出服务器返回的信息
    * @param message
    */
    public void showMessage(String message) {
        try {
            socket.getOutputStream().write((message + "\n").getBytes("UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
    * 判断socket是否关闭
    * @return
    */  
    public boolean isSocketClosed(){
        return socket.isClosed();
    }
}
/**
 * Created by kn on 2016/5/24.
 *
 * 聊天室线程管理类
 */
public class ChatManager {

    private static ChatManager instance = new ChatManager();
    //聊天线程列表
    Vector<ChatSocket> vector = new Vector<>();

    private ChatManager() {
    }

    /**
     * 单例模式
     * @return
     */
    public static ChatManager getInstance() {
        if (instance == null) {
            synchronized (ChatManager.class) {
                if (instance == null) {
                    instance = new ChatManager();
                }
            }
        }
        return instance;
    }

    /**
     * 添加聊天线程
     *
     * @param chatSocket
     */
    public void add(ChatSocket chatSocket) {
        vector.add(chatSocket);
    }

    /**
     * 向聊天室的其他线程发布消息
     * @param chatSocket
     * @param message
     */
    public void publish(ChatSocket chatSocket, String message) {
        //遍历线程列表
        for (int i = 0; i < vector.size(); i++) {
            ChatSocket mChatSocket = vector.get(i);
            //判断是否是己线程
            if (!chatSocket.equals(mChatSocket)) {
                //判断该线程是否已经断开服务器的连接
                if (mChatSocket.isSocketClosed()) {
                    vector.remove(i);
                } else {
                    mChatSocket.showMessage(message);
                }
            }
        }
    }
}

写完以上代码,服务器端基本上完成了,其实现在,我们就可以玩聊天室了。打开多个 cmd 并输入 telnet localhost 30000 就可以连接服务器,然后你就可以在黑框下聊天了。

客户端

主界面,输入服务器的 IP 和聊天中显示的名字

进入界面

public class MainActivity extends AppCompatActivity {

    MaterialEditText metName;//聊天的昵称
    MaterialEditText metIP;//聊天室的IP
    ImageView ivAvatar;//聊天的头像
    ButtonFlat btnEnter;//进入聊天按钮
    ProgressBar progressBar;//进度条
    FrameLayout flProgressBar;

    public static Socket socket = null;

    final static int SUCCESS = 0x01;//进入聊天室成功
    final static int FAILURE = 0x02;//进入聊天室失败
    final static int TIMEOUT = 0x03;//连接超时
    final static int UN_KNOWN_HOST = 0x04;//未知主机
    private BufferedWriter writer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        metName = (MaterialEditText) findViewById(R.id.met_name);
        metIP = (MaterialEditText) findViewById(R.id.met_ip);
        ivAvatar = (ImageView) findViewById(R.id.iv_avatar);
        btnEnter = (ButtonFlat) findViewById(R.id.btn_enter);
        progressBar = (ProgressBar) findViewById(R.id.progress);
        flProgressBar = (FrameLayout) findViewById(R.id.fl_progress);

        metName.setText("kntryer");
        metIP.setText("192.168.1.101");

        btnEnter.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                enterChatRoom();
            }
        });
    }

    private void enterChatRoom() {
        //获取ip
        final String ipString = metIP.getText().toString().trim();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    socket = new Socket();
                    socket.connect(new InetSocketAddress(ipString, 30000), 5000);
                    writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                    handler.sendEmptyMessage(SUCCESS);
                } catch (SocketTimeoutException e) {
                    //连接超时 在UI界面显示消息
                    handler.sendEmptyMessage(TIMEOUT);
                    e.printStackTrace();
                } catch (UnknownHostException e) {
                    handler.sendEmptyMessage(UN_KNOWN_HOST);
                    e.printStackTrace();
                } catch (IOException e) {
                    handler.sendEmptyMessage(FAILURE);
                    e.printStackTrace();
                }
            }
        }).start();
    }

    Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case SUCCESS:
                    String name = metName.getText().toString();
                    try {
                        if (writer != null) {
                            writer.write("system," + name + " enter ChatRoom\n");
                            writer.flush();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    Intent intent = new Intent(MainActivity.this, ChatActivity.class);
                    intent.putExtra("name", name);
                    startActivity(intent);
                    break;
                case FAILURE:
                    showIsOpenWifi("Please check whether the network is open or not.");
                    break;
                case TIMEOUT:
                    showIsOpenWifi("SocketTimeoutException");
                    break;
                case UN_KNOWN_HOST:
                    showIsOpenWifi("UnknownHostException");
                    break;

            }
            return false;
        }
    });

    private void showIsOpenWifi(String message) {
        new SnackBar(this, message, "Yes", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //跳到WiFi,这个还没做
            }
        }).show();
    }
}

聊天界面

/**
 * Created by kn on 2016/5/24.
 */
public class ChatActivity extends AppCompatActivity {

    ButtonFlat btnSent;//发送消息
    MaterialEditText metMsg;//消息
    ListView lvMsg;//显示消息

    ArrayList<ChatMessage> msgList = new ArrayList<>();//消息列表
    MessageAdapter adapter;
    Socket socket;
    String name;//聊天昵称
    BufferedReader reader;//读取服务器返回的数据
    BufferedWriter writer;//向服务器发送数据

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);
        initView();
    }

    private void initView() {

        name = getIntent().getStringExtra("name");
        socket = MainActivity.socket;

        btnSent = (ButtonFlat) findViewById(R.id.btn_sent);
        metMsg = (MaterialEditText) findViewById(R.id.met_msg);
        lvMsg = (ListView) findViewById(R.id.lv_msg);

        adapter = new MessageAdapter(this,msgList);
        lvMsg.setAdapter(adapter);

        //开启一个新的线程监听聊天室,并获取聊天室的消息
        getMessage();

        btnSent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                ChatMessage chatMessage = new ChatMessage();
                chatMessage.setType(2);
                chatMessage.setName(name);
                chatMessage.setMessage(metMsg.getText().toString());
                msgList.add(chatMessage);
                adapter.notifyDataSetChanged();

                String message = name + "," + metMsg.getText().toString();
                metMsg.setText("");
                setMessage(message);
            }
        });
    }
    /**
     * 发送消息
     * @param message
     */
    private void setMessage(String message) {
        try {
            if (writer != null) {
                writer.write(message + "\n");
                writer.flush();
            } else {
                Toast.makeText(ChatActivity.this, "你已经离开 YY 聊天室!", Toast.LENGTH_LONG).show();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 获取聊天室的消息
     */
    private void getMessage() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                    reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    String line = null;
                    while ((line = reader.readLine()) != null) {

                        ChatMessage chatMessage = new ChatMessage();
                        String[] temp = line.split(",");
                        System.out.print(temp[0]);
                        if(temp[0].equals("system")){//如果temp[0]=system说明是系统消息
                            chatMessage.setType(0);
                            chatMessage.setMessage(temp[1]);
                        }else{
                            chatMessage.setType(1);
                            chatMessage.setName(temp[0]);
                            chatMessage.setMessage(temp[1]);
                        }
                        msgList.add(chatMessage);
                        handler.sendEmptyMessage(0x00);
                    }
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            adapter.notifyDataSetChanged();
            return false;
        }
    });

}

注意

查看服务器 IP

  打开 cmd ,输入 ipconfig ,就可以看到本机的 IP 设置,在无线局域网适配 wifi 这里的 IPv4地址 就是服务器的 IP 了。

手机连不上服务器

  模拟器可以连上服务器,手机却连不上怎么办?网上说是手机是外网,访问不了内网的问题,手机和电脑连同一个 wifi 就行了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容