Socket
的使用在 Android
网络编程中很是重要Socket
及 其使用方法计算机网络分为五层:物理层、数据链路层、网络层、运输层、应用层java
其中:android
端口号规定为16位,即容许一个IP主机有2的16次方65535个不一样的端口。其中:git
0~1023:分配给系统的端口号github
> 咱们不能够乱用
1024~49151:登记端口号,主要是让第三方应用使用apache
> 可是必须在IANA(互联网数字分配机构)按照规定手续登记,
在Socket使用时,能够用1024~65535的端口号编程
做用:充分利用两端硬件环境的优点,将任务合理分配到Client端和Server端来实现,下降了系统的通信开销。服务器
> Socket正是使用这种结构创建链接的,一个套接字接客户端,一个套接字接服务器。
如图:网络
能够看出,Socket的使用能够基于TCP或者UDP协议。session
定义:Transmission Control Protocol,即传输控制协议,是一种传输层通讯协议app
基于TCP的应用层协议有FTP、Telnet、SMTP、HTTP、POP3与DNS。
特色:面向链接、面向字节流、全双工通讯、可靠
TCP创建链接
必须进行三次握手:若A要与B进行链接,则必须
第三次握手:客户端收到服务器的(SYN+ACK)报文段,并向服务器发送ACK报文段。即A收到确认信息后再次向B返回确认链接信息
> 此时,A告诉本身上层链接创建;B收到链接信息后告诉上层链接创建。
这样就完成TCP三次握手 = 一条TCP链接创建完成 = 能够开始发送数据
- 三次握手期间任何一次未收到对面回复都要重发。
- 最后一个确认报文段发送完毕之后,客户端和服务器端都进入ESTABLISHED状态。
答:防止服务器端由于接收了早已失效的链接请求报文从而一直等待客户端请求,从而浪费资源
- “已失效的链接请求报文段”的产生在这样一种状况下:Client发出的第一个链接请求报文段并无丢失,而是在某个网络结点长时间的滞留了,以至延误到链接释放之后的某个时间才到达server。
- 这是一个早已失效的报文段。但Server收到此失效的链接请求报文段后,就误认为是Client再次发出的一个新的链接请求。
- 因而就向Client发出确认报文段,赞成创建链接。
- 假设不采用“三次握手”:只要Server发出确认,新的链接就创建了。
- 因为如今Client并无发出创建链接的请求,所以不会向Server发送数据。
- 但Server却觉得新的运输链接已经创建,并一直等待Client发来数据。>- 这样,Server的资源就白白浪费掉了。
采用“三次握手”的办法能够防止上述现象发生:
TCP释放链接
TCP释放链接须要四次挥手过程,如今假设A主动释放链接:(数据传输结束后,通讯的双方均可释放链接)
第四次挥手:A收到B发送的信息后向B发送确认释放信息:我赞成你的释放链接请求
> B收到确认信息后就会正式关闭链接; > A等待2MSL后依然没有收到回复,则证实B端已正常关闭,因而A关闭链接
为了保证双方都能通知对方“须要释放链接”,即在释放链接后都没法接收或发送消息给对方
当主机1发出“释放链接请求”(FIN报文段)时,只是表示主机1已经没有数据要发送 / 数据已经所有发送完毕;
> 可是,这个时候主机1仍是能够接受来自主机2的数据。
当主机2返回“确认释放链接”信息(ACK报文段)时,表示它已经知道主机1没有数据发送了
> 但此时主机2仍是能够发送数据给主机1
当主机2也发送了FIN报文段时,即告诉主机1我也没有数据要发送了
> 此时,主机1和2已经没法进行通讯:主机1没法发送数据给主机2,主机2也没法发送数据给主机1,此时,TCP的链接才算释放
定义:User Datagram Protocol,即用户数据报协议,是一种传输层通讯协议。
基于UDP的应用层协议有TFTP、SNMP与DNS。
特色:无链接的、不可靠的、面向报文、没有拥塞控制
详情请看我写的另一篇文章你须要了解的HTTP知识都在这里了!
即套接字,是一个对 TCP / IP协议进行封装 的编程调用接口(API)
> 1. 即经过`Socket`,咱们才能在Andorid平台上经过 `TCP/IP`协议进行开发 > 2. `Socket`不是一种协议,而是一个编程调用接口(`API`),属于传输层(主要解决数据如何在网络中传输)
Socket ={(IP地址1:PORT端口号),(IP地址2:PORT端口号)}
Socket
的使用类型主要有两种:
streamsocket
) :基于 TCP
协议,采用 流的方式 提供可靠的字节流服务datagramsocket
):基于 UDP
协议,采用 数据报文 提供数据打包发送的服务具体原理图以下:
Socket
属于传输层,由于 TCP / IP
协议属于传输层,解决的是数据如何在网络中传输的问题 HTTP
协议 属于 应用层,解决的是如何包装数据 因为两者不属于同一层面,因此原本是没有可比性的。但随着发展,默认的Http里封装了下面几层的使用,因此才会出现Socket
& HTTP
协议的对比:(主要是工做方式的不一样):
Http
:采用 请求—响应 方式。
> 1. 即创建网络链接后,当 客户端 向 服务器 发送请求后,服务器端才能向客户端返回数据。 > 2. 可理解为:**是客户端有须要才进行通讯**
Socket
:采用 服务器主动发送数据 的方式
> 1. 即创建网络链接后,服务器可主动发送消息给客户端,而不须要由客户端向服务器发送请求 > 2. 可理解为:**是服务器端有须要才进行通讯**
Socket
可基于TCP
或者UDP
协议,但TCP更加经常使用 Socket
将基于TCP
协议// 步骤1:建立客户端 & 服务器的链接 // 建立Socket对象 & 指定服务端的IP及端口号 Socket socket = new Socket("192.168.1.32", 1989); // 判断客户端和服务器是否链接成功 socket.isConnected()); // 步骤2:客户端 & 服务器 通讯 // 通讯包括:客户端 接收服务器的数据 & 发送数据 到 服务器 <-- 操做1:接收服务器的数据 --> // 步骤1:建立输入流对象InputStream InputStream is = socket.getInputStream() // 步骤2:建立输入流读取器对象 并传入输入流对象 // 该对象做用:获取服务器返回的数据 InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); // 步骤3:经过输入流读取器对象 接收服务器发送过来的数据 br.readLine(); <-- 操做2:发送数据 到 服务器 --> // 步骤1:从Socket 得到输出流对象OutputStream // 该对象做用:发送数据 OutputStream outputStream = socket.getOutputStream(); // 步骤2:写入须要发送的数据到输出流对象中 outputStream.write(("Carson_Ho"+"\n").getBytes("utf-8")); // 特别注意:数据的结尾加上换行符才可以让服务器端的readline()中止阻塞 // 步骤3:发送数据到服务端 outputStream.flush(); // 步骤3:断开客户端 & 服务器 链接 os.close(); // 断开 客户端发送到服务器 的链接,即关闭输出流对象OutputStream br.close(); // 断开 服务器发送到客户端 的链接,即关闭输入流读取器对象BufferedReader socket.close(); // 最终关闭整个Socket链接
Demo
代码包括:客户端 & 服务器步骤1:加入网络权限
<uses-permission android:name="android.permission.INTERNET" />
步骤2:主布局界面设置
包括建立Socket链接、客户端 & 服务器通讯的按钮
<Button android:id="@+id/connect" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="connect" /> <Button android:id="@+id/disconnect" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="disconnect" /> <TextView android:id="@+id/receive_message" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/Receive" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Receive from message" /> <EditText android:id="@+id/edit" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/send" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="send"/>
步骤3:建立Socket链接、客户端 & 服务器通讯
具体请看注释
MainActivity.java
package scut.carson_ho.socket_carson; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MainActivity extends AppCompatActivity { /** * 主 变量 */ // 主线程Handler // 用于将从服务器获取的消息显示出来 private Handler mMainHandler; // Socket变量 private Socket socket; // 线程池 // 为了方便展现,此处直接采用线程池进行线程管理,而没有一个个开线程 private ExecutorService mThreadPool; /** * 接收服务器消息 变量 */ // 输入流对象 InputStream is; // 输入流读取器对象 InputStreamReader isr ; BufferedReader br ; // 接收服务器发送过来的消息 String response; /** * 发送消息到服务器 变量 */ // 输出流对象 OutputStream outputStream; /** * 按钮 变量 */ // 链接 断开链接 发送数据到服务器 的按钮变量 private Button btnConnect, btnDisconnect, btnSend; // 显示接收服务器消息 按钮 private TextView Receive,receive_message; // 输入须要发送的消息 输入框 private EditText mEdit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /** * 初始化操做 */ // 初始化全部按钮 btnConnect = (Button) findViewById(R.id.connect); btnDisconnect = (Button) findViewById(R.id.disconnect); btnSend = (Button) findViewById(R.id.send); mEdit = (EditText) findViewById(R.id.edit); receive_message = (TextView) findViewById(R.id.receive_message); Receive = (Button) findViewById(R.id.Receive); // 初始化线程池 mThreadPool = Executors.newCachedThreadPool(); // 实例化主线程,用于更新接收过来的消息 mMainHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: receive_message.setText(response); break; } } }; /** * 建立客户端 & 服务器的链接 */ btnConnect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 利用线程池直接开启一个线程 & 执行该线程 mThreadPool.execute(new Runnable() { @Override public void run() { try { // 建立Socket对象 & 指定服务端的IP 及 端口号 socket = new Socket("192.168.1.172", 8989); // 判断客户端和服务器是否链接成功 System.out.println(socket.isConnected()); } catch (IOException e) { e.printStackTrace(); } } }); } }); /** * 接收 服务器消息 */ Receive.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 利用线程池直接开启一个线程 & 执行该线程 mThreadPool.execute(new Runnable() { @Override public void run() { try { // 步骤1:建立输入流对象InputStream is = socket.getInputStream(); // 步骤2:建立输入流读取器对象 并传入输入流对象 // 该对象做用:获取服务器返回的数据 isr = new InputStreamReader(is); br = new BufferedReader(isr); // 步骤3:经过输入流读取器对象 接收服务器发送过来的数据 response = br.readLine(); // 步骤4:通知主线程,将接收的消息显示到界面 Message msg = Message.obtain(); msg.what = 0; mMainHandler.sendMessage(msg); } catch (IOException e) { e.printStackTrace(); } } }); } }); /** * 发送消息 给 服务器 */ btnSend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 利用线程池直接开启一个线程 & 执行该线程 mThreadPool.execute(new Runnable() { @Override public void run() { try { // 步骤1:从Socket 得到输出流对象OutputStream // 该对象做用:发送数据 outputStream = socket.getOutputStream(); // 步骤2:写入须要发送的数据到输出流对象中 outputStream.write((mEdit.getText().toString()+"\n").getBytes("utf-8")); // 特别注意:数据的结尾加上换行符才可以让服务器端的readline()中止阻塞 // 步骤3:发送数据到服务端 outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } }); } }); /** * 断开客户端 & 服务器的链接 */ btnDisconnect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { // 断开 客户端发送到服务器 的链接,即关闭输出流对象OutputStream outputStream.close(); // 断开 服务器发送到客户端 的链接,即关闭输入流读取器对象BufferedReader br.close(); // 最终关闭整个Socket链接 socket.close(); // 判断客户端和服务器是否已经断开链接 System.out.println(socket.isConnected()); } catch (IOException e) { e.printStackTrace(); } } }); } }
为了简化服务器使用,此处采用Mina
框架
> 1. 服务器代码请在`eclipse`平台运行 > 2. 按照个人步骤一步步实现就能够无脑运行了
步骤1:导入Mina
包
请直接移步到百度网盘:下载连接(密码: q73e)
步骤2:建立服务器线程
TestHandler.java
package mina; // 导入包 public class TestHandler extends IoHandlerAdapter { @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { System.out.println("exceptionCaught: " + cause); } @Override public void messageReceived(IoSession session, Object message) throws Exception { System.out.println("recieve : " + (String) message); session.write("hello I am server"); } @Override public void messageSent(IoSession session, Object message) throws Exception { } @Override public void sessionClosed(IoSession session) throws Exception { System.out.println("sessionClosed"); } @Override public void sessionOpened(IoSession session) throws Exception { System.out.println("sessionOpen"); } @Override public void sessionIdle(IoSession session, IdleStatus status) throws Exception { } }
步骤3:建立服务器主代码
TestHandler.java
package mina; import java.io.IOException; import java.net.InetSocketAddress; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.textline.TextLineCodecFactory; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; public class TestServer { public static void main(String[] args) { NioSocketAcceptor acceptor = null; try { acceptor = new NioSocketAcceptor(); acceptor.setHandler(new TestHandler()); acceptor.getFilterChain().addLast("mFilter", new ProtocolCodecFilter(new TextLineCodecFactory())); acceptor.setReuseAddress(true); acceptor.bind(new InetSocketAddress(8989)); } catch (Exception e) { e.printStackTrace(); } } }
至此,客户端 & 服务器的代码均实现完毕。
Connect
按钮: 链接成功Send
按钮发送Receive From Message
按钮,客户端 读取 服务器返回的消息DisConnect
按钮,断开 客户端 & 服务器的链接