zoukankan      html  css  js  c++  java
  • Android7.1 与 PC端 套接字 socket Tcp/Ip 通信(多线程)

    最近有点闲,打算通过 socket 实现 PC 调用手机摄像头,实现实时视频传输。搞到后来发现搞不了, java 和 c++ 的基本数据类型就有差别,要经过转换才行,自然二者的套接字也是不能直接传数据的。我在手机上运行自己写的客户端,PC 上运行C++开发的服务端,能连接上但是传输不了数据(不能直接传输)。所以不搞了,太麻烦。。。是我太菜哈哈哈哈哈,,,,,

    不过还是把最后结果在这里分享一下,因为这个过程那叫个坎坷哦,网上的确能找到很多这方面的博客什么的,但是没有一个能跑的通的代码,不知道是不是Android版本不同导致的(参考的那些帖也没有给出开发环境),反正我手机上就是运行不了(确切说是能运行,但发不了数据,老是崩溃), 花了  ≈Android小白 的我好几天时间才发现问题所在,没错!是的!!线程的问题!!!

    由于参考了不知道多少个博客和帖子,所以这里不一一列出了,说实话我自己都记不清看(抄)了多少代码了。。。那些代码大部分都是开了一共两个线程,即一个主线程用于UI显示,一个子线程用于建立 socket 连接并传送数据。但我在我手机上运行时总是崩溃,具体表现为:UI能正常显示;能通过socket建立连接;不能发送数据,一点击发送按钮,程序就闪退。开始我以为是我哪里设置的不对,后来搞了好几天,把代码改的面目全非,结构都变了,还是不行,而且不知道是客户端的问题还是服务器的问题。

    后来呢,柳暗花明,机智(愚蠢)的我终于还是弄清了原因:

    (1). 客户端的问题;

    (2). 一个线程中不能建立连接的同时还发送数据。

    那怎么办呢?改客户端代码呗,子线程再建子线程用于发送数据。

    下面进入正题,首先给出我开发环境:

    IDE: Android Studio 2.3.3

    API: android-25(Android 7.1)

    JDK: 1.8.0

    建立 AS 工程时,我设置的最低SDK支持版本是 24(Android 7.0),我的手机是基于 Android 7.1 的系统,是直接用手机连接电脑调试的,没有用虚拟设备。

    原始代码是从网上某个博主哪里找来的,不过客户端已经被我改的面目全非了,除了按钮的名字,差不多看不到原来的样子了。。服务端倒是没怎么改。

    多线程我是用线程池实现的,因此不了解线程池的小伙伴自己先去了解一下,相比于普通的多线程,线程池有很多优点(避免重复开辟新线程,节省资源啥的...)。多线程有很多坑,比如某些东西不能放在某些地方,我也是踩了很多,一遍遍试过来的,程序里面我也注释了一点。

    1. 程序结构、功能、界面

    程序开始运行,设置UI界面,此时运行的是主线程。

    然后,主线程新建一个线程,用于建立 socket 连接。连接成功后最上面的地方会显示服务器端发送的消息(类似于“服务器/192.168.0.101加入;此时总连接:1(服务器发送)”),如果显示的是“TCP-IP”(即项目名称),就说明没连上。

    然后在新线程中监听按钮,当按钮被按下时,再新开子线程用于发送数据。

    界面布局如下:

    2. 代码

    一共在 AS 下建立了两个工程,用两个 AS 窗口分别显示。

    一个工程名为 Tcp-Ip,这个工程为客户端程序,运行在手机端,是一个正常的 Android 工程。工程目录如图:

    另一个工程名为 Tcp-Ip-Server,这个工程为服务端程序,运行在PC端,是一个空的 Android 工程,里面添加了名为 TCPServer 的模块(Java Library型的module),运行的时候也是只运行这个模块(详情自己百度,就是在 Android Studio 下开发普通java程序)。工程目录如图:

     

    客户端

     AndroidManifest.xml,可以参考一下,主要是添加权限:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.gao.tcp_ip">
    
        <uses-permission android:name="android.permission.INTERNET"/>
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".SocketActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>

    SocketActivity.java,主程序:

    package com.example.gao.tcp_ip;
    
    import android.os.Handler;
    import android.os.Message;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class SocketActivity extends AppCompatActivity {
    
        public TextView tv_msg = null;
        public EditText ed_msg = null;
        public Button btn_send = null;
        public Button btn_break = null;
        public String content = "";
        public String temp = "";
        private ExecutorService SocketService = Executors.newSingleThreadExecutor(); //创建单线程池,用于套接字连接
    
        // 接收线程发送过来信息,并用TextView显示
        public Handler mHandler = new Handler() {
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                tv_msg.setText(content);
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_socket);
    
            tv_msg = (TextView) findViewById(R.id.tv_contents);
            ed_msg = (EditText) findViewById(R.id.et_content);
            btn_send = (Button) findViewById(R.id.bt_sender);
            btn_break = (Button) findViewById(R.id.bt_break);
    
            SocketService.execute(socketService);  // 在线程池中开启新线程socketService
        }
    
        /// 定义Runnable接口变量socketService完成的功能
        Runnable socketService = new Runnable(){
            // Socket变量不能定义在run中,否则不能在按钮响应函数中调用,BufferedReader、PrintWriter好像可以
            private Socket socket = null;
            private BufferedReader in = null;
            private PrintWriter out = null;
            private static final String HOST = "192.168.0.100";
            private static final int PORT = 6666;
            private String msg;
            private ExecutorService SendService = Executors.newSingleThreadExecutor(); //创建单线程池,用于发送数据
    
            @Override
            public void run(){
                try {
                    //建立套接字连接,获取输入输出流
                    socket = new Socket(HOST, PORT);
                    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
    
                    while (true) {
                        // 通过输入流从服务器获取数据并发送给主线程
                        if (!socket.isClosed() && socket.isConnected() && !socket.isInputShutdown()) {
                            if ((temp = in.readLine()) != null) {
                                content += temp + "
    ";
                                mHandler.sendMessage(mHandler.obtainMessage());
                            }
                        }
    
                        //获取编辑框内容并开启新线程发送
                        btn_send.setOnClickListener( new OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                msg = ed_msg.getText().toString();              //获取发送数据
                                ed_msg.getText().clear();
                                if (socket.isConnected() && !socket.isOutputShutdown()) {
                                    if(out != null) {
                                        SendService.execute(sendService);       //开启发送数据线程
                                    }
                                }
                            }
                        });
    
                        // 开启新线程发送"exit"信号
                        btn_break.setOnClickListener( new OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                msg = "exit";                                   //定义发送数据
                                if (socket.isConnected() && !socket.isOutputShutdown()) {
                                    if(out != null) {
                                        SendService.execute(sendService);       //开启发送数据线程
                                    }
                                }
                            }
                        });
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            //定义发送消息接口
            Runnable sendService = new Runnable(){
                @Override
                public void run() {
                    out.println(msg);   //通过输出流发送数据
                }
            };
        };
    
        public void exit(View view){
            finish();
        }
    }

    activity_socket.xml,布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.gao.tcp_ip.SocketActivity">
    
        <TextView
            android:id="@+id/tv_contents"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:text="@string/app_name"
            tools:layout_constraintTop_creator="1"
            tools:layout_constraintRight_creator="1"
            tools:layout_constraintBottom_creator="1"
            app:layout_constraintBottom_toTopOf="@+id/et_content"
            android:layout_marginStart="2dp"
            android:layout_marginEnd="2dp"
            app:layout_constraintRight_toRightOf="parent"
            tools:layout_constraintLeft_creator="1"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0" />
    
        <EditText
            android:id="@+id/et_content"
            android:layout_width="0dp"
            android:layout_height="54dp"
            android:inputType="text"
            tools:layout_constraintTop_creator="1"
            tools:layout_constraintRight_creator="1"
            android:layout_marginStart="2dp"
            android:layout_marginEnd="2dp"
            app:layout_constraintRight_toRightOf="parent"
            android:layout_marginTop="194dp"
            tools:layout_constraintLeft_creator="1"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            tools:layout_constraintTop_creator="1"
            tools:layout_constraintLeft_creator="1"
            app:layout_constraintLeft_toLeftOf="@+id/tv_contents"
            app:layout_constraintTop_toTopOf="parent"
            android:id="@+id/linearLayout">
    
        </LinearLayout>
    
        <Button
            android:id="@+id/bt_sender"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发送"
            android:layout_marginStart="23dp"
            tools:layout_constraintTop_creator="1"
            app:layout_constraintTop_toBottomOf="@+id/et_content"
            tools:layout_constraintLeft_creator="1"
            app:layout_constraintLeft_toLeftOf="@+id/et_content" />
    
        <Button
            android:id="@+id/bt_break"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="断开连接"
            android:layout_marginStart="35dp"
            tools:layout_constraintTop_creator="1"
            app:layout_constraintTop_toBottomOf="@+id/et_content"
            tools:layout_constraintLeft_creator="1"
            app:layout_constraintLeft_toRightOf="@+id/bt_sender" />
    
        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="退出"
            android:onClick="exit"
            android:layout_marginEnd="13dp"
            tools:layout_constraintTop_creator="1"
            tools:layout_constraintRight_creator="1"
            app:layout_constraintRight_toRightOf="@+id/et_content"
            app:layout_constraintTop_toBottomOf="@+id/et_content" />
    
    </android.support.constraint.ConstraintLayout>

     

    服务端

    相比于客户端,服务器端的代码我没有怎么改(因为它能直接用),就是差不多直接用的别人的博客里的,具体哪个忘了。。。。

    由于运行的是 java 模块(MainActivity和activity_main.xml压根就没用),所以我只贴模块的代码。

    TCPServer.java:

    package com.example;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class TCPServer {
        private static final int PORT = 6666;
        private List<Socket> mList = new ArrayList<>();
        private ServerSocket server = null;
        private ExecutorService mExecutorService = null; // 创建一个线程池
    
        public static void main(String[] args) {
            new TCPServer();
        }
    
        public TCPServer() {
            try {
                server = new ServerSocket(PORT);
                mExecutorService = Executors.newCachedThreadPool(); // 实例化一个线程池
                System.out.println("服务器已启动,等待加入...");
                Socket client = null;
                while (true) {
                    client = server.accept();
                    // 把客户端放入客户端集合中
                    mList.add(client);
                    mExecutorService.execute(new Service(client)); // 开启一个新线程
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /// 扩展Runnable接口
        class Service implements Runnable {
            private Socket socket;
            private BufferedReader in = null;
            private String msg = " ";
    
            public Service(Socket socket) {
                this.socket = socket;
                try {
                    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    // 客户端只要一连到服务器,便向客户端发送下面的信息。
                    msg = "服务器" + this.socket.getInetAddress() + "加入;此时总连接:" + mList.size() + "(服务器发送)";
                    this.sendmsg();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void run() {
                try {
                    while (true) {
                        if ((msg = in.readLine()) != null) {
                            // 当客户端发送的信息为:exit时,关闭连接
                            if (msg.trim().equals("exit")) {
                                System.out.println("GAME OVER");
                                mList.remove(socket);
                                in.close();
                                msg = "服务器" + socket.getInetAddress() + "退出;此时总连接:" + mList.size() + "(服务器发送)";
                                socket.close();
                                this.sendmsg();
                                break;
                            } else {
                                // 接收客户端发过来的信息msg,然后发送给客户端。
                                msg = socket.getInetAddress() + "" + msg + "(服务器发送)";
                                this.sendmsg();
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            /**
             * 循环遍历客户端集合,给每个客户端都发送信息。
             */
            public void sendmsg() {
                // 在服务器上打印
                System.out.println(msg);
                // 遍历打印到每个客户端上
                int num = mList.size();
                for (int i = 0; i < num; i++) {
                    Socket mSocket = mList.get(i);
    
                    PrintWriter out = null;
                    try {
                        out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream())), true);
                        out.println(msg);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    当客户端连接并发送数据时,运行效果如下:

    最后的最后,再来愉快(juewang)地吐槽一下吧,用 Qt 或者 C++ 库很简单就能实现的 socket,为啥 Android 就这么费劲呢???这么一个简单的功能,要搞这么多线程,让整个程序看起来很复杂。。。。哎

    目前看来,c++ 的 socket 客户端只能跟 c++ 的服务端通信,java 的 socket 客户端只能跟 java 的服务端通信,跨越语言的通信,,,,,我是不行了,。。。

  • 相关阅读:
    Interview with BOA
    Java Main Differences between HashMap HashTable and ConcurrentHashMap
    Java Main Differences between Java and C++
    LeetCode 33. Search in Rotated Sorted Array
    LeetCode 154. Find Minimum in Rotated Sorted Array II
    LeetCode 153. Find Minimum in Rotated Sorted Array
    LeetCode 75. Sort Colors
    LeetCode 31. Next Permutation
    LeetCode 60. Permutation Sequence
    LeetCode 216. Combination Sum III
  • 原文地址:https://www.cnblogs.com/hxzkh/p/10564426.html
Copyright © 2011-2022 走看看