当前位置: 代码迷 >> 综合 >> 【Android-Socket】Socket通信笔记(单例模式,线程管理,AsyncTask)
  详细解决方案

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

热度:26   发布时间:2023-12-15 11:09:54.0

扉:

  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-65535try{
    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-65535try {
    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 {
    @Overridepublic void run() {
    System.out.println("Hello, World!");//port 1-65535try{
    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"));}@Overridepublic 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 {
    @Overridepublic void run() {
    System.out.println("Hello, World!");//port 1-65535try{
    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"));}@Overridepublic 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 29buildToolsVersion "29.0.2"
class MainActivity : AppCompatActivity(), View.OnClickListener {
    companion object {
    const val KEY = "key"const val TAG = "My Phone"}//创建一个socketprivate var socket: Socket? = null//创建输入输出流private var writer: BufferedWriter? = nullprivate var reader: BufferedReader? = nullprivate var socketAddress: SocketAddress? = nullprivate var isConnected = falseprivate var sp: SharedPreferences? = nullprivate var currentName: String? = nullprivate var connectFailed = falseprivate var progressDialog: ProgressDialog? = nullprivate var task: AsyncTask<Void?, String?, Void?>? = nullval 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 = "连接状态:未连接"//初始化sharepreferencesp = 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 = setSetName!!.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)//包装Buffererdwriter = BufferedWriter(OutputStreamWriter(it.getOutputStream()))reader = BufferedReader(InputStreamReader(it.getInputStream()))//连接成功就发送这个publishProgress("@success")}var line: String? = nullLog.e(TAG, "doInBackground: Start$line")//就是line不为nullwhile (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?) {
    //这里没做连接失败的反馈 相加自己加个glagif (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=currentPositionsuper.onProgressUpdate(*values)}}read.execute()}//不涉及联网操作 放主线程应该没问题 有问题 flushprivate 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弃用之前

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

  相关解决方案