以java实现阿里云服务器与多客户端(安卓app,树莓派)向指定用户通信

前言

本代码目的:树莓派作为门锁,存在唯一标识门锁的id,为了让树莓派和app能够互相有效的通信,使用tcp套接字。为了方便识别通信的内容,三者的编码都是utf-8

java服务器

理论

用java编写服务器的代码,完成后打包成jar传到阿里云服务器上,在服务器上运行即可。
一个服务器首先创建一个主套接字用来接受响应客户端连接请求,每个客户端申请连接后,服务器接收到则会产生对应的套接字并且创建线程来单独对该客户端服务,通过对应的套接字来发送给对应的客户端。

如何确定哪个是套接字对应哪个客户端

创建一个list存放一个自己设置的user的类,类中包含了套接字、套接字的输入流、套接字的输出流、以及本实验需要的锁的id和类型type。
门锁user的id则为门锁的id,type为door,手机user的id为当时登录的id(即为所对应的门锁id),type为man。这个看自己做的应用的需要设置
每增加一个客户端,则创建一个user加在list中,线程的形参为该客户端的user和早创建了的list。则对指定用户发送消息是便可以通过user类的get函数的查找

实操

user类:

class User {

	private String id;
	private String type;
	private Socket socket;
	private BufferedReader br;
	private PrintWriter pw;

	public User(String id, final Socket socket, String type) throws IOException {
		this.id = id;
		this.socket = socket;
		this.type = type;
		this.br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		this.pw = new PrintWriter(socket.getOutputStream());
	}

	@Override
	public String toString() {
		return "User [id=" + id + ", type=" + type + ", socket=" + socket + ", br=" + br + ", pw=" + pw + "]";
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public Socket getSocket() {
		return socket;
	}

	public void setSocket(Socket socket) {
		this.socket = socket;
	}

	public BufferedReader getBr() {
		return br;
	}

	public void setBr(BufferedReader br) {
		this.br = br;
	}

	public PrintWriter getPw() {
		return pw;
	}

	public void setPw(PrintWriter pw) {
		this.pw = pw;
	}

}

服务器的类serr ,服务器从serr的main函数作为程序的入口:

package testsrc;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

//服务器类
public class serr {

	public static void main(String[] args) throws Exception {
		// 实例化一个list,用于保存所有的User
		List<User> list = new ArrayList<User>();
		list.clear();
		ServerSocket serverSocket = new ServerSocket(10001);
		System.out.println("服务端开始工作~");
		// 循环监听客户端连接
		while (true) {
			Socket socket = serverSocket.accept();
			User user = new User("id", socket, "type");
			list.add(user);
			System.out.println(socket);
			// 创建一个新的线程,接收信息并转发
			ServerThread thread = new ServerThread(user, list);
			thread.start();
		}
	}
}

HeartThread :

测试发现,tcp连接长时间不收发数据时会断开连接,上网查资料发现存在心跳连接这个定义,在此我为了方便!简化决定服务器创建一个线程用于隔一段时间给所连接的客户端发信息。
一般都是客户端给服务器定时发信息,服务器设置定时器接收到越好的消息后重置定时器,若超时没接收到则关闭套接字
该线程由在服务器类创建的ServerThread线程创建,即线程中的线程,需要传入参数user,用来确定对应的套接字


public class HeartThread extends Thread{

	private User user;
	
	public HeartThread(User user) {
		this.user=user;
	}
	public void run() {
		try {
			while(true) {
			//使用ServerThread定义的静态方法send,该方法有synchronized标识方法为临界资源
				ServerThread.send("heart test",user);
				sleep(5000);
			}
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		
	}
}

ServerThread线程:

由于存在子线程HeartThread隔段时间发送数据,因此服务器发消息的时候可能会产生同时发送数据的情况,因此将本类所定义的静态方法send用synchronized关键词,表示为临界资源,则不会产生同时发生数据给服务器的情况

public class ServerThread extends Thread {
	private User user;
	private List<User> list;
	//是否是第一次读取信息,是的话需要得到id和type
	private boolean fristtime = true;
	//是否查找到该用户标识
	private boolean findother = true;
	//是否一直读取信息
    private boolean Reading=true;
	public ServerThread(User user, List<User> list) {
		this.user = user;
		this.list = list;
	}

	//临界资源
	synchronized static void send(String instruction,User user1) {
		PrintWriter pw = user1.getPw();
		pw.write(instruction + "\n");
		pw.flush();
	}
	
	public void run() {
		try {
			// 获取该客户端的printwriter用来给该客户端发连接成功的消息
			PrintWriter pw = user.getPw();
			pw.write("hello" + "\n");
			pw.flush();
			HeartThread heartThread=new HeartThread(user);
			heartThread.start();
			InputStream in = user.getSocket().getInputStream(); // 获取客户端传来的消息
			// 按字节读取
			int len = 0;
			byte[] buf = new byte[1024];
			while (true) {
				len = in.read(buf);
				// 信息的格式:login,id,type(door/man)
				// exit
				// say,totype(man,door),thing(open,add,abnormal)
				// 不断地读取客户端发过来的信息
				String msg = new String(buf, 0, len); // 转化成string
				String[] str = msg.split(",");
				if (fristtime) {
					if (str[0].equals("login")) {
						user.setId(str[1]);
						user.setType(str[2]);
						System.out.println("login seuccess");
						System.out.println("login" + user);
						fristtime = false;
					} else {
						System.out.println("Illegal user!!");
						remove(user);
						break;
					}
				}
				System.out.println("client:" + user.getId() + "--" + user.getType() + ": " + msg);
				switch (str[0]) {
				case "login":
					System.out.println("ok,logined");
					break;
				case "exit":
					remove(user);// 移除用户
					break;
				case "say":
					judgecansend(str);
					break;
				default:
					System.out.println("非法信息:" + msg.toString());
					break;
				}
			}
		} catch (Exception e) {
			System.out.println("接受指令异常,异常原因:"+e.getMessage());
			remove(user);
		} 
	}

//这是我自己的设定,不同的应用不同的判定标准
	private void judgecansend(String[] str) {
		if(str[1].equals(user.getType())) {
			System.out.println("不要给自己发信息!");					
		}
		else if((str[1].equals("man")&&str[2].equals("abnormal"))||(str[1].equals("door")&&str[2].equals("open"))||(str[1].equals("door")&&str[2].equals("delete"))||(str[1].equals("door")&&str[2].equals("add"))) {
			sendToClient(user.getId(), str[1], str[2]); // 转发信息给特定的用户
		}
		else System.out.println("不要发不正确不对应的信息");
	}
	
	private void sendToClient(String userid, String type, String instruction) {
		// 从头查起list
		int number=0;
		for (User user : list) {
			if (user.getId().equals(userid) && user.getType().equals(type)) {
				findother=false;
				try {
					System.out.println("send to user " + user.getId() + "--" + user.getType() + ":" + instruction);
					send(instruction,user);
					number++;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		if(findother) {
			System.out.println("no conn can send");
		}
		else {
			System.out.println("send tatal "+number+" clients!");
		}
	}

	private void remove(User user2) {
		try {
			user2.getBr().close();
			user2.getSocket().close();
			list.remove(user2);
			System.out.println("关闭套接字");
			System.out.println("remove user:"+user.getId()+"--"+user.getType());
			Reading=false;
		} catch (IOException e) {
			System.out.println("remove异常原因:");
			e.printStackTrace();
		}
				
	}
}

把上面三个类放在一个包里import相应的包就可以了
最后打包成jar
右键项目——Export——java——Runnable JAR file——next——选择导出的位置和程序开始的类——finish

客户端

Android app

理论

主活动通过套接字进行连接了之后,绑定服务,服务用主活动创建的套接字

绑定服务原因

必须绑定服务,测试发现仅仅只是主活动打开服务的话,主活动被关闭了则套接字断开,服务依旧在后台工作但无法收到消息,在此打开app效果是只能发送服务器的消息不能接收。
以上情况出现在主活动创建套接字,如果是服务创建套接字的话不需要绑定,直接start就好。

授予应用权限

至关重要,给app联网权限才能够进行套接字通信,在AndroidMainfest.xml中增加

<uses-permission android:name="android.permission.INTERNET"/>

在这里插入图片描述

界面布局

这个各位随意,没必要照抄,在此我随意设置两个按钮用来发送信息,以及退出。
这里记得给各个按钮等控件设置id

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="send"
        tools:layout_editor_absoluteX="87dp"
        tools:layout_editor_absoluteY="122dp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="exit"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/go"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="52dp"
        android:layout_marginLeft="52dp"
        android:layout_marginBottom="156dp"
        android:text="go"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

服务recvservice

重写onBind方法

重写onBind方法,由于本服务不需要活动调用服务内相应的方法,因此我没有设置返回的响应的内容:

   @Override
   public IBinder onBind(Intent intent){
       return  null;
   }
重写onCreate方法

重写onCreate方法,在其中创建一个线程用来实时接收消息,入门的在线程start之后的代码就没写了。

    @Override
   public void onCreate(){
       super.onCreate();
       isflag=true;
       //创建线程接受服务器传递的消息
       new Thread((new Runnable() {
           @Override
           public void run() {
               System.out.println("lisen......");
               try {
                   while(isflag){
                       String read=HomeActivity.br.readLine();
                       System.out.println(read);
                       if(read.equals("abnormal")){
                           System.out.println("recive abnormal! ");
   						//查看活动是否可见
                           if(appOnForeground()){
                           //应用处于可见状态发送广播给活动
                               sendBroadcast(new Intent("abnormal"));
                           }
                           else {
                               mNotificationManager.notify(1, notification);
                           }
                       }
                   }
               }catch (Exception e){
                   e.printStackTrace();
                   System.out.println("read error!");
               }
           }
       })).start();
通知栏

于是我丰富了一下内容,即接收到消息后如果app是不可见的则发送通知栏,如果可见则发送广播给活动
首先判断app是否可见,这需要创建活动管理者以及得到当前的包名

//创建活动管理者用于接下来得到活动是否可见
      activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
      packageName = this.getPackageName();

线程调用了在服务类里自己设定的appOnForeground方法,返回true则为可见状态

 private boolean appOnForeground() {
     List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();

     if (appProcesses == null)
         return false;

     for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
         if (appProcess.processName.equals(packageName) && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
             return true;
         }
     }
     return false;
 }

通知栏通知首先要声明一个通知管理者,并且设定点击通知后跳转的界面。

通知栏点击跳转界面

这时候需要注意的是,如果没有任何对intent的setflag或者对活动的声明的话,此时点击通知栏会新建一个新的想跳转的活动,无论该活动是否已经开启只不过处于不可见状态——造成两个相同的activity在栈中

      mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
      //点进通知的跳转界面
      Intent intent =new Intent(getApplicationContext(), MainActivity.class);
      //intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);这句话代表如果该活动在栈顶则不新建该活动,但是我的无法成功依旧会新建,因此注释了
      PendingIntent pendingIntent = PendingIntent.getActivity(this,0, intent,0);

      //这里判定 如果是系统的 8.0以上,会出现无效的情况。那么单独额外处理一下
      if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
          NotificationChannel notificationChannel=new NotificationChannel("my channel","noticeabnormal",NotificationManager.IMPORTANCE_LOW);
          NotificationManager notificationManager =
                  (NotificationManager) getApplication().getSystemService(Context.NOTIFICATION_SERVICE);
          notificationManager.createNotificationChannel(notificationChannel);
      }
通知栏点击跳转界面存在该活动则打开它

通知栏点击跳转界面存在该活动则打开它而不是新建一个:
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);这句话代表如果该活动在栈顶则不新建该活动,但是我的无法成功依旧会新建,因此注释了
解决方法是在AndroidMainfest.xml中为相应的活动加 android:launchMode=“singleTask”
举个例子:

<activity android:name=".MainActivity"
               android:launchMode="singleTask"></activity>

接下来再详细设置通知栏的具体内容

     notification =new NotificationCompat.Builder(recvservice.this,"my channel")
             .setContentTitle("您的家门口有异常情况!!")//标题
             .setContentText("点击查看详细")//内容
             .setWhen(System.currentTimeMillis())//发送时间
             .setSmallIcon(R.drawable.lock)//设置小图标
             .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.lock))//设置下拉后显示的图标
             .setDefaults(NotificationCompat.DEFAULT_ALL)//跟随系统 震动和播放铃声
             .setPriority(NotificationCompat.PRIORITY_MAX)
             .setContentIntent(pendingIntent)//设置意图 目的是点击通知打开那个页面
             .setAutoCancel(true)//点击消失
             .build();
 }

接下来重写什么onDestroy之类的我就不详细说了。

记得在androidMainfest.xml中声明该服务

 <service android:name=".recvservice"/>
服务总和
package xxx
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Build;
import android.os.IBinder;

import androidx.core.app.NotificationCompat;

import java.util.List;

public class recvservice extends Service {
  //通知栏
  public NotificationManager mNotificationManager ; //通知管理者
  Notification notification;  //通知的详细内容
  //判断当前应用是否处于可见,处于可见就不用通知栏通知
  ActivityManager activityManager;
  String packageName;
  public boolean isflag;

  @Override
  public IBinder onBind(Intent intent){
      return  null;
  }
  @Override
  public void onCreate(){
      super.onCreate();
      isflag=true;
      //创建线程接受服务器传递的消息
      new Thread((new Runnable() {
          @Override
          public void run() {
              System.out.println("lisen......");
              try {
                  while(isflag){
                      String read=HomeActivity.br.readLine();
                      System.out.println(read);
                      if(read.equals("abnormal")){
                          System.out.println("recive abnormal! ");
                          if(appOnForeground()){//应用处于可见状态
                              sendBroadcast(new Intent("abnormal"));
                          }
                          else {
                              mNotificationManager.notify(1, notification);
                          }
                      }
                  }
              }catch (Exception e){
                  e.printStackTrace();
                  System.out.println("read error!");
              }
          }
      })).start();

      mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
      //点进通知的跳转界面
      Intent intent =new Intent(getApplicationContext(), HomeActivity.class);
      //intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
      PendingIntent pendingIntent = PendingIntent.getActivity(this,0, intent,0);

      //这里判定 如果是 8.0以上,会出现无效的情况。那么单独额外处理一下
      if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
          NotificationChannel notificationChannel=new NotificationChannel("my channel","noticeabnormal",NotificationManager.IMPORTANCE_LOW);
          NotificationManager notificationManager =
                  (NotificationManager) getApplication().getSystemService(Context.NOTIFICATION_SERVICE);
          notificationManager.createNotificationChannel(notificationChannel);
      }

      notification =new NotificationCompat.Builder(recvservice.this,"my channel")
              .setContentTitle("您的家门口有异常情况!!")//标题
              .setContentText("点击查看详细")//内容
              .setWhen(System.currentTimeMillis())//发送时间
              .setSmallIcon(R.drawable.lock)//设置小图标
              .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.lock))//设置下拉后显示的图标
              .setDefaults(NotificationCompat.DEFAULT_ALL)//跟随系统 震动和播放铃声,振动需要开权限VIBRATE
              .setPriority(NotificationCompat.PRIORITY_MAX)
              .setContentIntent(pendingIntent)//设置意图 目的是点击通知打开那个页面
              .setAutoCancel(true)//点击消失
              .build();

      activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
      packageName = this.getPackageName();
  }

  @Override
  public int onStartCommand(Intent intent,int flags,int startId){
      return super.onStartCommand(intent,flags,startId);
  }
  @Override
  public void onDestroy(){
      super.onDestroy();
  }

  private boolean appOnForeground() {
      List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();

      if (appProcesses == null)
          return false;

      for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
          if (appProcess.processName.equals(packageName) && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
              return true;
          }
      }

      return false;
  }
}

广播

广播的类比较简单,就是收到广播后用Toast提示一下就好

package xxx;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;


public class MyBroadcast  extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "你的家门口有异常情况", Toast.LENGTH_SHORT).show();

    }

}

活动

Main
声明需要的属性
    private String ip;
    private Socket socket;
    public static OutputStream os;
    public static BufferedReader br;
    Button one;
    Button exit;
    Button go;
    TextView tv;
    Intent serviceintent;
    final int CONNECTING=1;
    final int CONNECT=2;

由于在现在的版本里,套接字的创建连接需要在异步或是线程中进行,不可以在主线程中进行,因此创建一个线程类命名为TCPClient,是Thread的子类。
其中想更新界面的ui需要用handler修改,如果在子线程中修改则会闪退
并且在该线程中绑定服务,随时接收信息

建立套接字的线程
    class tcpClient extends Thread {
        public void run() {
            Message message=handler.obtainMessage();
            message.what = CONNECTING;
            handler.sendMessage(message);
             Log.d("TAG", "handleMessage: try to connect");
             ip ="59.110.216.125" ;
            try {
                socket = new Socket(InetAddress.getByName(ip),10001);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (socket.isConnected()) {
                Message message1=handler.obtainMessage();
                message1.what = CONNECT;
                handler.sendMessage(message1);
                    Log.d("TAG", "handleMessage:connect");
                try {
                    os = socket.getOutputStream();
                    br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    String mine="login,id,man";
                    //按字节输出,则python/java服务器都要按字节读取
                    os.write(mine.getBytes());
                    os.flush();
                    System.out.println("send"+mine);

                    serviceintent = new Intent(HomeActivity.this, recvservice.class);
                        //绑定服务
                        bindService(serviceintent,MyConn,  BIND_AUTO_CREATE);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                }

        }
    }

绑定服务需要ServiceConnection,要重写onServiceConnected和onServiceDisconnected方法

 private ServiceConnection Myconn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
handler
    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == CONNECTING) {
                tv.setText("conning");
            }
            if (msg.what == CONNECT) {
                tv.setText("connect!");
            }
        }
    };
onCreate

在onCreate函数中需要创建TCPClient线程并运行,以及对按钮点击事件进行监听:
最好把按钮点击事件的内容放在onCreate函数外面,这样代码可读性更强,我这里只是测试

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tcpClient tcp = new tcpClient();
        tcp.start();
        one=findViewById(R.id.button);
        exit=findViewById(R.id.button2);
        tv=findViewById(R.id.text);
        go=findViewById(R.id.go);
        go.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent();
                intent.setClass(MainActivity.this,testintentusesocket.class);
                startActivity(intent);
            }
        });
        exit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (socket.isConnected()) {
                    Thread sendthread=new sThread("exit");
                    sendthread.start();
                    finish();
                }
            }
        });
        one.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (socket.isConnected()) {
                    tv.setText("SEND OPEN"+i);
                    i++;
                    Thread sendthread=new sThread("say,door,open");
                    sendthread.start();
                }
                else{
                    tv.setText("NO CONNECT SOCKET");
                    Toast.makeText(MainActivity.this,"socket duankai",Toast.LENGTH_LONG);
                }

            }
        });
    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        try {
            os.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

sThread

其中,一按下send或exit按钮就创建一个线程用来发送数据,易知,线程sThread用于发送数据,这个线程放在MainActivity外,这样别的Activity也可以创建sThread发送数据

class sThread extends Thread {
    String msg;
    public sThread(String msg1)
    {
        msg=msg1;
    }
    public void run() {
        try {
            MainActivity.os.write(msg.getBytes());
            MainActivity.os.flush();

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

本人测试发现,按钮监听事件不可直接利用套接字发送信息,必须创建线程让线程发送信息

testintentusesocket

用于测试跳转后的intent是否能够通过套接字实现通信

界面布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/buttont"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="send"
        tools:layout_editor_absoluteX="87dp"
        tools:layout_editor_absoluteY="122dp"
        tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

活动代码
package com.example.servertest;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;


public class testintentusesocket extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.te);
        Button bt=findViewById(R.id.buttont);
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Thread sendthread=new sThread("open");
                sendthread.start();
                finish();
            }
        });

    }
}
配置活动

在AndroidMainfest.xml中增加

<activity android:name=".testintentusesocket"></activity>

常见问题

1.套接字连接失败——网络没连上、服务器没运行
2.读取到字符串和明明相同的内容equal却失败——看看服务器和客户端编码是否一致,看服务器发送的方式和客户端接受的方式是否对应
3.app闪退 ——查看是否在线程中修改的布局,套接字是否连接上

树莓派

通过创建线程去实时响应消息,在主线程中做自己的事情,发送消息

import socket
import time
import threading

#程序运行前先进行tcp连接
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = "59.110.216.125"  # 阿里云服务器的公网IP地址
port = 10001  # 云服务器控制台安全组中打开的端口,与服务端相同
s.connect((host, port))  # 创建连接

showconn=True

#门锁发送信息(异常)给服务器
def send(msg):
   print(s)
   s.send(msg.encode("utf-8"))

#门锁随时待命接受信息:开门
def recvthing():
   while showconn:
       data = s.recv(20)
       //由于服务器转发有转行
       data = data.decode("utf-8").strip('\n')
       if data=="hello":
           print("connect!")
       if data=="open":
           print("to opendoordef()")
       if data=="add":
           print("to addfgdef()")
       print(data)
   s.close()

if __name__ == '__main__':
   #创建线程让线程去待命接受信息
   t = threading.Thread(target=recvthing)
   t.start()
   #先发本门锁的id给服务器,让服务器将门锁id、socketnumber、type=2即作为门锁存于数据库中
   msg = 'login,lie1,door'
   send(msg)
   while True:
       sendthing = input()
       send(sendthing)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值