服务器端:
先启动一个服务器端的socket ServerSocket svr = new ServerSocket(8989);
开始侦听请求 Socket s = svr.accept();
取得输入和输出 DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());//向客户端发送数据
Socket 的交互通过流来完成,即是说传送的字节流,因此任何文件都可以在上面传送。谁打开的记得要关上。
用DataInputStream/DataOutputStream来进行包装是因为我们想要他们对基本数据类型的读写功能readInt(),writeInt(),readUTF(),writeUTF()等等。
客户端:
发起一个socket连接 Socket s = new Socket("192.168.1.200",8989);
取得输入和输出 DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
之后就可以相互通信了。谁打开的记得要关上。
TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在两端之间形成网络虚拟链路。
在两个通信实体没有建立虚拟链路之前,必须有一个实体先“主动”,主动接收来自其它实体的通信请求。而这个能接受其它实体连接请求的类是ServerSocket,用于监听来自客户端的Socket连接,如果没有连接则一直处于等待状态。accept():如果接收到一个客户端Socket的连接请求,则返回一个与连接客户端Socket对应的Socket;否则一直处于等待状态,线程也被阻塞。
setSoTimeout()设置读写操作完成的超时时长,即在该时间限制内没有完成读写操作则会抛出SocketTimeoutException。
而若要为Socket连接服务器的指定超时时长,由于Socket的构造器里没有该参数,所以需要先建立一个无连接的Socket,再调用Socket的connect()方法来连接远程服务器。而connect()方法可以接受一个超时时长参数:Socket s=new Socket(); s.connect(new InetSocketAddress(host,port),10000);
阻塞:用传统的BufferedReader的readLine()读取数据时,在该方法成功返回前,线程一直被阻塞,所以服务器应为每个Socket单独启动一条线程,没条线程负责与一个客户端进行通信。
客户端读取服务器数据的线程同样也会被阻塞,所以也应该单独启动一条线程,专门负责读取服务器的数据。
一个C/S聊天室,就是Socket多线程的应用。服务器端包含多条线程,每个Socket对应一条线程,该线程负责读取Socket对应输入流的数据(客户端发来的数据),并将读到的数据向每个Socket输出流发送一遍。
服务器端
MyServer:
public class MyServer { public static ArrayList<Socket> list=new ArrayList<>(); public static void main(String[] args) throws IOException { ServerSocket ss=new ServerSocket(30000); while (true){ Socket s=ss.accept();//此处会一直阻塞 list.add(s); //每当客户端连接成功后启动一条ServerThread线程为客户端服务 new Thread(new ServerThread(s)).start(); } } }
ServerThread:
public class ServerThread implements Runnable { Socket s=null; BufferedReader br=null; public ServerThread(Socket s) throws IOException { this.s=s; br=new BufferedReader(new InputStreamReader(s.getInputStream(),"utf-8")); } @Override public void run() { String content=null; try { while((content=ReadFromClient())!=null){ //将读到的内容向每个Socket发送一次 for (Iterator<Socket> it=MyServer.list.iterator();it.hasNext();){ Socket s=it.next(); OutputStream out=s.getOutputStream(); out.write((content+"\n").getBytes()); } } } catch (IOException e) { e.printStackTrace(); } } private String ReadFromClient() { try { return br.readLine(); } catch (IOException e) { //如果捕获到异常,说明该客户端已关闭 e.printStackTrace(); MyServer.list.remove(s); } return null; } }
Activity:
public class CSActivity extends AppCompatActivity { EditText input; TextView show; Button send; Handler handler; //与服务器通讯的子线程 ClientThread clientThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_cs); show= (TextView) findViewById(R.id.tv_content); input= (EditText) findViewById(R.id.et_content); send= (Button) findViewById(R.id.btn_send); handler=new Handler(){ @Override public void handleMessage(Message msg) { if (msg.what==0x123){ //消息来自子线程 show.append("\n"+msg.obj.toString()); } } }; clientThread=new ClientThread(handler); new Thread(clientThread).start();//客户端启动新线程,读取来自服务器的数据 send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //为了避免UI线程被阻塞,所以该程序将建立网络连接、与服务器通讯等工作都交给ClientThread线程完成 //所以要将用户的输入数据封装成Message,然后发送给子线程的Handler Message msg=new Message(); msg.what=0x345; msg.obj=input.getText().toString(); clientThread.revHandler.sendMessage(msg); input.setText(""); } }); }
public class ClientThread implements Runnable { public Handler revHandler; private Handler handler; private Socket s; BufferedReader br=null; OutputStream os=null; public ClientThread(Handler handler) { this.handler=handler; } @Override public void run() { try { s=new Socket("192.168.1.88",30000); br=new BufferedReader(new InputStreamReader(s.getInputStream())); os=s.getOutputStream(); //启动一条子线程读取服务器响应的数据 new Thread(){ @Override public void run() { String content=null; try { while ((content=br.readLine())!=null){ //从服务器读到的数据之后,发送消息通知UI更改界面 Message msg=new Message(); msg.what=0x123; msg.obj=content; handler.sendMessage(msg); } } catch (IOException e) { e.printStackTrace(); } } }.start(); //为当前线程初始化Looper Looper.prepare(); revHandler=new Handler(){ @Override public void handleMessage(Message msg) { if (msg.what==0x345){ try { os.write((msg.obj.toString()+"\r\n").getBytes()); } catch (IOException e) { e.printStackTrace(); } } } }; //启动Looper Looper.loop(); } catch (IOException e) { e.printStackTrace(); } } }两条线程: handler读取Socket对应输入流中的数据,并显示在界面上;而 revHandler负责响应用户动作,将用户输入的数据写入Socket对应的输出流中。
为避免UI线程被阻塞,由ClientThread完成建立网络连接、与服务器通信的工作。
Android中提供的HttpURLConnection和HttpClient接口可以用来开发HTTP程序。
首先需要明确的是,Http通信中的POST和GET请求方式的不同。GET可以获得静态页面,也可以把参数放在URL字符串后面,传递给服务器。而POST方法的参数是放在Http请求中。因此,在编程之前,应当首先明确使用的请求方法,然后再根据所使用的方式选择相应的编程方式。默认使用GET
HttpURLConnection是继承于URLConnection类,二者都是抽象类。其对象主要通过URL的openConnection方法获得。
//以Get方式上传参数 public class Activity03 extends Activity { private final String DEBUG_TAG = "Activity03"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.http); TextView mTextView = (TextView)this.findViewById(R.id.TextView_HTTP); //http地址"?par=abcdefg"是我们上传的参数 String httpUrl = "http://192.168.1.110:8080/httpget.jsp?par=abcdefg"; //获得的数据 String resultData = ""; URL url = null; try { //构造一个URL对象 url = new URL(httpUrl); } catch (MalformedURLException e) { Log.e(DEBUG_TAG, "MalformedURLException"); } if (url != null) { try { // 使用HttpURLConnection打开连接 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); //得到读取的内容(流) InputStreamReader in = new InputStreamReader(urlConn.getInputStream()); // 为输出创建BufferedReader BufferedReader buffer = new BufferedReader(in); String inputLine = null; //使用循环来读取获得的数据 while (((inputLine = buffer.readLine()) != null)) { //我们在每一行后面加上一个"\n"来换行 resultData += inputLine + "\n"; } //关闭InputStreamReader in.close(); //关闭http连接 urlConn.disconnect(); //设置显示取得的内容 if ( resultData != null ) { mTextView.setText(resultData); } else { mTextView.setText("读取的内容为NULL"); } } catch (IOException e) { Log.e(DEBUG_TAG, "IOException"); } } else { Log.e(DEBUG_TAG, "Url NULL"); } }
如果需要使用POST方式,则需要setRequestMethod设置为POST
//以post方式上传参数 public class Activity04 extends Activity { private final String DEBUG_TAG = "Activity04"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.http); TextView mTextView = (TextView)this.findViewById(R.id.TextView_HTTP); //http地址"?par=abcdefg"是我们上传的参数 String httpUrl = "http://192.168.1.110:8080/httpget.jsp"; //获得的数据 String resultData = ""; URL url = null; try { //构造一个URL对象 url = new URL(httpUrl); } catch (MalformedURLException e) { Log.e(DEBUG_TAG, "MalformedURLException"); } if (url != null) { try { // 使用HttpURLConnection打开连接 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); //因为这个是post请求,设立需要设置为true urlConn.setDoOutput(true); urlConn.setDoInput(true); // 设置以POST方式 urlConn.setRequestMethod("POST"); // Post 请求不能使用缓存 urlConn.setUseCaches(false); urlConn.setInstanceFollowRedirects(true); // 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的 urlConn.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); // 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成, // 要注意的是connection.getOutputStream会隐含的进行connect。 urlConn.connect(); //DataOutputStream流 DataOutputStream out = new DataOutputStream(urlConn.getOutputStream()); //要上传的参数 String content = "par=" + URLEncoder.encode("ABCDEFG", "gb2312"); //将要上传的内容写入流中 out.writeBytes(content); //刷新、关闭 out.flush(); out.close(); //获取数据 BufferedReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream())); String inputLine = null; //使用循环来读取获得的数据 while (((inputLine = reader.readLine()) != null)) { //我们在每一行后面加上一个"\n"来换行 resultData += inputLine + "\n"; } reader.close(); //关闭http连接 urlConn.disconnect(); //设置显示取得的内容 if ( resultData != null ) { mTextView.setText(resultData); } else { mTextView.setText("读取的内容为NULL"); } } catch (IOException e) { Log.e(DEBUG_TAG, "IOException"); } } else { Log.e(DEBUG_TAG, "Url NULL"); } } }
使用Apache提供的HttpClient接口同样可以进行HTTP操作。
对于GET和POST请求方法的操作有所不同。GET方法的操作代码示例如下:
public class Activity02 extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.http); TextView mTextView = (TextView) this.findViewById(R.id.TextView_HTTP); // http地址 String httpUrl = "http://192.168.1.110:8080/httpget.jsp?par=HttpClient_android_Get"; //HttpGet连接对象 HttpGet httpRequest = new HttpGet(httpUrl); try { //取得HttpClient对象 HttpClient httpclient = new DefaultHttpClient(); //请求HttpClient,取得HttpResponse HttpResponse httpResponse = httpclient.execute(httpRequest); //请求成功 if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { //取得返回的字符串 String strResult = EntityUtils.toString(httpResponse.getEntity()); mTextView.setText(strResult); } else { mTextView.setText("请求错误!"); } } catch (ClientProtocolException e) { mTextView.setText(e.getMessage().toString()); } catch (IOException e) { mTextView.setText(e.getMessage().toString()); } catch (Exception e) { mTextView.setText(e.getMessage().toString()); } } }使用POST方法进行参数传递时,需要使用NameValuePair来保存要传递的参数,另外,还需要设置所使用的字符集。进行字符编码
public class Activity03 extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.http); TextView mTextView = (TextView) this.findViewById(R.id.TextView_HTTP); // http地址 String httpUrl = "http://192.168.1.110:8080/httpget.jsp"; //HttpPost连接对象 HttpPost httpRequest = new HttpPost(httpUrl); //使用NameValuePair来保存要传递的Post参数 List<NameValuePair> params = new ArrayList<NameValuePair>(); //添加要传递的参数 params.add(new BasicNameValuePair("par", "HttpClient_android_Post")); try { //设置字符集 HttpEntity httpentity = new UrlEncodedFormEntity(params, "gb2312"); //请求httpRequest httpRequest.setEntity(httpentity); //取得默认的HttpClient HttpClient httpclient = new DefaultHttpClient(); //取得HttpResponse HttpResponse httpResponse = httpclient.execute(httpRequest); //HttpStatus.SC_OK表示连接成功 if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { //取得返回的字符串 String strResult = EntityUtils.toString(httpResponse.getEntity()); mTextView.setText(strResult); } else { mTextView.setText("请求错误!"); } } catch (ClientProtocolException e) { mTextView.setText(e.getMessage().toString()); } catch (IOException e) { mTextView.setText(e.getMessage().toString()); } catch (Exception e) { mTextView.setText(e.getMessage().toString()); } } }
HttpClient实际上是对Java提供方法的一些封装,在HttpURLConnection中的输入输出流操作,在这个接口中被统一封装成了HttpPost(HttpGet)和HttpResponse,这样,就减少了操作的繁琐性。