Android App开发基础篇—Socket通信
来源:互联网 发布:linux软件工程师 编辑:程序博客网 时间:2024/05/16 14:26
Android App开发基础篇—Socket通信
前言:离职闲置了半年,终于又开始上班了。重新工作的第一个项目就是Socket通信,虽然自己对Socket有一点点印象(读书的时候有过Java网络编程的课程),但是之前的工作中完全没接触过,于是,果断买了一本Android网络编程的书来开始学习。下面简单做一个学习笔记。
一、首先,看一下Socket和ServerSocket最基本的用法
1.1、在Eclipse上创建ServerSocket,代码如下:
public class Server{public static void main(String[] args) { try { //获取ServerSocket实例,监听指定端口 ServerSocket server = new ServerSocket(8033); System.out.println("服务端已启动,等待客户端连接..."); //accept()方法接收客户端的Socket连接请求,返回一个 //与客户端对应的Socket Socket socket = server.accept(); System.out.println("客户端" + socket.getInetAddress() + "已连接..."); //获取Socket的输出流 OutputStream os = socket.getOutputStream(); //将输出流包装成PrintStream PrintStream ps = new PrintStream(os); //向客户端输出信息 ps.println("Hello客户端"); //关闭输出流 ps.close(); //关闭Socket socket.close(); } catch (IOException e) { e.printStackTrace(); }}}
1.2、编写Android客户端Socket程序,代码如下:
import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.view.View;import android.widget.Button;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.Socket;public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static String TAG = "MainActivity"; //服务端的IP地址,依据每个人的实际情况而不同,例如 //笔者的服务端是在自己电脑上用Eclipse写的应用程序, //因此此处IP地址是用ipconfig(windows系统下)命令查询 //到的电脑的IPv4地址 private static final String IP = "192.168.1.132"; //端口号,有效值0~65535,避免使用已被占用的端口号 private static final int PORT = 8033; private String data = "this is from socket client"; private Socket socket; private Button mBtnConn; private Button mBtnSend; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBtnConn = (Button) findViewById(R.id.btn_connect); mBtnSend = (Button) findViewById(R.id.btn_send); mBtnConn.setOnClickListener(this); mBtnSend.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_connect: //Android上不能将网络操作写在主线程, //这里创建一个线程来执行Socket操作 new ClientThread().start(); break; case R.id.btn_send: break; } } //执行Socket操作的线程 private class ClientThread extends Thread { @Override public void run() { super.run(); try { //获取Socket实例,通过指定的端口号向指定的IP地址Server发送连接请求 Socket socket = new Socket(IP, PORT); //获取Socket输入流 InputStream is = socket.getInputStream(); //将Socket输入流包装成BufferedReader BufferedReader buff = new BufferedReader(new InputStreamReader(is)); //读取数据 String s = buff.readLine(); //打印信息 Log.e(TAG, "run: 服务端:" + s); //关闭输入流 buff.close(); //关闭Socket socket.close(); } catch (IOException e) { e.printStackTrace(); } } }}
以上便是Socket通信最基本的过程。在Eclipse上运行Server端程序,然后运行Android客户端APP,点击“连接”按钮,连接成功后,Server端直接向Client端发送信息,Client端接收到消息后通过log打印出来。通信结束后双方都关闭各自的IO流和Socket连接。这里只是一个简单的例子,但实际应用中不会这么简单,通常都需要Server端和Client端都具备接收消息和发送消息的功能。因此,接下来需要修改一下程序。
二、Socket通信Server端和Client端互发信息
2.1、在Eclipse中书写Server端代码如下:
import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.PrintStream;import java.net.ServerSocket;import java.net.Socket;import javax.swing.JButton;import javax.swing.JTextArea; public class Server implements ActionListener { private JButton sendButton; private Window window; private ServerSocket serverSocket; private JTextArea jTextArea; private Socket mCurrentSocket; public Server() { // 为了操作方便,这里写一个Java应用程序窗口 window = new Window("服务端"); // 获取一个按钮 sendButton = window.getSendButton(); // 给按钮设置事件监听 sendButton.addActionListener(this); // 获取一个文本显示区 jTextArea = window.getJTextArea(); try { // 获取ServerSocket实例,开启端口监听 serverSocket = new ServerSocket(8033); System.out.println("服务端已启动,等待客户端连接..."); jTextArea.append("服务端已启动,等待客户端连接...\n"); // 使用while循环实现持续监听客户端Socket连接请求 while (true) { // accept()方法接收客户端Socket连接请求,产生一个与客户端对应 // 的Socket Socket socket = serverSocket.accept(); //这里用一个全局变量mCurrentSocket来记录当前正在与服务端互动的 //的客户端Socket,即最新连接Server端,或者最近一次向Server端发送 //消息的客户端Socket,服务端发送的消息会被该客户端接收 mCurrentSocket = socket; System.out.println("客户端" + socket.getInetAddress() + "已连接..."); jTextArea.append("客户端:" + socket.getInetAddress() + "已连接...\n"); //每有一个客户端Socket接入,就开启一个线程单独监听来自该客户端的消息的输入 new Thread(new Runnable() { @Override public void run() { // 当一个客户端Socket连接成功时,开启一个while循环 // 用于持续监听客户端的消息输入 while (true) { if(!socket.isClosed()) { // 读取客户端发送的消息 readMessage(socket); } } } }).start(); } } catch (IOException e) { e.printStackTrace(); } } // main方法是Java程序启动的入口 public static void main(String[] args) { new Server(); } private void readMessage(Socket socket) { //记录最近一次向服务端发送消息的客户端Socket,服务端发送 //的消息将被该客户端接收 mCurrentSocket=socket; try { // 获取客户端Socket输入流 InputStream is = socket.getInputStream(); // 将输入流包装成BufferedReader BufferedReader buff = new BufferedReader(new InputStreamReader(is)); // 读取数据 String readLine = buff.readLine(); System.out.println("来自客户端:" + readLine); jTextArea.append("来自客户端:" + readLine + "\n"); } catch (IOException e) { e.printStackTrace(); try { socket.close(); } catch (IOException e1) { e1.printStackTrace(); } } } @Override public void actionPerformed(ActionEvent arg0) { sendMessage(); } private void sendMessage() { try { // 将Socket对应的输出流包装成PrintStream PrintStream ps = new PrintStream(mCurrentSocket.getOutputStream()); // 进行普通IO操作,向客户端输出信息 ps.println("Hello客户端"); } catch (IOException e) { e.printStackTrace(); } } }
2.2、Android客户端代码如下:
import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.TextView;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.PrintStream;import java.net.Socket;public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static String TAG = "MainActivity"; private static final String IP = "192.168.1.132"; private static final int PORT = 8033; private String data = "this is from socket client"; private Button mBtnConn; private Button mBtnSend; private TextView mMessageTv; private Socket mSocket; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mMessageTv = (TextView) findViewById(R.id.message_tv); mBtnConn = (Button) findViewById(R.id.btn_connect); mBtnSend = (Button) findViewById(R.id.btn_send); mBtnConn.setOnClickListener(this); mBtnSend.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_connect: //Android上不能将网络操作写在主线程, //这里创建一个线程来执行Socket操作 new ConnectThread().start(); break; case R.id.btn_send: new SendDataThread().start(); break; } } //执行Socket连接操作的线程 private class ConnectThread extends Thread { @Override public void run() { super.run(); try { //获取Socket实例,通过指定的端口号向指定的IP地址Server发送连接请求 mSocket = new Socket(IP, PORT); if (mSocket != null) { //获取Socket输入流 InputStream is = mSocket.getInputStream(); //将Socket输入流包装成BufferedReader BufferedReader buff = new BufferedReader(new InputStreamReader(is)); //连接成功以后,开启一个while循环来持续监听服务端的消息输入 while (true) { final String s = buff.readLine(); //将获取到的信息显示在TextView上,子线程中不能更新UI,这里直接用 //runOnUiThread来做 runOnUiThread(new Runnable() { @Override public void run() { mMessageTv.append("来自服务端:" + s + "\n"); } }); } } } catch (IOException e) { e.printStackTrace(); } } } private class SendDataThread extends Thread { @Override public void run() { super.run(); if (mSocket != null) { try { OutputStream os = mSocket.getOutputStream(); PrintStream ps = new PrintStream(os); ps.println(data); } catch (IOException e) { e.printStackTrace(); } } } }}
好了,上面的代码已经可以实现Socket服务端和客户端互相收发消息的通信 了。燃鹅,问题还没有结束。相信熟悉的同学马上就会发现了,上面的程序有问题,有BUG。那么,问题在哪里呢?很简单,我们多启动一个客户端,然后两个客户端分别与服务端互发消息,问题马上就出现 了。尝试过后发现,如果启动了两个客户端,服务端只能向其中一个客户端发送消息,而两个客户端也只有其中一个能向服务端发送消息。并且当两个客户端分别都执行了向客户端发消息的操作后,原本能向服务端发送消息的客户端再发消息时,服务端接收到的却是空数据。这里出现问题的原因,由于作者目前水平有限,暂时无法解释清楚。只是根据书本的说法,这里我们需要开启多线程。因此,下面我们继续对代码进行修改。修改的地方主要在服务端,将读取客户端消息的代码放到一个线程里面。即将上面服务端代码中的
// 当一个客户端Socket连接成功时,开启一个while循环// 用于持续监听客户端的消息输入while(true){ // 读取客户端发送的消息 readMessage(socket);}
部分放入一个线程中去执行。修改如下:
new Thread(new Runnable() {@Overridepublic void run() { // 当一个客户端Socket连接成功时,开启一个while循环 // 用于持续监听客户端的消息输入 while (true) { // 读取客户端发送的消息 readMessage(socket); } }}).start();
好了,现在服务端可以同时跟多个客户端正常通信了。然而,现在的程序虽然可以运行,也能实现功能,达到效果,但是还是存在一些问题的。这也是本人目前水平的局限,这里就暂且把问题留下,当做今后学习的方向之一。
最后贴上程序中用到的用于创建Java应用程序窗口的Window类
import java.awt.Color;import java.awt.Dimension;import java.awt.FlowLayout;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JTextArea;import javax.swing.JTextField; public class Window extends JFrame { /** * 窗口类 定义客户端和服务器端的窗口 */ private static final long serialVersionUID = 2L; private String windowName; private JFrame myWindow; private JTextArea area; private JTextField field; private JButton btnSend; private JButton btnSendAll; public Window(String windowName) { this.windowName = windowName; myWindow = new JFrame(windowName); myWindow.setLayout(new FlowLayout()); myWindow.setSize(new Dimension(500, 300)); // 不能改变窗口大小 myWindow.setResizable(false); area = new JTextArea(); field = new JTextField(); btnSend = new JButton("Send"); btnSendAll = new JButton("Send To All"); // 设置field的大小 field.setPreferredSize(new Dimension(300, 30)); myWindow.add(field); myWindow.add(btnSend); myWindow.add(btnSendAll); myWindow.add(area); // 改变area的大小 area.setPreferredSize(new Dimension(470, 210)); area.setBackground(Color.PINK); area.setEditable(false); // 设置窗口显示在电脑屏幕的某区域 myWindow.setLocation(400, 200); myWindow.setVisible(true); // 点击关闭按钮时触发该方法 closeMyWindow(); } /** * 方法名:closeMyWindow() * * @param * @return 功能:当用户点击关闭按钮时,退出并且关闭该窗口 */ private void closeMyWindow() { myWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } /** * 方法名:getFieldText() * * @param * @return string 功能:获取窗口的TextField中的字符串 */ public String getFieldText() { return field.getText().toString(); } /** * 方法名:getSendButton() * * @param * @return JButton 功能:获得该窗口中的按钮 */ public JButton getSendButton() { return btnSend; } /** * 方法名:getSendAllButton() * * @param * @return JButton 功能:获得该窗口中的按钮 */ public JButton getSendAllButton() { return btnSendAll; } /** * 方法名:getJTextArea() * * @param * @return JTextArea 功能:返回窗口中的JTextArea */ public JTextArea getJTextArea() { return area; } /** * 方法名:getTextField() * * @param * @return JTextField 功能:获得窗口中的textfield */ public JTextField getTextField() { return field; } }
后记:Android 开发中的Socket通信主要用到了Java API中java.net中的两个类Socket和ServerSocket,分别用来表示Socket连接的客户端和服务端。Socket通信的过程大致可描述为:(1)Server端运行一个SocketServer程序,监听设备上指定的端口;(2)Client端通过指定的Server端的IP地址和端口号,向Server端发起连接请求;(3)Server端向Client发回Accept(接收)消息,Socket连接建立;(4)Server端和Client端开始通信。
- Android App开发基础篇—Socket通信
- Android App开发基础篇—实现非阻塞Socket通信
- Android应用开发基础篇(12)-----Socket通信
- Android socket通信基础
- Android开发:Socket通信
- Android App开发基础篇—HttpURLConnection基础使用
- Android开发之Socket通信
- Android开发之Socket通信
- Socket通信基础篇(一)
- Android App开发基础篇—数据存储(SQLite数据库)
- Android App开发从零开始之基础篇—四大组件(三)—组件间的通信方式Broadcast(广播)
- [Android开发] Android客户端使用socket通信
- Android开发Socket编程基础
- Android app 基础开发流程
- Android网络开发之Socket通信
- Android开发手记:Socket网络通信
- Android网络开发之Socket通信
- Android开发笔记: Socket通信--【带例子】
- 门面模式,产品经理
- 大学生活。
- Linux系统--ELF文件之可执行文件(Executable file)解析
- ubuntu下Atom环境搭建:c++ & opencv & curl & jsoncpp
- day01-app的开发步骤
- Android App开发基础篇—Socket通信
- Python 3 格式化字符串的几种方法
- FFmpeg函数简单分析:内存的分配和释放(av_malloc()、av_free()等)
- 4.5-全栈Java笔记:垃圾回收机制
- 程序员的半生
- Django 新建项目时提示can't open file 'django-admin.py': 解决办法
- 广播电视SDI上下变换器,SDI视频输出格式上下变换。
- 三目运算符
- Ubuntu 一些大数据挖掘与机器学习工具安装