【Android-Socket】Socket通信笔记(单例模式,线程管理,AsyncTask)

  • Post author:
  • Post category:其他




扉:

  1. 本作学习视频来源:https://www.bilibili.com/video/BV1Nx411r7Pr?t=940&p=11
  2. 界面参考: https://blog.csdn.net/fszeng2011/article/details/42743323?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1

  3. Sock定义与方法详解参考

  4. Socket详解

  5. TCP与UDP区别

  6. 点击跳转百度网盘

    提取码:pgfj

  7. 连接时请注意端口号和查看本机IP


    1)cmd ipconfig

    2)点击网络查看属性
  8. TestMySocket为服务器端(Java开发) MySocketClient为安卓端(Kotlin开发)

  9. 定义

    :是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述



一:首先在浏览器输入127.0.0.1

  1. 若无法访问 ping这个地址看能不能ping通 。若能通的话则看看IIS开了没有
  2. 开启互联网信息服务

    IIS


    在这里插入图片描述



二: 代码块



1. (创建本地服务器一)最简单的例子

  1. 建立Java项目

    在这里插入图片描述
  2. 核心:建立ServerSocket对象并使用accept()建立连接,建立后来个弹窗

    1)建立服务端的socket

    2)接收socket对象建立连接 搞定
public class MyServerSocket {
    public static void  main(String[] args){
        System.out.println("Hello, World!");
        //port 1-65535
        try{
            ServerSocket serverSocket=new ServerSocket(12345);
            //accept是个阻塞的方法 会阻塞当前线程 返回socket类型
            Socket socket=serverSocket.accept();
            //建立连接
            JOptionPane.showMessageDialog(null,"有客户机连接到了本机的12345端口");
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

Kotlin写法

object MyServerSocket {

    fun main(args: Array<String>) {
        println("Hello, World!")
        //port 1-65535
        try {
            val serverSocket = ServerSocket(12345)
            //accept是个阻塞的方法 会阻塞当前线程 返回socket类型
            val socket = serverSocket.accept()//接收客户端请求
            //建立连接
            JOptionPane.showMessageDialog(null, "有客户机连接到了本机的12345端口")
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}
  1. 点击运行后,访问127.0.0.1:12345 是没有界面的,但可以看到。关闭窗体后 程序自动关闭

    在这里插入图片描述

    在这里插入图片描述



2. (创建本地服务器二) 解决线程阻塞问题,完成服务器向可客户端发送数据的功能

  1. 建立ServerListener类继承Thread

    1)复写run方法

    2)其中循环监听,循环返回新接入的socket对象

    3)每当有新的accept进入接起连接,就再次 new一个线程进行通话管理
public class ServerListener extends Thread {
    @Override
    public void run() {
        System.out.println("Hello, World!");
        //port 1-65535
        try{
            ServerSocket serverSocket=new ServerSocket(12345);
            //accept是个阻塞的方法 会阻塞当前线程 返回socket类型
            //每当有客户端连接 accept都会返回一个socket对象 循环接听客户端的连接
            while(true){
                Socket socket=serverSocket.accept();
                //建立连接
                JOptionPane.showMessageDialog(null,"有客户机连接到了本机的12345端口");
                //每个socket又要与每个独立的客户端进行通信 所以要将socket传递给新的线程
                new ChatSocket(socket).start();//循环监听客户端请求
            }

        }catch (
                IOException e){
            e.printStackTrace();
        }
    }
}
  1. 新建ChatSocker用于单独处理每个socket

    1)Linux 不区分套接字文件和普通文件,使用 write() 可以向套接字中写入数据,使用 read() 可以从套接字中读取数据。两台计算机之间的通信相当于两个套接字之间的通信,在服务器端用 write() 向套接字写入数据,客户端就能收到,然后再使用 read() 从套接字中读取出来,就完成了一次通信。

public class ChatSocket extends Thread {

    Socket socket;
    public ChatSocket(Socket s){
        this.socket=s;
    }
    //写回方法
    public void out(String out) throws IOException {
        socket.getOutputStream().write(out.getBytes("UTF-8"));
    }

    @Override
    public void run() {
        int count=0;
        while (true){
            count++;
            try {
                out("loop"+count);
                sleep(1000);
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

1)运行多个窗体访问

2)

telnet localhost 12345


3)多个线程不同,每个分配独立的chatsocket线程,所以比如要管理线程

在这里插入图片描述

在这里插入图片描述



3. (创建本地服务器完成)让多个终端同步显示,建立管理线程类

  1. 测试发现 左上角有特化图标的输入不会显示出来字,但可以发送 类似保密
  2. 而非特化的输入可以看到也可以发送。

    1)private私有化构造方法

    2)建立单例返回cm对象

    3)使用Vector报错每个线程对象

    4)使用方法publish发送信息,for循环向每个发送(输出流,不存缓冲区直接显示的)

    代码一:建立管理类

    5)补充Kotlin的Vector写法

    在这里插入图片描述

public class ChatManager {
    //私有化构造方法 单例化
    private ChatManager() { }

    private static final ChatManager cm = new ChatManager();

    public static ChatManager getChatManaget() {
        return cm;
    }

    Vector<ChatSocket> vector = new Vector<ChatSocket>();

    public void add(ChatSocket cs) {
        vector.add(cs);
    }

    //某个线程向所有客户端发送信息
    public void publish(ChatSocket cs, String out) throws IOException {
        System.out.print(out);
        for (int i = 0; i < vector.size(); i++) {
            System.out.println("当前cs"+cs);
            ChatSocket csChatSoccket = vector.get(i);

            if (!cs.equals(csChatSoccket)){
                String msg = "";
                msg += out + "\n";//加个换行 不然安卓收不到 太坑了
                csChatSoccket.out(msg);
            }
        }
    }
}

代码二:建立了解后单独保存并且封入管理类

public class ServerListener extends Thread {
    @Override
    public void run() {
        System.out.println("Hello, World!");
        //port 1-65535
        try{
            ServerSocket serverSocket=new ServerSocket(12345);
            //accept是个阻塞的方法 会阻塞当前线程 返回socket类型
            //每当有客户端连接 accept都会返回一个socket对象 循环接听客户端的连接
            while(true){
                Socket socket=serverSocket.accept();
                //建立连接
                JOptionPane.showMessageDialog(null,"有客户机连接到了本机的12345端口");
                //每个socket又要与每个独立的客户端进行通信 所以要将socket传递给新的线程
                /*更新1.
                匿名方式直接创建 改为命名方式(赋个值 然后可以被调用)
                new ChatSocket(socket).start();//循环监听客户端请求*/
                ChatSocket cs=new ChatSocket(socket);
                cs.start();
                ChatManager.getChatManaget().add(cs);
            }

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

代码三:读入缓冲区的内容并发送消息给终端


public class ChatSocket extends Thread {

    Socket socket;
    public ChatSocket(Socket s){
        this.socket=s;
    }
    public void out(String out) throws IOException {
        socket.getOutputStream().write(out.getBytes("UTF-8"));
    }

    @Override
    public void run() {
//        int count=0;
/*
* 修改2. 服务器循环读取消息再发送给终端
* */
            try {
                BufferedReader br=new BufferedReader(
                        new InputStreamReader(
                                socket.getInputStream(),"UTF-8"));
                String line=null;
                while ((line=br.readLine())!=null) {
                    ChatManager.getChatManaget().publish(this,line);
                }
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //count++;
            /*try {
                out("loop"+count);
                sleep(1000);
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }*/

    }
}

在这里插入图片描述

在这里插入图片描述

关闭一个窗口后后,若未remove数组中的东西 ,则会报错(1. socked

关闭报错 2.读写流关闭报错(安卓))。每次连接的这个弹窗提示要点掉,不然会阻塞线程。

在这里插入图片描述

在这里插入图片描述



4. 安卓代码

  1. 给权限
   <uses-permission android:name="android.permission.INTERNET" />
  1. AsyncTask防止弃用修改版本
    compileSdkVersion 29
    buildToolsVersion "29.0.2"

class MainActivity : AppCompatActivity(), View.OnClickListener {


    companion object {
        const val KEY = "key"
        const val TAG = "My Phone"
    }

    //创建一个socket
    private var socket: Socket? = null

    //创建输入输出流
    private var writer: BufferedWriter? = null
    private var reader: BufferedReader? = null

    private var socketAddress: SocketAddress? = null
    private var isConnected = false
    private var sp: SharedPreferences? = null
    private var currentName: String? = null
    private var connectFailed = false
    private var progressDialog: ProgressDialog? = null
    private var task: AsyncTask<Void?, String?, Void?>? = null

    val currentPosition by lazy { StringBuilder() }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        init()


    }

    private fun init() {

        btnConnect.setOnClickListener(this)
        btnSend.setOnClickListener(this)
        btnSetName.setOnClickListener(this)

        tvConnectState!!.text = "连接状态:未连接"
        //初始化sharepreference
        sp = getPreferences(MODE_PRIVATE)
        sp?.let {
            currentName = it.getString(MainActivity.Companion.KEY, null)
        }
        if (currentName != null) {
            tvCurrentName!!.text = "当前昵称:$currentName"
        }
    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.btnConnect ->
                if (currentName != null) {
                    connect()
                } else {
                    Toast.makeText(this, "请先设置昵称后重新连接", Toast.LENGTH_SHORT).show()
                }
            R.id.btnSend -> send()
            R.id.btnSetName -> {
                val tmp = etSetName!!.text.toString().trim { it <= ' ' }
                setName(tmp)
            }
        }
    }


    //    ==================================== 逻辑相关
    private fun setName(s: String) {
        if (s != "") {
            val editor = sp!!.edit()
            editor.putString(MainActivity.Companion.KEY, s)
            if (editor.commit()) {
                tvCurrentName!!.text = "当前昵称:$s"
                currentName = s
                etSetName!!.setText("")
                Toast.makeText(this, "昵称设置成功", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "昵称设置失败,请重试", Toast.LENGTH_SHORT).show()
            }
        } else {
            Toast.makeText(this, "昵称不能为空", Toast.LENGTH_SHORT).show()
        }
    }


    private fun connect() {
        progressDialog = ProgressDialog.show(this," 连接状况", "正在连接...")
        var read: AsyncTask<Void, String, Void> = @SuppressLint("StaticFieldLeak")
        object : AsyncTask<Void, String, Void>() {

            @SuppressLint("WrongThread")
            override fun doInBackground(vararg params: Void?): Void? {


                //把这些放Asynctask中更好  不允许在主线程连接
                val ip = etIP!!.text.toString()
                val port = Integer.valueOf(etPort!!.text.toString())

                //socket = java.net.Socket(ip, port)//Socket(etIP!!.text,)
                //设置最长连接时间
                socket= Socket()
                socketAddress = InetSocketAddress(ip, port)

                socket?.let {
                    it.connect(socketAddress,3000)
                    //包装Buffererd
                    writer = BufferedWriter(OutputStreamWriter(it.getOutputStream()))
                    reader = BufferedReader(InputStreamReader(it.getInputStream()))
                    //连接成功就发送这个
                    publishProgress("@success")
                }


                var line: String? = null
                Log.e(TAG, "doInBackground: Start$line")

                //就是line不为null
                while (reader!!.readLine().also({ line = it }) != null) {
                    Log.e(TAG, "doInBackground: 进入循环$line" )
                    publishProgress(line)
                }
                Log.e(TAG, "doInBackground: 退出循环$line" )

                return null
            }

            override fun onProgressUpdate(vararg values: String?) {


                //这里没做连接失败的反馈  相加自己加个glag
                if (values[0].equals("@success")){
                    tvConnectState!!.text = "连接状态:已连接"
                    Toast.makeText(this@MainActivity, "连接成功", Toast.LENGTH_SHORT).show()
                    progressDialog!!.dismiss()
                }
                //发布values的第一个对象
                Log.e(TAG, "onProgressUpdate: Start$values[0]" )

                currentPosition.append("别人说:==${values[0]}"+"\n")
                tvChatRecord?.text=currentPosition
                super.onProgressUpdate(*values)

            }
        }

        read.execute()
    }



    //不涉及联网操作 放主线程应该没问题 有问题 flush
    private fun send() {
        Thread(Runnable {
            //一定要追加换行符
            currentPosition.append("我说:${etMassage.text}").append("\n")
            writer?.write(etMassage.text.toString() + "\n")
            writer?.flush()

            runOnUiThread {
                etMassage!!.setText("")
                tvChatRecord?.text = currentPosition
            }
//数据强制输出  安卓反复开启  服务器会有混淆 报closed错误

        }).start()

    }

    override fun onDestroy() {
        writer?.close()
        reader?.close()
        super.onDestroy()
    }
}

在这里插入图片描述



5.调试过程和同步更新机制

  1. 第一个阶段,完成连接发送更新UI消息,之后便进入了While循环,从断点我们知道一进入就不会退出循环了(哪怕刚开始取到空,

    只是单纯的 不执行代码

    这种类似以起c++ !=EOF输入CTRL+Z才退出循环,一直在循环中 发现输入流有东西(其他端实现)就直接通知UI更新)

    在这里插入图片描述

  2. 实现我发的就是我说,其他人发的就其他人说:这个比较简直自己发的直接获取为我说,AsyncTask中接到的所有消息都是他说

  3. 发送消息非常重要加换行符,估计是侦测借位换行符的,亲测结尾加上两个换行符也达不到换行效果。

    在这里插入图片描述

    在这里插入图片描述

  4. 打断点暂时测不出来关闭连接后的 情况

    在这里插入图片描述



6. 补充AsyncTask 30弃用之前

在这里插入图片描述

在这里插入图片描述



版权声明:本文为qq_38304672原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。