其实是个很简单的事情,服务端(PC)用一个端口新建一个socket然后等待客户端设备过来连接,而客户端(Android)只需用服务端IP和服务端端口去创建socket连接就行。

可是问题是我花了两小时一直就没连上…….刚刚觉得可能是IP地址的问题,尝试了使用120.0.0.1(回环地址),尝试了10.0.2.2(模拟机和电脑互联的地址),192.168.1.27(PC的无线网卡IP)结果都连不上。后来百度无果,问了一位大神,一语道破,原来是PC的防火墙打开了,导致局域网其他设备无法通过socket连接到PC

下面板书下完整的代码,包括客户端(Android程序)和服务端(java程序),ps:是某博客摘抄的,然后后面会有自己的理解。

客户端

package com.example.socketclient;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends Activity {

    EditText input;
    TextView show;
    Button send;
    Handler handler;
    // 定义与服务器通信的子线程
    ClientThread clientThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        input = (EditText) findViewById(R.id.input);
        show = (TextView) findViewById(R.id.show);
        send = (Button) findViewById(R.id.send);
        handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                // 如果消息来自子线程
                if (msg.what == 0x123) {
                    // 将读取的内容追加显示在文本框中
                    show.append("\n" + msg.obj.toString());
                }
            }
        };
        clientThread = new ClientThread(handler);
        // 客户端启动ClientThread线程创建网络连接、读取来自服务器的数据
        new Thread(clientThread).start();
        send.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                try {
                    // 当用户按下按钮之后,将用户输入的数据封装成Message
                    // 然后发送给子线程Handler
                    Message msg = new Message();
                    msg.what = 0x345;
                    msg.obj = input.getText().toString();
                    clientThread.revHandler.sendMessage(msg);
                    input.setText("");

                } catch (Exception e) {

                }
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}
package com.example.socketclient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

public class ClientThread implements Runnable {
    private Socket s;
    // 定义向UI线程发送消息的Handler对象
    Handler handler;
    // 定义接收UI线程的Handler对象
    Handler revHandler;
    // 该线程处理Socket所对用的输入输出流
    BufferedReader br = null;
    OutputStream os = null;

    public ClientThread(Handler handler) {
        this.handler = handler;
    }

    @Override
    public void run() {
        s = new Socket();
        try {
            s.connect(new InetSocketAddress("192.168.1.27", 51706), 5000);//10.0.2.2,192.168.1.27
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            os = s.getOutputStream();
            // 启动一条子线程来读取服务器相应的数据
            new Thread() {

                @Override
                public void run() {
                    String content = null;
                    // 不断的读取Socket输入流的内容
                    try {
                        while ((content = br.readLine()) != null) {
                            // 每当读取到来自服务器的数据之后,发送的消息通知程序
                            // 界面显示该数据
                            Message msg = new Message();
                            msg.what = 0x123;
                            msg.obj = content;
                            handler.sendMessage(msg);
                        }
                    } catch (IOException io) {
                        io.printStackTrace();
                    }
                }

            }.start();
            // 为当前线程初始化Looper
            Looper.prepare();
            // 创建revHandler对象
            revHandler = new Handler() {

                @Override
                public void handleMessage(Message msg) {
                    // 接收到UI线程的中用户输入的数据
                    if (msg.what == 0x345) {
                        // 将用户在文本框输入的内容写入网络
                        try {
                            os.write((msg.obj.toString() + "\r\n").getBytes("gbk"));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }

            }; 
            // 启动Looper
            Looper.loop();

        } catch (SocketTimeoutException e) {
            Message msg = new Message();
            msg.what = 0x123;
            msg.obj = "网络连接超时!";
            handler.sendMessage(msg);
        } catch (IOException io) {
            io.printStackTrace();
        }

    }
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <EditText
        android:id="@+id/input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="input" />
    <Button 
        android:id="@+id/send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="send"
        android:layout_below="@id/input"/>
    <TextView 
        android:id="@+id/show"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/send"/>

</RelativeLayout>

服务端

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class MyService {

    // 定义保存所有的Socket
    public static List<Socket> socketList = new ArrayList<Socket>();

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(51706);
        while(true){
            System.out.println("in connecting");
            Socket s=server.accept();
            socketList.add(s);
            //每当客户端连接之后启动一条ServerThread线程为该客户端服务
            new Thread(new ServiceThread(s)).start();

        }
    }

}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class ServiceThread implements Runnable {

    // 定义当前线程处理的Socket
    Socket s = null;
    // 该线程所处理的Socket所对应的输入流
    BufferedReader br = null;

    public ServiceThread(Socket s) {
        this.s = s;
        try {
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {

        String content = null;
        //采用循环不断的从Socket中读取客户端发送过来的数据
        while((content=readFromClient())!=null){
            //遍历socketList中的每个Socket
            //将读取到的内容每个向Socket发送一次
            for(Socket s:MyService.socketList){
                OutputStream os;
                try {
                    os = s.getOutputStream();
                    os.write((content+"\n").getBytes("gbk"));
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        }

    }

    // 定义读取客户端的信息
    public String readFromClient() {
        try {
            return br.readLine();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

一些理解

  • 服务端先监听到来的客户端连接,来一个客户端,就开启一个线程为它服务。服务过程:客户端发一串字符到服务端,然后服务端再返回同样的字符。
  • 整个过程相比c语言socket编程的代码复杂度上简单很多,socket编程中传输的其实是字节流,从读写函数就可以看到,写入的是byte数组,那么客户端和服务端必须规定使用同样的字符集编解码,才能做到不出现乱码。就是说客户端的对二进制流的编码和服务端对二进制流的解码的字符集要一致,例子中采用的都是gbk。