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

2023-05-16

扉:

  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弃用之前

在这里插入图片描述
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 二、Redis安装配置(云服务器、vmware本地虚拟机)

    一 自己购买服务器 自己购买阿里云 青牛云 腾讯云或华为云服务器 xff0c 自带CentoOS或者Ubuntu环境 xff0c 直接开干 二 Vmware本地虚拟机安装 1 VMWare虚拟机的安装 xff0c 不讲解 xff0c 默认懂
  • 【MySQL基础】数据类型

    文章目录 整数类型浮点类型定点数类型日期和时间类型字符串类型文本类型二进制字符串类型JSON 类型位类型ENUM类型SET类型空间类型 整数类型 整数类型一共有 5 种 xff0c 包括 TINYINT SMALLINT MEDIUMINT
  • ubuntu16.04备份和迁移

    ubuntu16 04备份和迁移 背景实践1 备份整个系统2 重装Ubuntu16 043 恢复系统 题外话 xff1a 修改主机名参考文章 背景 此文用来快速记录备份和恢复的过程步骤 xff0c 具体命令意思不做过多介绍 因为不想新设备重
  • c++20协程基础概念

    c 43 43 协程介绍 前言 官方文档地址 本文主要对c 43 43 reference做翻译 不会逐字翻译 xff0c 同时对其中的概念以及协程运行过程做对应的解释 因为是学习过程中的记录 xff0c 如有问题 xff0c 希望大家能够
  • Flask 与 Django 框架对比

    详细分析了两种 Python Web框架 xff1a Flask 与 Django 从开发难易度 应用架构 性能 可扩展性以及适用范围等方面进行了详细说明 Django 中级教程在 B 站上线 xff0c 深入解析 Django 体系架构
  • STM32F103C8T6基础开发教程(HAL库)—点亮第一颗LED灯

    STM32F103C8T6基础开发教程目录 STM32F103C8T6基础开发教程 xff08 HAL库 xff09 开发环境配置STM32F103C8T6基础开发教程 xff08 HAL库 xff09 Keil添加注释的快捷键STM32F
  • C++实现插入排序算法(直接插入排序、折半插入排序、希尔排序)

    排序算法分为五大类 xff0c 一共是有九种 xff0c 如下 xff1a 插入类 xff1a 直接插入排序 折半插入排序 希尔排序 交换类 xff1a 冒泡排序 快速排序 选择类 xff1a 简单选择排序 堆排序 归并类 xff1a 二路
  • C++实现二路归并排序算法

    排序算法分为五大类 xff0c 一共是有九种 xff0c 如下 xff1a 插入类 xff1a 直接插入排序 折半插入排序 希尔排序 交换类 xff1a 冒泡排序 快速排序 选择类 xff1a 简单选择排序 堆排序 归并类 xff1a 二路
  • C语言实现-学生信息管理系统

    通过C语言实现一个学生信息管理系统 xff0c 要求如下 xff1a xff08 1 xff09 用户采用自己账号和密码登录系统 xff1b xff08 2 xff09 学生信息和账号密码通过文件的形式存储 xff1b xff08 3 xf
  • 通过python画矢量图(matplotlib,有代码)

    python画矢量图 xff08 有代码 xff09 python的matplotlib可以保存的文件格式word可以插入哪些图片格式呢代码中文乱码问题 有些同学因为文章的要求 xff0c 图片插入到word里的时候需要足够清晰 xff0c
  • Java实现LRU

    首先看看什么是LRU LRU是Least Recently Used的缩写 xff0c 即最近最少使用 xff0c 是一种常用的页面置换算法 xff0c 选择最近最久未使用的页面予以淘汰 该算法赋予每个页面一个访问字段 xff0c 用来记录
  • 域名cdn加速(apache与nginx)

    一 xff1a 由于公司业务属于请求量比较大的吧 xff0c 每个月几亿条 xff0c 考虑到安全性 xff0c 所以需要域名由http改为https cdn加速才可以支成撑业务 二 xff1a 之前的系统是使用lamp配置的 xff0c
  • ubuntu系统安装完nvidia显卡驱动后黑屏,不能进入系统

    我之前安装了系统里建议安装的nvidia 380显卡驱动 xff0c 为了安装更高版本的CUDA xff0c 我将nvidia显卡驱动升级到了430 xff0c 但是重启电脑进入Ubuntu系统时黑屏 xff0c 进不去系统界面 xff0c
  • 黑盒模糊测试之AFL++

    git clone depth 1 https github com AFLplusplus AFLplusplus cd AFLplusplus make Build Summary 43 afl fuzz and supporting
  • centos7安装MySQL5.7

    一 下载mysql5 7 1 下载地址 mysql 5 7 28 1 el7 x86 64 rpm bundle tar 2 上传至服务器 3 解压压缩包 解压命令 tar xvf mysql 5 7 28 1 el7 x86 64 rpm
  • 打包VSCode源码为安装程序(.exe)

    参考博客 GitHub vscode里的Packaging部分 xff1a https github com microsoft vscode wiki How to Contribute 这里提供了vscode打包后可以发布的平台代码 x
  • Ubuntu的安装卡在安装界面 (解决方法记录)

    安装过程 在 Install Ubuntu 的grub 选项上点击e 在 Linux 系统的启动参数 倒数第二行 中加入 nomodset 安装界面分辨率会有问题 xff0c 但这个之后再解决 安装过程中 xff0c 如果需要拖拽窗口 Al
  • 树莓派ubuntuMATE 安装xrdp来进行显示图形的远程调试

    树莓派ubuntuMATE 安装xrdp来进行显示图形的远程调试 引 在树莓派上调试显示图形界面的项目 xff0c 纠结配显示器的问题 pi本身有hdmi的接口 xff0c 如果有多余的显示器直接连接的那最好 xff0c 倘若接口不合适也可
  • C与C++源文件的拼接

    C 43 43 与C处理函数名 如果C 43 43 两个cpp源文件中函数名称相同 xff0c 会出现如下错误 xff08 ave就是函数名 xff09 34 int cdecl ave void 34 ave 64 64 YAHXZ 已经
  • 【Android-Socket】Socket通信笔记(单例模式,线程管理,AsyncTask)

    扉 本作学习视频来源 https www bilibili com video BV1Nx411r7Pr t 61 940 amp p 61 11界面参考 https blog csdn net fszeng2011 article det