android socket 从零打造一个简单聊天室

使用socket打造一个聊天室,本人是个小白,过程中遇到的所有问题,我都会进行说明,一切面向和我一样的小白,毕竟大神也不会看这些,先看一看效果

所有代码和资料:点这里

资料说明:基于socket的聊天室,包括一个服务端,一个客户端代码,一个将客户端代码封装好后的app代码和apk文件,socket,https相关资料,网络基础知识资料,观察者模式资料

第一种:一个socket服务端,一个客户端,客户端上线,服务端有提示,会打印上线设备的ip,表示建立连接在这里插入图片描述

client客户端,发送一个消息,服务端会转发到所有设备,前面的数字是发送该信息的设备端口号,可以当id用
在这里插入图片描述
第二种将上面的客户端简单封装后,做成app,服务端不变,不要问我为什么一个大一个小,懒得改了…
在这里插入图片描述

一:好啦,正式开始,我会一步一步的完成这个小东西,以下内容仅适合小小白阅读,大神请直接看代码

1.本人刚学Android一个月左右,看完郭大神的第一行代码,觉得很不错,给五星好吧,不吹不黑,后来又买了Android 精彩编程200例,这个是有点坑,例子多,质量就不敢保证了,一般一个例子或许只有十几行代码,一笔带过,看完依然云里雾里,在这提醒大家,土豪略过。
我的初衷是做一个真正的翻版qq,这算是万里长征第一步吧,嘿嘿嘿嘿,整个代码异常简单,尤其是客户端
2.通过这个例子,你将会学到以下:

socket连接----异常------线程---------handler异步消息转发---------加深构造方法的理解 -------- getInputStream等流的操作 ----一个loding等待界面的动画------------观察者模式-----泛型等

二:上代码,代码为第一种方式,即服务端-客户端,app只是封装了以一下

服务端:服务端代码可以在Android studio,eclipse,java web端运行,其本质只是一段j基于java的独立程序 ,不拘泥与平台

思路:在TcpServer中开一个socket,通过阻塞循环获取收到的字节流信息传给clientTask处理,clientTask将收到的消息转成字符串并调用Msgpool进行分发,分发消息使用观察者模式,把消息传给所有连接上的设备,在Msqpool中每接到一个设备的消息就为其开一个线程,
结构:
TcpServer ------> socketl连接
clientTask -------> socket读取的消息进行处理,连接TcpServer和Msgpool
Msgpool ---------> 消息转发给各个客户端

TcpServer 由于代码较多,就不贴了,所有代码都有详细注释(见开头),已经上传

package com.example.tcpserver.tcpserver;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
    //方法带括号,属性不带括号
    public  void start(){
        //服务端是serversocket 客户端是socket
         ServerSocket serverSocket = null;
        try {
            //服务端只需要开一个端口即可,网络服务属于耗时操作,需在线程里进行,以免影响其他任务,
            // 但是这里服务器只进行网络的监控这一个操作,所以可以省略
            serverSocket = new ServerSocket(9100);
            //Msgpool主要负责消息分发给各个客户端,这里是启动Msgpool
            Msgpool.getInstance().start();
            //循环监听端口,一有消息立即丢给clientTask进行处理,然后继续监听,避免连接上一个设备后,
            // 其余设备无法连接,端口被占用的情况
            while (true){
                //端口堵塞,会一直卡在这,直到有消息,丢给clientTask后继续堵在这
                Socket socket = serverSocket.accept();
                //系统打印连接者的ip和端口号
                System.out.println("ip:"+socket.getInetAddress().getHostAddress()+"port:"
                        +socket.getPort()+"is online..");
                //clientTask负责处理接受到的信息
               ClientTask clientTask = new ClientTask(socket);
               //将处理好的消息丢给Msgpool进行转发
               Msgpool.getInstance().addMsgConingListener(clientTask);
               clientTask.start();
            }

        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }
    public static void main(String[] args) {
        new TcpServer().start();
    }

}

客户端:借鉴了mvp模式的一点精髓,逻辑,界面,活动分开

TCPActivity:
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;  //发送按钮
import android.widget.EditText;  //文本框
import android.widget.ProgressBar; //圆形进度条,用来当loding加载过程
import android.widget.TextView;  //消息显示区
import android.text.TextUtils;

import com.example.myapplication.tcpclient.TcpClientApp;


public class TCPActivity extends AppCompatActivity {
    private EditText et;
    private TextView tv;
    private Button bt;
    private ProgressBar loding;
    private TextView tv_loding;

    private TcpClientApp tcpClientApp = new TcpClientApp();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initview(); //控件初始化

        tcpClientApp.setOnMsgComingListener(new TcpClientApp.OnMsgComingListener() {
            @Override
            public void onMsgComing(String msg) {
                appendMsgToContent(msg);
            }
            @Override
            public void onError(Exception e) {
                e.printStackTrace();
            }

            @Override//是否连接的handle
            public void uiupDate(String msg) {
                upDate(msg);
            }


        });


        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String msg = et.getText().toString();
                if(TextUtils.isEmpty(msg)){ //一个工具类,进行字符串处理,判断是否为空
                    return;
                }
                tv.setText("");//清空界面

                tcpClientApp.sendMsg(msg);
            }
        });

    }

    private void upDate(String msg) {
        if(msg == "wait"){
            loding.setVisibility(View.VISIBLE); //设置控件的显示和隐藏
            tv_loding.setVisibility(View.VISIBLE);
        }
        if(msg == "kkk"){
            tv.append("kkk"); //测试,不用管
        }
        if(msg == "content"){
            loding.setVisibility(View.GONE);
            tv_loding.setVisibility(View.GONE);
            tv.append("连接成功!");
        }
    }

    private void appendMsgToContent(String msg){
        tv.append(msg+"\n"); //向ui更新消息
    }
    private void initview(){
        et = findViewById(R.id.et_msg);
        tv = findViewById(R.id.tv_msg);
        bt = findViewById(R.id.bt_send);
        loding = findViewById(R.id.loging);
        tv_loding = findViewById(R.id.tv_loding);
    }

    protected void onDestroy(){
        super.onDestroy();
        tcpClientApp.onDestroy();
    }
}

TcpClientApp:
package com.example.myapplication.tcpclient;



import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.ProgressBar;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;


public class TcpClientApp {


    private Socket msocket;
    private InputStream ls;
    private OutputStream os;

    private Handler mUiHandler = new Handler(Looper.getMainLooper());
    private Handler waithandler = new Handler(Looper.getMainLooper());

    private static final String TAG = "TcpClientApp";
    //接口,在活动中实例化
    public interface OnMsgComingListener{
        void onMsgComing(String msg);
        void onError(Exception e);
        void uiupDate(String msg);
    }
    private OnMsgComingListener mlistener;
    public void setOnMsgComingListener(OnMsgComingListener listener){
        mlistener = listener;
    }


    public TcpClientApp() { //构造方法,和类名一致
        //一定要在线程中进行,网络连接属于耗时操作
        //可以看见,socket连接前后设置了两个检测的handler,因为这里socket是异步的
        //就是说,在我没有连接成功的时候,用下面的sendmsg发送消息会报空指针异常
        //所以通过handle将是否已经连接这一信息传给主线程,并在ui界面更新显示
        new Thread(){
            @Override
            public void run() {
                try {
                    waithandler.post(new Runnable() {
                        @Override
                        public void run() {
                            //handler消息机制 向主程序发送wait标识正在连接
                            mlistener.uiupDate("wait");
                            // 在这里执行你要想的操作 比如直接在这里更新ui或者调用回调在 在回调中更新ui
                        }
                    });
                    msocket = new Socket("10.4.2.107", 9100);
                    ls = msocket.getInputStream();
                    os = msocket.getOutputStream();
                    waithandler.post(new Runnable() {
                        @Override
                        public void run() {
                            //连接成功发送content,
                            mlistener.uiupDate("content");
                        }
                    });
                    readServerMsg();
                }catch (final IOException e){
                    mUiHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            if (mlistener != null) {
                                mlistener.onError(e);
                            }
                        }
                    });
                    //handle消息
                }
            }
        }.start();
    }
    private void readServerMsg() throws IOException {
        final BufferedReader br = new BufferedReader(new InputStreamReader(ls));

        String line = null;
        while ((line = br.readLine()) != null) {
            final String finalLine = line;
            //handle消息
            mUiHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (mlistener != null) {
                        Log.d(TAG, "接受到消息并转发到ui线程...");
                        mlistener.onMsgComing(finalLine);
                    }
                }
            });
        }
    }

    public void sendMsg(final String msg){

        new Thread(){
            @Override
            public void run() {
                try {
                    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));

                    bw.write(msg);
                    bw.newLine();
                    bw.flush();
                } catch (IOException e) {
                    e.printStackTrace();
            }
            }
        }.start();
    }
    public void onDestroy(){
        //所有异常分开保证一个出现问题,其余的可以正常关掉
        if(os != null){
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }if(ls != null){
            try {
                ls.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(msocket != null){
            try {
                msocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}


最后:借鉴资料:
https://www.cnblogs.com/tonglingqijie/p/4753405.html 控件的显示和隐藏
https://blog.csdn.net/m0_37705108/article/details/82993433 子线程更新UI
https://blog.csdn.net/fengye810130/article/details/9102263 TextUtil工具类介绍
https://blog.csdn.net/wait_for_eva/article/details/86557930 android studio开启多个客户端解决方法
注意点:运行时如果连接不上请关闭防火墙,不要问我怎么知道的,都是泪

发布了23 篇原创文章 · 获赞 2 · 访问量 1201

猜你喜欢

转载自blog.csdn.net/qq_42733641/article/details/102696472