Android(客户端)通过socket与QT(服务端)通信

一、概述

在这里我想实现一个跨平台的socket通讯,Android手机作为客户端向Ubuntu的QT平台上的服务端发送一个字符命令,由于是只发送一个字符,这里我尽可能简化socket通讯的过程以供后人参考。
文中贴上主要代码,末尾会给出完整源代码的下载。

二、QT的服务端

QT上的服务端我使用了QTcpServer和QTcpSocket类,大体的流程是这样的:
1、主窗口进入UI
2、启动TcpServer开始监听一个端口
3、监听到有新的Connection信号则触发下一个函数获取该socket
4、获取到该socket后触发读函数槽
5、读取信息,并进行字符编码的转换(很重要)
上代码
mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <sys/socket.h>
#include <sys/types.h>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    startTcpserver();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::startTcpserver()
{
    m_tcpServer = new QTcpServer(this);
    m_tcpServer->listen(QHostAddress::Any,60000); //监听任何连上60000端口的ip
    connect(m_tcpServer,SIGNAL(newConnection()),this,SLOT(newConnect())); //新连接信号触发,调用newConnect()槽函数,这个跟信号函数一样,可以随便取。
}

void MainWindow::newConnect()
{
    m_tcpSocket = m_tcpServer->nextPendingConnection(); //得到每个连进来的socket
    connect(m_tcpSocket,SIGNAL(readyRead()),this,SLOT(readMessage())); //有可读的信息,触发读函数槽 
}

void MainWindow::readMessage() //读取信息
{
    qint64 len = m_tcpSocket->bytesAvailable();
    qDebug()<<"socket data len:"<< len;
    QByteArray alldata = m_tcpSocket->read(len);
    //开始转换编码
    QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8");
    QString utf8str = utf8codec->toUnicode(alldata.mid(2));
    qDebug()<<"hex:["<<alldata.toHex().toUpper()<<"]";
    qDebug()<<"utf-8 ["<< (utf8str) << "]";
    //显示到控件上
    ui->label->setText(utf8str);

}

void MainWindow::sendMessage() //发送信息
{
    //QString strMesg= ui->lineEdit_sendmessage->text();
    QString strMesg="连接成功";
    qDebug()<<strMesg;
    m_tcpSocket->write(strMesg.toStdString().c_str(),strlen(strMesg.toStdString().c_str())); //发送
}

这是主窗口源代码,所有函数都在这里,函数调用过程是ui->startTcpserver()->newConnect()->readMessage()。
由于从java发过来的String类型字符串在socket传输过程中实际上被转换成UTF-8编码的字节数组,QT作为Server接收之后要对其进行转换,就是readMessage()函数里的过程

    qint64 len = m_tcpSocket->bytesAvailable();//获取长度
    qDebug()<<"socket data len:"<< len;
    QByteArray alldata = m_tcpSocket->read(len);
    /**开始转换编码**/
    QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8");
    QString utf8str = utf8codec->toUnicode(alldata.mid(2));
    qDebug()<<"hex:["<<alldata.toHex().toUpper()<<"]";
    qDebug()<<"utf-8 ["<< (utf8str) << "]";
    ui->label->setText(utf8str);//显示到控件上

在QT用QByteArray字节数组接收java发过来的String转换而来的字节数组,最终解包成QString类型的字符串得以在QT上显示

三、Android的客户端

在Android 4.0之后网络操作这样耗时的操作只能放在子线程中实现,所以在Android代码中我简单的创建了一个子线程来实现socket发送字符,下面这段代码就是子线程:

    private void sendData(){
        try{
            Socket socket = new Socket("192.168.1.112",60000);//创建Socket实例,并绑定连接服务端的IP地址和端口
            Log.e("线程反馈","创建成功!");
            DataOutputStream out = new DataOutputStream(socket.getOutputStream());          
            out.writeUTF(command); //以UTF的方式发送字符command
            socket.close();//一定记得关闭socket
            button_status.setText(command);//按钮上显示被发送的字符 
        }catch(Exception e){
            Log.e("线程反馈","线程异常!");          
        }
    }

值得注意的是客户端和服务端必须在同一局域网或者在电脑是用 ping 命令尝试连接手机的IP,如果可以ping的通,才能保证客户端和服务端能够正常通信。
其实创建socket并发送字符核心的就下面四句话:

Socket socket = new Socket("192.168.1.112",60000);
DataOutputStream out = new DataOutputStream(socket.getOutputStream()); 
out.writeUTF(command); 
socket.close();

我的手机客户端设置了几个按钮用来发送对应的控制命令字符
Android(客户端)通过socket与QT(服务端)通信_第1张图片
下面是Android主要代码:
MainActivity.java

package com.example.test_socket;

import java.io.DataOutputStream;
import java.net.Socket;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity implements OnClickListener{
    private Button button_left;
    private Button button_right;
    private Button button_up;
    private Button button_down;
    private Button button_stop;
    private Button button_start;
    private Button button_status;
    private String command;//按钮发送的命令

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//指定了当前活动的布局,这里表示将从res/layout目录中找到activity_main.xml文件作为本例的布局文件使用。
        button_left=(Button)findViewById(R.id.button_left);
        button_right=(Button)findViewById(R.id.button_right);
        button_up=(Button)findViewById(R.id.button_up);
        button_down=(Button)findViewById(R.id.button_down);
        button_start=(Button)findViewById(R.id.button_start);
        button_stop=(Button)findViewById(R.id.button_stop);
        button_status=(Button)findViewById(R.id.button_status);//显示被发送的命令
        button_left.setOnClickListener(this); //监听按键 
        button_right.setOnClickListener(this); //监听按键 
        button_up.setOnClickListener(this); //监听按键 
        button_down.setOnClickListener(this); //监听按键 
        button_start.setOnClickListener(this); //监听按键 
        button_stop.setOnClickListener(this); //监听按键 
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public void onClick(View arg0) {
        switch (arg0.getId()){
            case R.id.button_left:
                command = "L";
                break;
            case R.id.button_right:
                command = "R";
                break;
            case R.id.button_up:
                command = "U";
                break;
            case R.id.button_down:
                command = "D";
                break;
            case R.id.button_start:
                command = "B";
                break;
            case R.id.button_stop:
                command = "E";  
                break;
            default:
                command = " ";//在按了其他按键的情况下命令置为空格
                break;
        }
        new Thread(){
            @Override
            public void run(){
                sendData();//启动子线程创建socket并发送字符
            }
        }.start();              
    }

    private void sendData(){
        try{
            Socket socket = new Socket("192.168.1.112",60000);//创建Socket实例,并绑定连接远端IP地址和端口
            Log.e("线程反馈","创建成功!");
            DataOutputStream out = new DataOutputStream(socket.getOutputStream());          
            out.writeUTF(command); 
            socket.close();
            button_status.setText(command);
            /*OutputStream ops = socket.getOutputStream();//定义一个输出流,来自于Socket输出流 String b="a\n"; byte[] bytes = b.getBytes(); ops.write(bytes);//向输出流中写入数据 Log.v("线程反馈","发送成功!"); ops.flush();//刷行输出流 */ 
        }catch(Exception e){
            Log.e("线程反馈","线程异常!");          
        }
    }
}

四、要注意的几点

1、服务端和客户端必须能够Ping通才能保证正常通信
2、Android端设置的IP地址一定要是服务端的IP,端口号一定要和服务端监听的端口一致
Android客户端:

Socket socket = new Socket("192.168.1.112",60000);

QT服务端:

m_tcpServer->listen(QHostAddress::Any,60000);

3、如果连接成功,手机按一个控制按钮最下面一排第三个按钮会显示那个被按下的字符
4、建议调试Android端程序的时候开启手机里开发者选项的USB调试,直接连接手机进行调试,因为在模拟器里调试会遇到其他问题。
5、我调试的时候跑Ubuntu的电脑和跑Android的手机是连在同一个无线路由上的,保证他们在同一个局域网下可以ping通
6、由于跨平台传输存在字符编码转换的问题,请仔细考虑上面的readMessage()函数
7、如果想先测试一下Android客户端能否与电脑建立socket连接可以用java写一个服务端程序做测试,但要注意电脑上开启监听某一个端口之后一定要正常关闭程序否则会出现程序关闭端口未被关闭导致端口占用的情况。
下面给出一个java服务端测试代码:
(此处要感谢Defonds的博客 一个 Java 的 Socket 服务器和客户端通信的例子这两个例子代码很简洁,也很实用)
Server.java

import java.io.BufferedReader;  
import java.io.DataInputStream;  
import java.io.DataOutputStream;  
import java.io.InputStreamReader;  
import java.net.ServerSocket;  
import java.net.Socket;  

public class Server {  
    public static final int PORT = 60000;//监听的端口号 

    public static void main(String[] args) {    
        System.out.println("服务器启动...\n");    
        Server server = new Server();    
        server.init();    
    }    

    public void init() {    
        try {    
            ServerSocket serverSocket = new ServerSocket(PORT);    
            while (true) {    
                // 一旦有堵塞, 则表示服务器与客户端获得了连接 
                Socket client = serverSocket.accept();    
                // 处理这次连接 
                new HandlerThread(client);    
            }    
        } catch (Exception e) {    
            System.out.println("服务器异常: " + e.getMessage());    
        }    
    }    

    private class HandlerThread implements Runnable {    
        private Socket socket;    
        public HandlerThread(Socket client) {    
            socket = client;    
            new Thread(this).start();    
        }    

        public void run() {    
            try {    
                // 读取客户端数据 
                DataInputStream input = new DataInputStream(socket.getInputStream());  
                String clientInputStr = input.readUTF();//这里要注意和客户端输出流的写方法对应,否则会抛 EOFException 
                // 处理客户端数据 
                System.out.println("客户端发过来的内容:" + clientInputStr);    

                // 向客户端回复信息 
                DataOutputStream out = new DataOutputStream(socket.getOutputStream());                         
                out.close();    
                input.close();    
            } catch (Exception e) {    
                System.out.println("服务器 run 异常: " + e.getMessage());    
            } finally {    
                if (socket != null) {    
                    try {    
                        socket.close();    
                    } catch (Exception e) {    
                        socket = null;    
                        System.out.println("服务端 finally 异常:" + e.getMessage());    
                    }    
                }    
            }   
        }    
    }    
}    

sublime编辑器里直接配置好java编译环境可以直接Ctrl+B编译运行开始监听。
完整代码包下载:
http://download.csdn.net/detail/u013453604/9017403
有问题请回复评论

你可能感兴趣的:(android,socket,ubuntu,qt,字符编码)