本文源码参见:https://github.com/YongYuIT/MyQQ
三、基于Socket的网络通信 这个例子实现的是客户端向服务器发送请求,服务器向客户端发送响应数据。
服务端(.net C#)
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { //第一个参数指定寻址方案,AddressFamily.InterNetwork代表的是ipv4寻址方案 //第二个参数指定连接类型,SocketType.Stream代表的是基于字节流的可靠双工连接 //第三个参数指定运输层协议,这里指定用tcp协议承载 Socket soc_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //指定套接字监听的IP及端口。IP是指客户端的IP,Any表示允许任何客户端接入。端口是本地端口。这样的设置意味着允许任意IP的客户端通过本机2222端口接入 IPEndPoint ip = new IPEndPoint(IPAddress.Any, 2222); soc_server.Bind(ip); //10表示挂起连接队列的最大长度是10 soc_server.Listen(10); while (true) { Console.WriteLine(soc_server.LocalEndPoint.ToString() + "等待客户端连接"); //当有连接接入时,创建新套接字为这个连接服务。我们将使用这个新套接字与客户端通信 //Accept方法是一个同步方法,会阻塞所在线程。也就是说,此处Accept函数调用时会停下来等待,直到有连接接入才会返回继续执行后面的代码 Socket soc_new = soc_server.Accept(); Console.WriteLine(soc_new.RemoteEndPoint.ToString() + "已连接"); string all = string.Empty; while (true) { string input = string.Empty; //建立输入缓存 byte[] receiveBytes = new byte[1024]; //每次读取1024个字节,直到读到指定的字符串,一次读取结束。 //如果没有可读取的数据,则 Receive 方法将一直处于阻止状态 //如果远程主机使用 Shutdown 方法关闭了 Socket 连接,并且所有可用数据均已收到,则 Receive 方法将立即完成并返回零字节。 int byteNums = soc_new.Receive(receiveBytes); if (byteNums == 0) { Console.WriteLine("远程主机主动关闭"); break; } //编码数据 input = Encoding.ASCII.GetString(receiveBytes, 0, byteNums); all += input; Console.WriteLine("收到:" + input); //当接收到的"[FINAL]"时,会话结束 if (input.IndexOf("[final]") > -1) { Console.WriteLine("收到结束字符串"); break; } } byte[] reply = Encoding.ASCII.GetBytes(all + ":receive succeed"); soc_new.Send(reply); //禁止客户端收发数据 soc_new.Shutdown(SocketShutdown.Both); soc_new.Close(); } soc_server.Close(); } } } 客户端(Android)
/MyQQ/res/drawable-hdpi/sharp_liaotian.xml文件:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas./apk/res/android" > <!-- 外框 --> <stroke android:width="2dp" android:color="#DD2ECCFA" /> <!-- 内容 --> <solid android:color="#ffffffff" /> <!-- 拐角 --> <corners android:radius="5dp" /> </shape>
/MyQQ/res/layout/main.xml文件:
<LinearLayout xmlns:android="http://schemas./apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffffff" android:orientation="vertical" > <LinearLayout android:id="@+id/linearLayout1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:orientation="horizontal" > <ImageView android:layout_width="25dp" android:layout_height="25dp" android:layout_gravity="center_vertical" android:background="@drawable/app_logo" > </ImageView> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:paddingLeft="20dp" android:text="与XXX的聊天" android:textSize="20dp" > </TextView> </LinearLayout> <TextView android:id="@+id/txt_message" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:layout_weight="7" android:background="@drawable/sharp_liaotian" android:padding="5dp" android:text="聊天记录" android:textColor="#DD2ECCFA" > </TextView> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:layout_weight="1" android:orientation="horizontal" > <EditText android:id="@+id/edt_input" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_margin="3dp" android:layout_weight="3" android:background="@drawable/sharp_liaotian" android:gravity="center" android:hint="请输入聊天內容" android:textColor="#4F2F4F" > </EditText> <Button android:id="@+id/btn_send_message" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_margin="3dp" android:layout_weight="1" android:gravity="center" android:text="发送" android:textColor="#DD2ECCFA" /> </LinearLayout> </LinearLayout>
/MyQQ/src/com/yongyu/myqq/IConnectSuccess.java文件:
package com.yongyu.myqq; import java.net.Socket; public interface IConnectSuccess { public void onGetSocket(Socket s); } /MyQQ/src/com/yongyu/myqq/IGetMessage.java文件:
package com.yongyu.myqq; public interface IGetMessage { public void onGetMessage(String str); } /MyQQ/src/com/yongyu/myqq/MainActivity.java文件:
package com.yongyu.myqq; import java.net.Socket; import java.util.concurrent.ArrayBlockingQueue; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity implements IConnectSuccess, IGetMessage { private Socket connect; private EditText edt_input; private ArrayBlockingQueue queue; private TextView txt_message; private Handler handler = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btn_send_message = (Button) findViewById(R.id.btn_send_message); // 每个Handler实例,都会绑定到创建他的线程中。Handler实例可以分发Message对象和Runnable对象其绑定的线程中并伺机执行 handler = new Handler(); edt_input = (EditText) findViewById(R.id.edt_input); txt_message = (TextView) findViewById(R.id.txt_message); btn_send_message.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { if (connect == null) { Toast t = Toast.makeText(MainActivity.this, "网络异常,请检查设置!", 2000); t.show(); return; } if (edt_input.getText().toString().equals("")) { Toast t = Toast.makeText(MainActivity.this, "请输入消息!", 2000); t.show(); return; } else { String str = edt_input.getText().toString(); try { MainActivity.this.queue.put(str); } catch (InterruptedException e) { Log.e("com.yongyu.myqq.MainActivity.onCreate", e.getMessage()); } txt_message.setText(txt_message.getText() + "\n发出的信息是:" + str); edt_input.setText(""); } } }); Task_Connect task_conn = new Task_Connect(this); task_conn.execute(); } @Override public void onGetSocket(Socket s) { this.connect = s; this.queue = new ArrayBlockingQueue<String>(10); Thread t_s = new Thread(new MyRunnableSendMessage(connect, queue)); t_s.start(); Thread t_r = new Thread(new MyRunnableReceiveMessage(connect, this)); t_r.start(); } @Override public void onGetMessage(String str) { myUpdateUIRunnable runnable = new myUpdateUIRunnable(str, txt_message); handler.post(runnable); } } class myUpdateUIRunnable implements Runnable { private String msg; private TextView text; public myUpdateUIRunnable(String str, TextView t) { msg = str; text = t; } @Override public void run() { text.setText(text.getText() + "\n收到的信息是:" + msg); } } /MyQQ/src/com/yongyu/myqq/MyRunnableReceiveMessage.java文件:
package com.yongyu.myqq; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; import java.net.SocketException; public class MyRunnableReceiveMessage implements Runnable { private Socket socket; private IGetMessage iget; private String message = ""; public MyRunnableReceiveMessage(Socket _socket, IGetMessage _get) { this.socket = _socket; this.iget = _get; } @Override public void run() { InputStream in = null; if (!isConnected()) return; try { // 尝试打开输入流 in = this.socket.getInputStream(); } catch (IOException e1) { return; } while (true) { try { if (!isConnected()) { break; } // 读取消息------------------------- InputStreamReader inReader = new InputStreamReader( socket.getInputStream()); BufferedReader reader = new BufferedReader(inReader); message = reader.readLine(); // -------------------------------- // 直接在异步线程里更新UI会导致:Only the original thread that created a view // hierarchy can touch its views. if (message == null) continue; this.iget.onGetMessage(message); message = ""; } catch (SocketException e) { // 如果本地套接字关闭,退出输入尝试 if (e.getMessage().toLowerCase().trim() .equals(("Socket is closed").toLowerCase().trim())) break; } catch (Exception e0) { // 继续输入尝试 continue; } } try { socket.shutdownOutput(); } catch (IOException e) { } } private boolean isConnected() { // 注意,在套接字编程中,我们要时刻注意对网络连接状态进行判断,java中的Socket提供的isClosed(),isConnected(),isInputShutdown(),sOutputShutdown()都是用于检查本地Socket状态的,不是用于检查远程Socket状态的,所以是无效的的。 // 要检查远程Socket的状态可以用: try { socket.sendUrgentData(0xFF); return true; } catch (Exception e) { return false; } } }
/MyQQ/src/com/yongyu/myqq/MyRunnableSendMessage.java文件:
package com.yongyu.myqq; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.net.SocketException; import java.util.concurrent.ArrayBlockingQueue; public class MyRunnableSendMessage implements Runnable { private Socket socket; private ArrayBlockingQueue queue; public MyRunnableSendMessage(Socket _socket, ArrayBlockingQueue _que) { this.socket = _socket; this.queue = _que; } @Override public void run() { OutputStream out; if (!isConnected()) return; try { // 尝试打开输出流 out = this.socket.getOutputStream(); } catch (IOException e1) { return; } while (true) { try { String message = (String) queue.take(); if (!isConnected()) { break; } out.write((message).getBytes()); } catch (SocketException e) { // 如果本地套接字关闭,退出输出尝试 if (e.getMessage().toLowerCase().trim() .equals(("Socket is closed").toLowerCase().trim())) break; } catch (Exception e0) { // 继续输出尝试 continue; } } try { // 对于在socket上建立的输入(输出)流一旦调用close函数关闭,会造成socket关闭。这样基于socket的输出(输入)流也将不可用 // 调用socket.shutdownOutputStream()只会单方面关闭输出流,不会导致socket关闭,此时基于socket的输入流还是可用的 socket.shutdownOutput(); } catch (IOException e) { } } private boolean isConnected() { // 注意,在套接字编程中,我们要时刻注意对网络连接状态进行判断,java中的Socket提供的isClosed(),isConnected(),isInputShutdown(),sOutputShutdown()都是用于检查本地Socket状态的,不是用于检查远程Socket状态的,所以是无效的的。 // 要检查远程Socket的状态可以用: try { socket.sendUrgentData(0xFF); return true; } catch (Exception e) { return false; } } } /MyQQ/src/com/yongyu/myqq/Task_Connect.java文件:
package com.yongyu.myqq; import java.net.Socket; import android.os.AsyncTask; import android.util.Log; public class Task_Connect extends AsyncTask<Void, Void, Socket> { private IConnectSuccess iget; public Task_Connect(IConnectSuccess _iget) { this.iget = _iget; } @Override protected Socket doInBackground(Void... arg0) { Socket connect = null; try { connect = new Socket("192.168.10.111", 2222); } catch (Exception e) { connect = null; Log.e(" com.yongyu.myqq.Task_Connect.doInBackground", e.getMessage()); } return connect; } @Override protected void onPostExecute(Socket result) { this.iget.onGetSocket(result); } } 效果:
|