Android网络功能开发(6)——TCP协议通信

TCP通信的双方需要建立连接,所以先由一方监听某个端口,等待其他设备来连接,这一方称为服务器端。另一方向服务器端发起连接请求,称为客户端。服务器端接受客户端的连接请求后,双方之间的连接建立起来。连接建立后,双方对于连接的使用是相同的,都可以通过连接发送和接收数据。

如果双方通信时没有像HTTP协议这种一问一答的固定模式,就需要随时接收和处理对方发来的数据,所以要把接收和处理数据的工作在一个单独的线程中执行。如果服务器端想同时与多个客户端通信,要对每个客户端的连接建立一个接收线程。

下面通过一个简单的聊天室原型来演示如何编程实现TCP通信。聊天室系统包含一个服务器和多个客户端。服务器对从一个客户端接收的信息,要转发到所有客户端。客户端要把从服务器接收的信息显示给用户,并把用户输入的信息发送到服务器。为实现这一功能,系统的结构要像下面这个图这样设计。

聊天服务器端有一个TCP监听线程,当有客户端发来连接请求时,负责接受客户端的连接请求并创建接收线程。同时,用一个列表记下所有的客户端连接。这样,接收线程就能够将接收到的信息发往列表中保存的所有客户端。客户端也有两个线程,接收线程负责从服务器接收数据并显示给用户,主线程接收用户的输入并发往服务器。

服务器端运行在JavaSE上,的具体代码是这样的:

监听线程ListeningThread首先创建一个ServerSocket对象,然后循环调用它的accept方法等待接受客户端的连接请求。当有客户端发来连接请求时,accept方法返回一个Socket对象。监听线程把Socket对象添加到连接列表connections,并创建一个接收线程ServiceThread。

接收线程循环调用readLine,一旦客户端有消息发来,readLine就会返回发来的消息内容,然后调用chatMessage发送到所有客户端(包括自己)。

chatMessage方法遍历客户端列表connections,把客户端发来的消息转发到所有客户端。

客户端是一个Android应用,界面是这样的:最上面一行文本框中输入服务器的IP地址;下面TCP一行的三个按钮控制建立连接、发送信息、断开连接;再下面UDP一行三个按钮用于测试UDP通信方式;再下面的Content文本框是要发送的聊天消息;最下面的文本是所有聊天内容。

当点击TCP的Connect按钮时,以服务器端的IP地址和端口号为参数创建一个Socket对象,这样就会向服务器发送一个建立连接的请求。如果服务器接受请求,那么连接就成功建立,Socket对象也会创建成功,否则会产生异常,转入异常处理流程。

有了Socket对象后,接着创建一个接收数据的线程。注意这部分代码也需要用AsyncTask异步执行,不能占用主线程。接收线程中循环调用in.readLine方法不断读取服务器发来的消息,一旦有消息发过来,该方法就会返回消息内容。接着再调用sendMessage把消息内容传给主线程显示,因为子线程中不能直接操作界面控件。

传递消息内容用的Android的Handler机制,主线程中创建一个Handler对象,并在它的handleMessage方法中接收消息。子线程中调用该Handler对象的sendMessage方法传递消息。这样就能在线程间传递消息了。

当点击TCP的Send按钮时,代码是把Content文本框中的字符串通过Socket连接发送到服务器。

Android端的布局文件如下:

<?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:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:text="Server:"
        android:textAppearance="?android:attr/textAppearanceLarge"
        app:layout_constraintEnd_toStartOf="@+id/editTextServer"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
 
    <EditText
        android:id="@+id/editTextServer"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:ems="10"
        app:layout_constraintBaseline_toBaselineOf="@+id/textView1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView1">
 
        <requestFocus
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </EditText>
 
    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:text="TCP: "
        android:textAppearance="?android:attr/textAppearanceLarge"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/buttonTCPConnect" />
 
    <Button
        android:id="@+id/buttonTCPConnect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Connect"
        android:textAllCaps="false"
        app:layout_constraintBaseline_toBaselineOf="@+id/buttonTCPSend"
        app:layout_constraintStart_toEndOf="@+id/textView2" />
 
    <Button
        android:id="@+id/buttonTCPSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginLeft="10dp"
        android:text="Send"
        android:textAllCaps="false"
        app:layout_constraintStart_toStartOf="@+id/buttonUDPSend"
        app:layout_constraintTop_toBottomOf="@+id/editTextServer" />
 
    <Button
        android:id="@+id/buttonTCPClose"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Close"
        android:textAllCaps="false"
        app:layout_constraintBaseline_toBaselineOf="@+id/buttonTCPSend"
        app:layout_constraintStart_toEndOf="@+id/buttonTCPSend" />
 
    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:text="UDP:"
        android:textAppearance="?android:attr/textAppearanceLarge"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/buttonUDPListen" />
 
    <Button
        android:id="@+id/buttonUDPListen"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Listen"
        android:textAllCaps="false"
        app:layout_constraintBaseline_toBaselineOf="@+id/buttonUDPSend"
        app:layout_constraintStart_toEndOf="@+id/textView3" />
 
    <Button
        android:id="@+id/buttonUDPSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send"
        android:textAllCaps="false"
        app:layout_constraintStart_toEndOf="@+id/buttonUDPListen"
        app:layout_constraintTop_toBottomOf="@+id/buttonTCPConnect" />
 
    <Button
        android:id="@+id/buttonUDPStop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop"
        android:textAllCaps="false"
        app:layout_constraintBaseline_toBaselineOf="@+id/buttonUDPSend"
        app:layout_constraintStart_toEndOf="@+id/buttonUDPSend" />
 
    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Content:"
        android:textAppearance="?android:attr/textAppearanceLarge"
        app:layout_constraintBaseline_toBaselineOf="@+id/editTextContent"
        app:layout_constraintStart_toStartOf="parent" />
 
    <EditText
        android:id="@+id/editTextContent"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:ems="10"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView4"
        app:layout_constraintTop_toBottomOf="@+id/buttonUDPListen" />
 
    <TextView
        android:id="@+id/textViewResult"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editTextContent" />
 
</androidx.constraintlayout.widget.ConstraintLayout>

Android端的完整代码如下:

import androidx.appcompat.app.AppCompatActivity;
 
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
 
public class MainActivity extends AppCompatActivity {
    EditText etServer;
    EditText etContent;
    TextView tvResult;
 
    Handler mHandler;
 
    Socket tcpSocket;
    TCPReceiveThread tcpReceiveThread;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        etServer = findViewById(R.id.editTextServer);
        etContent = findViewById(R.id.editTextContent);
        tvResult = findViewById(R.id.textViewResult);
 
        etServer.setText("192.168.1.2");
 
        tcpSocket = null;
        mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg){
                switch(msg.what) {
                    case 1:    // receive socket message
                        String received = (String)msg.obj;
                        tvResult.append(received+"\r\n");
                        break;
                }
            }
        };
 
        Button btnTCPConnect = findViewById(R.id.buttonTCPConnect);
        btnTCPConnect.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                String serverIP = etServer.getText().toString();
                // tcpConnect(serverIP);    // 不能在主线程中执行网络操作
                new AsyncTask<String, Void, Void>(){
                    @Override
                    protected Void doInBackground(String... arg0) {
                        try {
                            tcpConnect(arg0[0]);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
                }.execute(serverIP);
            }
        });
 
        Button btnTCPSend = findViewById(R.id.buttonTCPSend);
        btnTCPSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                new AsyncTask<String, Void, Void>(){
                    @Override
                    protected Void doInBackground(String... arg0) {
                        try {
                            tcpSend(arg0[0]);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
                }.execute(etContent.getText().toString());
                etContent.setText("");
            }
        });
 
        Button btnTCPClose = (Button) findViewById(R.id.buttonTCPClose);
        btnTCPClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                new AsyncTask<Void, Void, Void>(){
                    @Override
                    protected Void doInBackground(Void... arg0) {
                        try {
                            tcpDisconnect();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
                }.execute();
            }
        });
 
    }
    void tcpConnect(String ip) throws IOException {
        if(tcpSocket!=null) tcpDisconnect();
        tcpSocket = new Socket(ip, 8899);
        tcpSocket.setSoTimeout(1000);
        tcpReceiveThread = new TCPReceiveThread();
        tcpReceiveThread.start();
    }
 
    void tcpSend(String ctx) throws IOException {
        ctx += "\r\n";
        byte[] buf = ctx.getBytes("UTF-8");
        tcpSocket.getOutputStream().write(buf);
    }
 
    void tcpDisconnect() throws IOException {
        if(tcpReceiveThread!=null)
            tcpReceiveThread.setStopFlag();    // 具体的关闭操作在接收线程结束后执行,
    }
 
    void sendMessage(String str){
        Message msg = Message.obtain();    // 从Message池中取Message对象,用new创建会用到内存分配,影响效率
        msg.what = 1;
        msg.obj = str;
        mHandler.sendMessage(msg);
    }
 
    class TCPReceiveThread extends Thread {
        private boolean flag;
 
        public void setStopFlag(){
            flag = false;
        }
 
        @Override
        public void run(){
            try {
                flag = true;
                BufferedReader in = new BufferedReader(new InputStreamReader(tcpSocket.getInputStream(), "UTF-8"));
                sendMessage("TCP socket connection connected");
                String line = null;
                while(flag) {
                    try {
                        line = in.readLine();
                    } catch (SocketTimeoutException e){
                        //e.printStackTrace();
                        //flag = false;
                    }
                    if(line != null) {
                        System.out.println(line);
                        sendMessage(line);
                        line = null;
                    }
                }
                in.close();
                tcpSocket.close();
                tcpSocket = null;
                sendMessage("TCP socket connection closed");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 
}

Server端在JavaSE平台上实现,完整代码如下:

import java.io.*;
import java.net.*;
import java.util.*;
 
public class ChatServer {
    public static void main(String[] args){
        Thread listeningThread = new ListeningThread(8899);
        listeningThread.start();
    }
}
class ListeningThread extends Thread {
    private int port;
    private boolean flag = true;
    private ServerSocket lServerSocket;
    private List<PrintWriter> connections = new Vector<PrintWriter>();    //保存所有连结
    public ListeningThread(int aPort){
        port = aPort;
    }
    public void run(){
        try {
            lServerSocket = new ServerSocket(port);
            lServerSocket.setSoTimeout(1000);
            System.out.println("TCP Socket Start listening......");
            while(flag) {
                try {
                    Socket incoming = lServerSocket.accept();
                    System.out.println("Accept "+incoming.getInetAddress()+"("+incoming.getPort()+")");
                    //PrintWriter out = new PrintWriter(incoming.getOutputStream(),true);
                    PrintWriter out = new PrintWriter(new OutputStreamWriter(incoming.getOutputStream(), "UTF-8"),true);
                    connections.add(out);    //有新连结则将其输出流添加到连结列表中
                    out.println("Welcome to "+lServerSocket.getInetAddress()+"("+lServerSocket.getLocalPort()+")");    
                    out.flush();
                    Thread t = new ServiceThread(incoming);    //启动接收数据线程
                    t.start();
                }
                catch(SocketTimeoutException e) {
                    if(!flag)break;
                }
            }
            lServerSocket.close();
            System.exit(0);
        }
        catch(IOException e) {
            System.out.println(e);
        }
    }
    public synchronized void chatMessage(String msg) {
        Iterator<PrintWriter> iter = connections.iterator();
        while(iter.hasNext()) {
            try {
                PrintWriter out = iter.next();
                out.println(msg);
                out.flush();
            }
            catch(Exception e) {
                iter.remove();    //如果发送中出现异常,则将连结移除
            }
        }
    }
    class ServiceThread extends Thread {
        private Socket lSocket;
        public ServiceThread(Socket aSocket){
            lSocket = aSocket;
        }
        public void run(){
            try {
                BufferedReader in = new BufferedReader(new InputStreamReader(lSocket.getInputStream(), "UTF-8"));
                String line;
                while(flag){
                    line = in.readLine();
                    if(line != null){
                        System.out.println(line);    // 本地屏幕显示
                        chatMessage("Chat:"+line);    // 发送到所有客户端
                    }
                }
                lSocket.close();
                System.out.println("Thread stoped.");
            }
            catch(IOException e){
                System.out.println(e);
            }
        }
    }
}

————————————————
转载于:https://blog.csdn.net/nanoage/article/details/127967224

猜你喜欢

转载自blog.csdn.net/weixin_42602900/article/details/128653898
今日推荐