- 访问服务器的方法很多,基于http协议的,像HttpURLConnection及HttpClient,还有就是现在比较常见的socket接口访问。而HttpURLConnection及HttpClient又有GET和POST之分,GET访问直接把需要传递的参数以明文的方式连接在url后面就可以,因此GET请求的发送不需要像POST请求那样使用DataOutputStream进行writeBytes()和flush(),而是在HttpURLConnection的getInputStream()时自动发送出去。对于读取服务器响应回来的数据,GET请求和POST都可以使用InputStreamReader和BufferReader就可以读取到。需要注意的是POST请求发送需要使用OutputStream来进行。
- http协议是不安全的,因为它的传递方式是以明文的形式进行的,可以通过抓包工具很容易地截取到传输的数据。现在主流的传输协议都是http加SSL安全协议,即https传输协议。
1、在讲解之前,先温习一遍网络基本知识。
1.1 计算机网络分层
计算机网络分为五层:物理层、数据链路层、网络层、运输层、应用层
其中:
- 网络层:负责根据IP找到目的地址的主机
- 运输层:通过端口把数据传到目的主机的目的进程,来实现进程与进程之间的通信
1.2 端口号(PORT)
端口号规定为16位,即允许一个IP主机有2的16次方65535个不同的端口。其中:
- 0~1023:分配给系统的端口号
我们不可以乱用
1024~49151:登记端口号,主要是让第三方应用使用
但是必须在IANA(互联网数字分配机构)按照规定手续登记,
49152~65535:短暂端口号,是留给客户进程选择暂时使用,一个进程使用完就可以供其他进程使用。
扫描二维码关注公众号,回复: 1507482 查看本文章
在Socket使用时,可以用1024~65535的端口号
1.3 C/S结构
- 定义:即客户端/服务器结构,是软件系统体系结构
- 作用:充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统的通讯开销。
Socket正是使用这种结构建立连接的,一个套接字接客户端,一个套接字接服务器。
如图:
可以看出,Socket的使用可以基于TCP或者UDP协议。
1.4 TCP协议
- 定义:Transmission Control Protocol,即传输控制协议,是一种传输层通信协议
基于TCP的应用层协议有FTP、Telnet、SMTP、HTTP、POP3与DNS。
特点:面向连接、面向字节流、全双工通信、可靠
面向连接:指的是要使用TCP传输数据,必须先建立TCP连接,传输完成后释放连接,就像打电话一样必须先拨号建立一条连接,打完后挂机释放连接。
全双工通信:即一旦建立了TCP连接,通信双方可以在任何时候都能发送数据。
可靠的:指的是通过TCP连接传送的数据,无差错,不丢失,不重复,并且按序到达。
面向字节流:流,指的是流入到进程或从进程流出的字符序列。简单来说,虽然有时候要传输的数据流太大,TCP报文长度有限制,不能一次传输完,要把它分为好几个数据块,但是由于可靠性保证,接收方可以按顺序接收数据块然后重新组成分块之前的数据流,所以TCP看起来就像直接互相传输字节流一样,面向字节流。
TCP建立连接
必须进行三次握手:若A要与B进行连接,则必须- 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认。即A发送信息给B
- 第二次握手:服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认。即B收到连接信息后向A返回确认信息
- 第三次握手:客户端收到服务器的(SYN+ACK)报文段,并向服务器发送ACK报文段。即A收到确认信息后再次向B返回确认连接信息
此时,A告诉自己上层连接建立;B收到连接信息后告诉上层连接建立。
这样就完成TCP三次握手 = 一条TCP连接建立完成 = 可以开始发送数据
- 三次握手期间任何一次未收到对面回复都要重发。
- 最后一个确认报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态。
为什么TCP建立连接需要三次握手?
答:防止服务器端因为接收了早已失效的连接请求报文从而一直等待客户端请求,从而浪费资源
- “已失效的连接请求报文段”的产生在这样一种情况下:Client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。
- 这是一个早已失效的报文段。但Server收到此失效的连接请求报文段后,就误认为是Client再次发出的一个新的连接请求。
- 于是就向Client发出确认报文段,同意建立连接。
- 假设不采用“三次握手”:只要Server发出确认,新的连接就建立了。
- 由于现在Client并没有发出建立连接的请求,因此不会向Server发送数据。
- 但Server却以为新的运输连接已经建立,并一直等待Client发来数据。>- 这样,Server的资源就白白浪费掉了。
采用“三次握手”的办法可以防止上述现象发生:
- Client不会向Server的确认发出确认
- Server由于收不到确认,就知道Client并没有要求建立连接
所以Server不会等待Client发送数据,资源就没有被浪费
TCP释放连接
TCP释放连接需要四次挥手过程,现在假设A主动释放连接:(数据传输结束后,通信的双方都可释放连接)- 第一次挥手:A发送释放信息到B;(发出去之后,A->B发送数据这条路径就断了)
第二次挥手:B收到A的释放信息之后,回复确认释放的信息:我同意你的释放连接请求
第三次挥手:B发送“请求释放连接“信息给A
第四次挥手:A收到B发送的信息后向B发送确认释放信息:我同意你的释放连接请求
B收到确认信息后就会正式关闭连接;
A等待2MSL后依然没有收到回复,则证明B端已正常关闭,于是A关闭连接
为什么TCP释放连接需要四次挥手?
为了保证双方都能通知对方“需要释放连接”,即在释放连接后都无法接收或发送消息给对方
- 需要明确的是:TCP是全双工模式,这意味着是双向都可以发送、接收的
- 释放连接的定义是:双方都无法接收或发送消息给对方,是双向的
- 当主机1发出“释放连接请求”(FIN报文段)时,只是表示主机1已经没有数据要发送 / 数据已经全部发送完毕;
但是,这个时候主机1还是可以接受来自主机2的数据。
- 当主机2返回“确认释放连接”信息(ACK报文段)时,表示它已经知道主机1没有数据发送了
但此时主机2还是可以发送数据给主机1 - 当主机2也发送了FIN报文段时,即告诉主机1我也没有数据要发送了
此时,主机1和2已经无法进行通信:主机1无法发送数据给主机2,主机2也无法发送数据给主机1,此时,TCP的连接才算释放
1.5 UDP协议
定义:User Datagram Protocol,即用户数据报协议,是一种传输层通信协议。
基于UDP的应用层协议有TFTP、SNMP与DNS。
特点:无连接的、不可靠的、面向报文、没有拥塞控制
无连接的:和TCP要建立连接不同,UDP传输数据不需要建立连接,就像写信,在信封写上收信人名称、地址就可以交给邮局发送了,至于能不能送到,就要看邮局的送信能力和送信过程的困难程度了。
不可靠的:因为UDP发出去的数据包发出去就不管了,不管它会不会到达,所以很可能会出现丢包现象,使传输的数据出错。
面向报文:数据报文,就相当于一个数据包,应用层交给UDP多大的数据包,UDP就照样发送,不会像TCP那样拆分。
- 没有拥塞控制:拥塞,是指到达通信子网中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象,就像交通堵塞一样。TCP建立连接后如果发送的数据因为信道质量的原因不能到达目的地,它会不断重发,有可能导致越来越塞,所以需要一个复杂的原理来控制拥塞。而UDP就没有这个烦恼,发出去就不管了。
应用场景
很多的实时应用(如IP电话、实时视频会议、某些多人同时在线游戏等)要求源主机以很定的速率发送数据,并且允许在网络发生拥塞时候丢失一些数据,但是要求不能有太大的延时,UDP就刚好适合这种要求。所以说,只有不适合的技术,没有真正没用的技术。
2、先看一段效果演示
3、布局
这里只是为了测试,为方便起见,直接建立两个activity来测试
<LinearLayout 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:orientation="vertical" > <EditText android:id="@+id/nickname" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:hint="@string/nickname" /> <EditText android:id="@+id/content" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:hint="@string/content" /> <Button android:id="@+id/send" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="#6600cc" android:text="@string/send1" /> <Button android:id="@+id/next" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="#6600cc" android:text="下一步" /> <ScrollView android:id="@+id/scrollview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/showview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:textSize="16sp"/> </LinearLayout> </ScrollView> </LinearLayout>
<LinearLayout 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:orientation="vertical" > <EditText android:id="@+id/nickname1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:hint="@string/nickname" /> <EditText android:id="@+id/content1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:hint="@string/content" /> <Button android:id="@+id/return2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="#6600cc" android:text="返回到上一个" /> <Button android:id="@+id/sendhttpclient1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="#6600cc" android:text="@string/send2" /> <ScrollView android:id="@+id/scrollview1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/showview1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:textSize="16sp"/> </LinearLayout> </ScrollView> </LinearLayout>
同样看一下效果展示:
4、JAVA代码
下面是mainactivity的CODE,注释都很清楚,这里就不做过多的解释了,直接看代码
package com.example.httpurlconnection; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; 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; import java.io.InputStreamReader; @SuppressLint("HandlerLeak") public class MainActivity extends Activity implements OnClickListener{ private EditText nickname,content; private Button send,next; private TextView showview; private String result=""; private Handler handler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); nickname=(EditText)findViewById(R.id.nickname); content=(EditText)findViewById(R.id.content); send=(Button)findViewById(R.id.send); next=(Button)findViewById(R.id.next); showview=(TextView)findViewById(R.id.showview); send.setOnClickListener(this); final Intent intent=new Intent(MainActivity.this,HTTPClient.class); next.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub startActivity(intent); } }); /* * 创建一个handler对象,重写handlermessage方法,处理通知时更新界面 */ handler=new Handler() { @Override public void handleMessage(Message msg) { if(result!=null) { showview.setText(result); //result非空,即已将从服务器获取数据,更新显示 //nickname.setText(""); //清空上面 content.setText(""); //清空输入内容 Toast.makeText(MainActivity.this, "发送成功", Toast.LENGTH_SHORT).show(); } } }; } /* * 将数据传送到服务器中 */ protected void send() { String target="http://ruibo.free.ngrok.cc/webtext/blog/dealPost.jsp"; URL url; try { url=new URL(target); HttpURLConnection uconn=(HttpURLConnection)url.openConnection(); uconn.setDoInput(true); //设置允许向链接中写入数据 uconn.setDoOutput(true); //设置允许向链接中读取数据 uconn.setInstanceFollowRedirects(true); //设置重定向为false uconn.setUseCaches(false); //设置允许缓存为false uconn.setRequestProperty("Content-Type", //设置内容类型为表单数据 "application/x-www-form-urlencoded"); uconn.setRequestMethod("POST"); //设置请求方式为POST请求 /* * 发送数据到服务器 */ DataOutputStream dataOutputStream=new //实例化一个数据输出流对象 DataOutputStream(uconn.getOutputStream()); dataOutputStream.writeBytes("nickname=" //将需要传递的数据写入输出流中 +URLEncoder.encode(nickname.getText().toString(),"utf-8") +"&content=" +URLEncoder.encode(content.getText().toString(),"utf-8")); dataOutputStream.flush(); //输出缓存 dataOutputStream.close(); //关闭数据输出流 /* * 从服务器获取数据 */ if(uconn.getResponseCode()==HttpURLConnection.HTTP_OK) //判断是否获得服务器回应 { InputStreamReader inputStreamReader = new InputStreamReader( uconn.getInputStream()); // 获得读取的内容 BufferedReader bufferedReader=new BufferedReader(inputStreamReader); //用bifferreader来处理转换为字符 String inputLine=null; while((inputLine=bufferedReader.readLine())!=null) //逐行输出到result中 result+=inputLine+"\n"; inputStreamReader.close(); //获取数据完毕,关闭输入流 } uconn.disconnect(); //断开连接 }catch(MalformedURLException e) { Log.i("error1",e.toString()); }catch(IOException e) { Log.i("error2",e.toString()); } } /* * 设置发表按钮的监听事件,当该按钮被单击时,将编辑框的内容发送到服务器并获取回应,同时发送一个 * message,通知更新 */ public void onClick(View v) { if(nickname.getText().toString().isEmpty()) { Toast.makeText(MainActivity.this, "昵称不能为空", Toast.LENGTH_SHORT).show(); return;} else if(content.getText().toString().isEmpty()) { Toast.makeText(MainActivity.this, "发表内容不能为空", Toast.LENGTH_SHORT).show(); return; } else { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub send(); Message message=handler.obtainMessage(); handler.sendMessage(message); } }).start(); } } }
HttpClient的代码:
package com.example.httpurlconnection; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; 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; @SuppressLint("HandlerLeak") public class HTTPClient extends Activity implements OnClickListener{ private Button sendfromHttpClient; private EditText nickname,content; private Button back; private TextView showview; private String result=""; private Handler handler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.httpclient); sendfromHttpClient=(Button)findViewById(R.id.sendhttpclient1); sendfromHttpClient.setOnClickListener(this); nickname=(EditText)findViewById(R.id.nickname1); content=(EditText)findViewById(R.id.content1); back=(Button)findViewById(R.id.return2); final Intent intent=new Intent(HTTPClient.this,MainActivity.class); back.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub startActivity(intent); finish(); } }); showview=(TextView)findViewById(R.id.showview1); /* * 创建一个handler对象,重写handlermessage方法,处理通知时更新界面 */ handler=new Handler() { @Override public void handleMessage(Message msg) { if(result!=null) { showview.setText(result); //非空则输出获取的内容 content.setText(""); //清空发送的编辑框 Toast.makeText(HTTPClient.this, "发送成功", Toast.LENGTH_SHORT).show(); } } }; } /* * 设置按钮监听方式,该按钮将会使用Client方式连接服务器 * */ @Override public void onClick(View v) { if(nickname.getText().toString().isEmpty()) { Toast.makeText(HTTPClient.this, "昵称不能为空", Toast.LENGTH_SHORT).show(); return;} else if(content.getText().toString().isEmpty()) { Toast.makeText(HTTPClient.this, "发表内容不能为空", Toast.LENGTH_SHORT).show(); return; } else { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub send(); Message message=handler.obtainMessage(); handler.sendMessage(message); } }).start(); } } /* * 将数据发送给服务器并且同时获取从服务器回传的数据 */ protected void send() { HttpClient httpClient=new DefaultHttpClient(); //实例化一个httpclient连接 String target="http://ruibo.free.ngrok.cc/webtext/blog/ClientPost.jsp"; HttpPost httpPost=new HttpPost(target); //实例化一个httppost请求 //将要传递的参数保存到List集合中 List<NameValuePair> list=new ArrayList<NameValuePair>(); list.add(new BasicNameValuePair("param", "post")); //标记参数、 list.add(new BasicNameValuePair("nickname",nickname.getText().toString())); list.add(new BasicNameValuePair("content", content.getText().toString())); try { httpPost.setEntity(new UrlEncodedFormEntity(list,"utf-8")); //将传递的数据通过utf-8编码传设置到httppost实例中 HttpResponse httpResponse=httpClient.execute(httpPost); //执行httppost请求 /* * 如果上面的post请求成功,则执行从服务器读取返回的数据的操作 */ if(httpResponse.getStatusLine().getStatusCode()==HttpStatus.SC_OK) {//如果post请求成功 result+=EntityUtils.toString(httpResponse.getEntity()); }else result="请求失败"; } catch (UnsupportedEncodingException e) { // TODO: handle exception Toast.makeText(HTTPClient.this, e.toString(), Toast.LENGTH_SHORT).show(); }catch(ClientProtocolException e) { Toast.makeText(HTTPClient.this, e.toString(), Toast.LENGTH_SHORT).show(); }catch(IOException e) { Toast.makeText(HTTPClient.this, e.toString(), Toast.LENGTH_SHORT).show(); } } }
这里用于测试的服务器是在自己电脑上搭建的tomcat服务器,通过映射使得外网能够访问。但是由于映射使用的是免费域名绑定,解析速度较慢,所以效果会差一点,但是不影响测试。从上面的代码我们也可以知道,在webapps/webtext/blog下应该建立两个jsp文件,分别处理两种传输方式的消息并做出响应。
4、web项目
dealPost.jsp文件的CODE
<%@page import="java.util.Date"%> <%@ page language="java" contentType="text/html; charset=utf-8" import="sun.misc.BASE64Decoder"%> <% String nickname; String content; nickname=request.getParameter("nickname"); content=request.getParameter("content"); if(nickname!=null && content!=null){ nickname=new String(nickname.getBytes("iso-8859-1"),"utf-8"); //进行utf-8转码 content=new String(content.getBytes("iso-8859-1"),"utf-8"); String time=new Date().toLocaleString(); //获取系统当前时间 %> <%=time+"(URL) ["+nickname+"] :"%> <%=content%> <%}%>
ClientPost.jsp文件
<%@page import="java.util.Date"%> <%@ page language="java" contentType="text/html; charset=utf-8" import="sun.misc.BASE64Decoder"%> <% String nickname; String content,param; param=request.getParameter("param"); nickname=request.getParameter("nickname"); content=request.getParameter("content"); if(nickname!=null && content!=null && param.equals("post")){ nickname=new String(nickname.getBytes("iso-8859-1"),"utf-8"); //进行utf-8转码 content=new String(content.getBytes("iso-8859-1"),"utf-8"); String time=new Date().toLocaleString(); //获取系统当前时间 %> <%=time+"(Client) ["+nickname+"] :"%> <%=content%> <%}%>5、socket编程将在下一次讲解