在我們使用okHttp等網(wǎng)絡框架去請求網(wǎng)絡時,都是我們Android客戶端主動發(fā)起一次請求,然后服務器返回相應的數(shù)據(jù)給我們客戶端去使用,即經(jīng)常說起的一次請求一次響應,和Servlet一樣。這樣的請求都是基于HTTP協(xié)議去實現(xiàn)的,在HTTP模式下,只能是客戶端主動去請求數(shù)據(jù),服務端才會給你返回你想要的數(shù)據(jù),也就是說,服務端只能是你要了我才能給。我們主動久了,總會希望服務端也主動一點,給我們主動發(fā)一點數(shù)據(jù)。試想一下,和妹子一起么么噠的時候,妹子在你耳邊輕輕的嬌嗔:“我要,我要,我要給你,我就要給你嘛!”,是不是超級刺激?為了解決妹子的需求,我們肯定要使上九牛二虎之力,把妹子整的服服帖帖的,一陣巫山云雨,不可描述之后,妹子嬌紅著臉,把腦袋埋在你的胸口,宛如一只小綿羊。emmmmm,舒服了?;赥CP協(xié)議的Socket就是為了幫助我們解決這種需求的。它能讓你和妹子相互的“要”,相互的“給”,還能超級持久哦。
下面就簡單的在代碼里介紹一下Socket長連接的使用,栗子要實現(xiàn)的效果是:安卓客戶端給服務端發(fā)送一個消息,服務端收到消息后立馬給我返回一個消息。
看圖說話:我代表客戶端,媳婦兒代表服務端
[圖片上傳中...(image-810b3b-1532078403207-0)]

具體實現(xiàn):
下面是MainActivity的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<Button
android:id="@+id/bt_sendData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="發(fā)送數(shù)據(jù)"
android:layout_alignParentTop="true"/>
<LinearLayout
android:id="@+id/ll_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/bt_sendData"
android:layout_marginTop="20dp"
android:orientation="vertical"></LinearLayout>
</RelativeLayout>
接著繼續(xù)看MainActivity的代碼:
1.首先調(diào)用doConnect()方法建立服務器連接,由于網(wǎng)絡請求要寫在子線程中,所以這里新建了一個線程,在子線程中建立連接。在這個連接中使用了我本機的ip地址(用手機測試時,手機需連接wifi,和電腦處于同一個局域網(wǎng)中),以及6666端口,設置了5秒的響應超時。
private void doConnect() {
//子線程請求網(wǎng)絡
new Thread(new Runnable() {
@Override
public void run() {
try {
Socket socket = new Socket();
//建立Socket連接,需要服務端IP地址和端口號
socket.connect(new InetSocketAddress("192.168.1.188", 6666), 5 * 1000);
//通過Socket實例獲取輸入輸出流,作為和服務器交換數(shù)據(jù)的通道
bis = new BufferedInputStream(socket.getInputStream());
bos = new BufferedOutputStream(socket.getOutputStream());
readThread = new ReadThread();
readThread.start();
Log.i("Socket","socket連接成功");
} catch (Exception e) {
e.printStackTrace();
Log.i("Socket","socket連接失敗");
}
}
}).start();
}
2.新建子線程WriteThread,負責向服務器發(fā)送數(shù)據(jù), showText()是顯示客服端與服務端通訊數(shù)據(jù)的方法。
//啟動一個線程,在子線程中發(fā)送數(shù)據(jù)給服務端
private class WriteRead extends Thread{
@Override
public void run() {
try {
String iSay = "我說:媳婦兒媳婦兒,你還行嗎?";
bos.write(iSay.getBytes());
//記得一定要flush一下
bos.flush();
showText(FROM_CLIENT, iSay);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.客服端通過WriteRead 向服務端發(fā)送數(shù)據(jù)后,我們肯定就是要去建立一個測試的服務端去接收數(shù)據(jù)。
新建Java類SocketServer,需要注意的是,這個類的位置需要與與包名平級,即放在As的Java目錄下。
代碼中的端口號需要與Android中設置的端口號一樣,即6666。
在連接建立后立馬創(chuàng)建了一個服務端的ReadThread去讀取信息,讀取到信息后,又立馬使用bos給客戶端回寫了一條消息。這時,我們就需要在客戶端去創(chuàng)建接受消息的方法了。
public class SocketServer {
private static BufferedOutputStream bos;
private static BufferedInputStream bis;
private static ReadThread readThread;
public static void main(String args[]){
try{
//通過accept方法獲取socket實例,只有在程序獲得到一個socket實例后,程序才會繼續(xù)向下執(zhí)行,否則會一直阻塞在accept方法
ServerSocket serverSocket = new ServerSocket(6666);
Socket socket = serverSocket.accept();
//通過Socket實例獲取輸入輸出流,作為和服務器交換數(shù)據(jù)的通道
bis = new BufferedInputStream(socket.getInputStream());
bos = new BufferedOutputStream(socket.getOutputStream());
readThread = new ReadThread();
readThread.start();
}catch (Exception e){
e.printStackTrace();
System.out.print("完了完了,我GG了");
}
}
//啟動一個線程,一直讀取從客戶端發(fā)送過來的消息
private static class ReadThread extends Thread{
@Override
public void run() {
while (true){
byte[] data = new byte[1024];
int size = 0;
try {
//收到客服端發(fā)送的消息后,返回一個消息給客戶端
while((size = bis.read(data)) != -1){
String str = new String(data,0,size);
System.out.print("老公對我說:"+str);
bos.write("媳婦兒說:死鬼!我還要,不夠嘛。".getBytes());
bos.flush();
}
} catch (IOException e) {
e.printStackTrace();
System.out.print("完了完了,我GG了");
}
}
}
}
4.回到MainActivity去創(chuàng)建接收服務端返回消息的方法:即客戶端的ReadThread,同樣的也需要啟動一個線程,因為不管是使用bis去讀取數(shù)據(jù),還是使用bos去讀取數(shù)據(jù),都是需要去操作網(wǎng)絡的,而我們Android中只能在子線程操作網(wǎng)絡。
讀取到數(shù)據(jù)后,使用Handler將信息傳遞回主線程顯示
//啟動一個線程,一直讀取從服務端發(fā)送過來的消息
private class ReadThread extends Thread {
@Override
public void run() {
while (true) {
byte[] data = new byte[1024];
int size = 0;
try {
//收到客服端發(fā)送的消息后,返回一個消息給客戶端
while((size = bis.read(data)) != -1) {
String str = new String(data, 0, size);
Message msg = new Message();
msg.obj = new String(str);
mHandler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5.下面是MainActivity中所有的代碼:
public class MainActivity extends AppCompatActivity {
private Button bt_sendData;
private LinearLayout ll_content;
private BufferedOutputStream bos;
private BufferedInputStream bis;
private ReadThread readThread;
private WriteRead writeRead;
private int FROM_CLIENT = 0;
private int FROM_SERVER = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
doConnect();
}
private void initView() {
bt_sendData = (Button) findViewById(R.id.bt_sendData);
ll_content = (LinearLayout) findViewById(R.id.ll_content);
bt_sendData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
writeRead = new WriteRead();
writeRead.start();
}
});
}
private void doConnect() {
//子線程請求網(wǎng)絡
new Thread(new Runnable() {
@Override
public void run() {
try {
Socket socket = new Socket();
//建立Socket連接,需要服務端IP地址和端口號
socket.connect(new InetSocketAddress("192.168.1.188", 6666), 5 * 1000);
//通過Socket實例獲取輸入輸出流,作為和服務器交換數(shù)據(jù)的通道
bis = new BufferedInputStream(socket.getInputStream());
bos = new BufferedOutputStream(socket.getOutputStream());
readThread = new ReadThread();
readThread.start();
Log.i("Socket","socket連接成功");
} catch (Exception e) {
e.printStackTrace();
Log.i("Socket","socket連接失敗");
}
}
}).start();
}
//啟動一個線程,一直讀取從服務端發(fā)送過來的消息
private class ReadThread extends Thread {
@Override
public void run() {
while (true) {
byte[] data = new byte[1024];
int size = 0;
try {
//收到客服端發(fā)送的消息后,返回一個消息給客戶端
while((size = bis.read(data)) != -1) {
String str = new String(data, 0, size);
Message msg = new Message();
msg.obj = new String(str);
mHandler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//啟動一個線程,在子線程中發(fā)送數(shù)據(jù)給服務端
private class WriteRead extends Thread{
@Override
public void run() {
try {
String iSay = "我說:媳婦兒媳婦兒,你還行嗎?";
bos.write(iSay.getBytes());
//記得一定要flush一下
bos.flush();
showText(FROM_CLIENT, iSay);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
String xifuerSay = msg.obj.toString();
showText(FROM_SERVER, xifuerSay);
}
};
//顯示客戶端與服務端之間的對話
private void showText(final int type,final String text) {
runOnUiThread(new Runnable() {
@Override
public void run() {
TextView tv = new TextView(MainActivity.this);
tv.setText(text);
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
tv.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
if (type == FROM_CLIENT) {
tv.setTextColor(Color.parseColor("#000000"));
} else if (type == FROM_SERVER) {
tv.setTextColor(Color.parseColor("#ff0000"));
}
ll_content.addView(tv);
}
});
}
}
6.主程序就是一個MainActivity和一個SocketServer類,測試的時候首先需要在As中啟動服務端SocketServer,然后再啟動我們的APP程序。
7.這個程序有很多的不足,比如粗暴的創(chuàng)建線程,退出App后重新進入點擊按鈕,就收不到服務端返回的消息了。下篇文章將在服務中結(jié)合線程池去解決這些問題。