TCP通信的雙方需要建立連接,所以先由一方監(jiān)聽某個端口,等待其他設(shè)備來連接,這一方稱為服務(wù)器端。另一方向服務(wù)器端發(fā)起連接請求,稱為客戶端。服務(wù)器端接受客戶端的連接請求后,雙方之間的連接建立起來。連接建立后,雙方對于連接的使用是相同的,都可以通過連接發(fā)送和接收數(shù)據(jù)。
如果雙方通信時沒有像HTTP協(xié)議這種一問一答的固定模式,就需要隨時接收和處理對方發(fā)來的數(shù)據(jù),所以要把接收和處理數(shù)據(jù)的工作在一個單獨的線程中執(zhí)行。如果服務(wù)器端想同時與多個客戶端通信,要對每個客戶端的連接建立一個接收線程。
下面通過一個簡單的聊天室原型來演示如何編程實現(xiàn)TCP通信。聊天室系統(tǒng)包含一個服務(wù)器和多個客戶端。服務(wù)器對從一個客戶端接收的信息,要轉(zhuǎn)發(fā)到所有客戶端??蛻舳艘褟姆?wù)器接收的信息顯示給用戶,并把用戶輸入的信息發(fā)送到服務(wù)器。為實現(xiàn)這一功能,系統(tǒng)的結(jié)構(gòu)要像下面這個圖這樣設(shè)計。
聊天服務(wù)器端有一個TCP監(jiān)聽線程,當(dāng)有客戶端發(fā)來連接請求時,負(fù)責(zé)接受客戶端的連接請求并創(chuàng)建接收線程。同時,用一個列表記下所有的客戶端連接。這樣,接收線程就能夠?qū)⒔邮盏降男畔l(fā)往列表中保存的所有客戶端??蛻舳艘灿袃蓚€線程,接收線程負(fù)責(zé)從服務(wù)器接收數(shù)據(jù)并顯示給用戶,主線程接收用戶的輸入并發(fā)往服務(wù)器。
服務(wù)器端運行在JavaSE上,的具體代碼是這樣的:
監(jiān)聽線程ListeningThread首先創(chuàng)建一個ServerSocket對象,然后循環(huán)調(diào)用它的accept方法等待接受客戶端的連接請求。當(dāng)有客戶端發(fā)來連接請求時,accept方法返回一個Socket對象。監(jiān)聽線程把Socket對象添加到連接列表connections,并創(chuàng)建一個接收線程ServiceThread。
接收線程循環(huán)調(diào)用readLine,一旦客戶端有消息發(fā)來,readLine就會返回發(fā)來的消息內(nèi)容,然后調(diào)用chatMessage發(fā)送到所有客戶端(包括自己)。
chatMessage方法遍歷客戶端列表connections,把客戶端發(fā)來的消息轉(zhuǎn)發(fā)到所有客戶端。
客戶端是一個Android應(yīng)用,界面是這樣的:最上面一行文本框中輸入服務(wù)器的IP地址;下面TCP一行的三個按鈕控制建立連接、發(fā)送信息、斷開連接;再下面UDP一行三個按鈕用于測試UDP通信方式;再下面的Content文本框是要發(fā)送的聊天消息;最下面的文本是所有聊天內(nèi)容。
當(dāng)點擊TCP的Connect按鈕時,以服務(wù)器端的IP地址和端口號為參數(shù)創(chuàng)建一個Socket對象,這樣就會向服務(wù)器發(fā)送一個建立連接的請求。如果服務(wù)器接受請求,那么連接就成功建立,Socket對象也會創(chuàng)建成功,否則會產(chǎn)生異常,轉(zhuǎn)入異常處理流程。
有了Socket對象后,接著創(chuàng)建一個接收數(shù)據(jù)的線程。注意這部分代碼也需要用AsyncTask異步執(zhí)行,不能占用主線程。接收線程中循環(huán)調(diào)用in.readLine方法不斷讀取服務(wù)器發(fā)來的消息,一旦有消息發(fā)過來,該方法就會返回消息內(nèi)容。接著再調(diào)用sendMessage把消息內(nèi)容傳給主線程顯示,因為子線程中不能直接操作界面控件。
傳遞消息內(nèi)容用的Android的Handler機制,主線程中創(chuàng)建一個Handler對象,并在它的handleMessage方法中接收消息。子線程中調(diào)用該Handler對象的sendMessage方法傳遞消息。這樣就能在線程間傳遞消息了。
當(dāng)點擊TCP的Send按鈕時,代碼是把Content文本框中的字符串通過Socket連接發(fā)送到服務(wù)器。
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); // 不能在主線程中執(zhí)行網(wǎng)絡(luò)操作
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(); // 具體的關(guān)閉操作在接收線程結(jié)束后執(zhí)行,
}
void sendMessage(String str){
Message msg = Message.obtain(); // 從Message池中取Message對象,用new創(chuàng)建會用到內(nèi)存分配,影響效率
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平臺上實現(xiàn),完整代碼如下:
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>(); //保存所有連結(jié)
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); //有新連結(jié)則將其輸出流添加到連結(jié)列表中
out.println("Welcome to "+lServerSocket.getInetAddress()+"("+lServerSocket.getLocalPort()+")");
out.flush();
Thread t = new ServiceThread(incoming); //啟動接收數(shù)據(jù)線程
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(); //如果發(fā)送中出現(xiàn)異常,則將連結(jié)移除
}
}
}
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); // 發(fā)送到所有客戶端
}
}
lSocket.close();
System.out.println("Thread stoped.");
}
catch(IOException e){
System.out.println(e);
}
}
}
}
完整代碼下載:文章來源:http://www.zghlxwxcb.cn/news/detail-484135.html
?Android網(wǎng)絡(luò)功能開發(fā)-Socket編程接口使用的例子文章來源地址http://www.zghlxwxcb.cn/news/detail-484135.html
到了這里,關(guān)于Android網(wǎng)絡(luò)功能開發(fā)(6)——TCP協(xié)議通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!