我们说过Android中客户端与服务端通信有两种方式,HTTP通信和Socket通信,前面我们介绍过HTTP通信了,现在,我们来学习一下Socket通信。学习Socket之前,我们需要先学习一下TCP/IP协议和UDP协议。
1、Socket基本通信模型
我们需要学习的有两种网络通信参考模型,分别是TCP/IP参考模型和OSI参考模型,下面我们分别学习一下这两种参考模型:
TCP/IP参考模型
TCP/IP参考模型是计算机网络的祖父ARPANET和其后继的因特网使用的参考模型。TCP/IP参考模型将协议分为以下的四个层次:
应用层:主要为用户提供各种服务,例如:Telnet、FTP、DNS等,数据传输单位是:数据段
传输层:应用层实体提供端到端的通信功能,保证了数据包的顺序传送及数据的完整性。该层定义了两个主要的协议:传输控制协议(TCP)和用户数据报协议(UDP).数据传输单位是:数据包
网际互联层:主要解决主机到主机的通信问题。它所包含的协议设计数据包在整个网络上的逻辑传输。注重重新赋予主机一个IP地址来完成对主机的寻址,它还负责数据包在多种网络中的路由。该层有三个主要协议:网际协议(IP)、互联网组管理协议(IGMP)和互联网控制报文协议(ICMP)。数据传输单位是:帧
网络接入层:负责监视数据在主机和网络之间的交换。事实上,TCP/IP本身并未定义该层的协议,而由参与互连的各网络使用自己的物理层和数据链路层协议,然后与TCP/IP的网络接入层进行连接。数据传输单位是:比特
OSI参考模型
OSI(Open System Interconnect)开放式系统互联,是ISO(国际标准化组织)组织在1985年研究的网络互联模型。该体系结构标准定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层和应用层),即ISO开放系统互连参考模型。
第7层应用层:OSI中的最高层。为特定类型的网络应用提供了访问OSI环境的手段。应用层确定进程之间通信的性质,以满足用户的需要。应用层不仅要提供应用进程所需要的信息交换和远程操作,而且还要作为应用进程的用户代理,来完成一些为进行信息交换所必需的功能。它包括:文件传送访问和管理FTAM、虚拟终端VT、事务处理TP、远程数据库访问RDA、制造报文规范MMS、目录服务DS等协议;应用层能与应用程序界面沟通,以达到展示给用户的目的。 在此常见的协议有:HTTP,HTTPS,FTP,TELNET,SSH,SMTP,POP3等。
第6层表示层:主要用于处理两个通信系统中交换信息的表示方式。为上层用户解决用户信息的语法问题。它包括数据格式交换、数据加密与解密、数据压缩与终端类型的转换。
第5层会话层:在两个节点之间建立端连接。为端系统的应用程序之间提供了对话控制机制。此服务包括建立连接是以全双工还是以半双工的方式进行设置,尽管可以在层4中处理双工方式 ;会话层管理登入和注销过程。它具体管理两个用户和进程之间的对话。如果在某一时刻只允许一个用户执行一项特定的操作,会话层协议就会管理这些操作,如阻止两个用户同时更新数据库中的同一组数据。
第4层传输层:—常规数据递送-面向连接或无连接。为会话层用户提供一个端到端的可靠、透明和优化的数据传输服务机制。包括全双工或半双工、流控制和错误恢复服务;传输层把消息分成若干个分组,并在接收端对它们进行重组。不同的分组可以通过不同的连接传送到主机。这样既能获得较高的带宽,又不影响会话层。在建立连接时传输层可以请求服务质量,该服务质量指定可接受的误码率、延迟量、安全性等参数,还可以实现基于端到端的流量控制功能。
第3层网络层:本层通过寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。它包括通过互连网络来路由和中继数据 ;除了选择路由之外,网络层还负责建立和维护连接,控制网络上的拥塞以及在必要的时候生成计费信息。
第2层数据链路层:在此层将数据分帧,并处理流控制。屏蔽物理层,为网络层提供一个数据链路的连接,在一条有可能出差错的物理连接上,进行几乎无差错的数据传输(差错控制)。本层指定拓扑结构并提供硬件寻址。常用设备有网卡、网桥、交换机;
第1层物理层:处于OSI参考模型的最底层。物理层的主要功能是利用物理传输介质为数据链路层提供物理连接,以便透明的传送比特流。常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。
数据发送时,从第七层传到第一层,接收数据则相反。上三层总称应用层,用来控制软件方面。下四层总称数据流层,用来管理硬件。除了物理层之外其他层都是用软件实现的。数据在发至数据流层的时候将被拆分。在传输层的数据叫段,网络层叫包,数据链路层叫帧,物理层叫比特流,这样的叫法叫PDU(协议数据单元)
两种参考模型比较
两种参考模型比较图
共同点
(1)OSI参考模型和TCP/IP参考模型都采用了层次结构的概念。
(2)都能够提供面向连接和无连接两种通信服务机制。
不同点
(1)OSI采用的七层模型,而TCP/IP是四层结构。
(2)TCP/IP参考模型的网络接口层实际上并没有真正的定义,只是一些概念性的描述。而OSI参考模型不仅分了两层,而且每一层的功能都很详尽,甚至在数据链路层又分出一个介质访问子层,专门解决局域网的共享介质问题。
(3)OSI模型是在协议开发前设计的,具有通用性。TCP/IP是先有协议集然后建立模型,不适用于非TCP/IP网络。
(4)OSI参考模型与TCP/IP参考模型的传输层功能基本相似,都是负责为用户提供真正的端对端的通信服务,也对高层屏蔽了底层网络的实现细节。所不同的是TCP/IP参考模型的传输层是建立在网络互联层基础之上的,而网络互联层只提供无连接的网络服务,所以面向连接的功能完全在TCP协议中实现,当然TCP/IP的传输层还提供无连接的服务,如UDP;相反OSI参考模型的传输层是建立在网络层基础之上的,网络层既提供面向连接的服务,又提供无连接的服务,但传输层只提供面向连接的服务。
(5)OSI参考模型的抽象能力高,适合与描述各种网络;而TCP/IP是先有了协议,才制定TCP/IP模型的。
(6)OSI参考模型的概念划分清晰,但过于复杂;而TCP/IP参考模型在服务、接口和协议的 区别上不清楚,功能描述和实现细节混在一起。
(7)TCP/IP参考模型的网络接口层并不是真正的一层;OSI参考模型的缺点是层次过多,划分意义不大但增加了复杂性。
(8)OSI参考模型虽然被看好,由于没把握好时机,技术不成熟,实现困难;相反,TCP/IP参考模型虽然有许多不尽人意的地方,但还是比较成功的。
2、TCP/IP协议简介
TCP/IP(transmission Control Protocol/Internet Protocol)传输控制协议/互联网络协议,是一种网络通信协议,它规范了网络上的所有通信设备,尤其是一个主机与另一个主机之间的数据往来格式以及传送方式。举个简单的例子就是:TCP和IP就像两个信封,我们把要传输的数据分割开,放入到TCP信封里面,TCP信封里面会记录有分段号;然后将TCP信封装入IP这个大信封里面,传输到网络上。在接收端,一个TCP软件包收集信封,抽出数据,按发送前的顺序还原,并加以校验,若发现差错,TCP将会要求重发。
TCP/IP网络连接需要经过三次握手,断开连接需要三次挥手,在这里就不在做很详细的描述了,推荐一篇博文:TCP/IP三次握手与四次挥手 ,这里已经很详细的描述三次握手和四次挥手的过程,建议大家一定要阅读并理解,面试经常会被问到。关于TCP/IP协议就简单介绍到这里,我们做应用开发不是网络工程师,不需要做很深入的学习。
3、UDP协议简介
前面我们介绍了TCP/IP协议,接着我们介绍一下UDP协议。UDP(User Datagram Protocol)用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。UDP在IP报文的协议号是17。
UDP协议全称是用户数据报协议 ,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但是即使是在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。
与所熟知的TCP(传输控制协议)协议一样,UDP协议直接位于IP(网际协议)协议的顶层。根据OSI(开放系统互连)参考模型,UDP和TCP都属于传输层协议。UDP协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。
4、Socket基本概念
前面介绍的都是概念性的东西,非常枯燥,下面正式开始学习基于TCP的Socket通信。
什么是Socket?称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。
Socket通信模型
5、Socket的通信流程
Server端Listen(监听)某个端口是否有连接请求,Client端向Server 端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。
对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
1、创建ServerSocket和Socket
2、打开连接到的Socket的输入/输出流
3、按照协议对Socket进行读/写操作
4、关闭输入输出流,以及Socket
6、Socket搭建简单的聊天室
下面我们通过Socket搭建一个简单聊天室来体会一下Socket通信:
首先是服务端的编写,服务端编写步骤:
创建ServerSocket对象,绑定监听的端口
通过调用accept()方法监听客户端的请求
建立连接之后,通过输入流读取客户端的请求信息
获取输出流向客户端输出信息
关闭连接
客户端的编写步骤:
创建Socket对象,指明需要连接的服务器的地址和端号
连接建立后,通过输出流向服务器发送请求信息
通过输出流获取服务器响应的信息
关闭连接,释放相关资源
具体实例代码:
服务端:
package com.example.socket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Devin on 2016/7/28.
*/
public class SocketServer {
public static boolean isServerStart = true;
private ServerSocket serverSocket;
private static int LISTENER_PART = 10010;
public List<Socket> socketList = new ArrayList<>();
private ExecutorService executorService;
public void startServer() {
try {
serverSocket = new ServerSocket(LISTENER_PART);
executorService = Executors.newCachedThreadPool();
InetAddress address = InetAddress.getLocalHost();
System.out.println("服务器的IP地址是:" + address.getHostAddress() + ",监听的端口号是:" + LISTENER_PART);
System.out.println("--------------服务器启动,等待连接-----------------");
Socket socket = null;
while (isServerStart) {
System.out.println("有用户登录进来了");
socket = serverSocket.accept();
socketList.add(socket);
executorService.execute(new ServerThread(socket));
}
serverSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SocketServer socketServer = new SocketServer();
socketServer.startServer();
}
}
客户端:
package com.example.socketdemo;
import android.content.Intent;
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 com.example.socketdemo.activity.FriendsActivity;
import com.example.socketdemo.comm.Constans;
import com.example.socketdemo.comm.ServerConn;
import com.example.socketdemo.comm.ToastUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
public class MainActivity extends AppCompatActivity {
private EditText et_user_id;
private EditText et_user_name;
private Button btn_user_login;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
String isSuccess = bundle.getString("isSuccess");
int userID = bundle.getInt("userID");
if (isSuccess.equals("success")) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, FriendsActivity.class);
intent.putExtra("userID", userID);
startActivity(intent);
} else {
ToastUtils.showToast(MainActivity.this, "登录失败");
return;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_user_id = (EditText) findViewById(R.id.et_user_id);
et_user_name = (EditText) findViewById(R.id.et_user_name);
btn_user_login = (Button) findViewById(R.id.btn_user_login);
btn_user_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String userID = et_user_id.getText().toString().trim();
if (userID != null && !userID.equals("")) {
login(Integer.parseInt(userID));
} else {
ToastUtils.showToast(MainActivity.this, "请输入用户ID");
return;
}
}
});
}
private void login(int userID) {
final int id = userID;
new Thread(new Runnable() {
@Override
public void run() {
try {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(Constans.SERVER_ADDRESS, Constans.SERVER_PORT), 5000);
InetAddress address = InetAddress.getLocalHost();
PrintWriter writer = new PrintWriter(socket.getOutputStream());
writer.println(Constans.TAG_LOGIN + ":" + id + ":" + address.getHostAddress());
writer.flush();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String isSuccess = bufferedReader.readLine();
ServerConn.mSocket = socket;
Message message = mHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putString("isSuccess", isSuccess);
bundle.putInt("userID", id);
message.setData(bundle);
mHandler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
package com.example.socketdemo.activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.example.socketdemo.R;
import com.example.socketdemo.comm.Constans;
import com.example.socketdemo.comm.OnItemClickListener;
import com.example.socketdemo.domain.UserInfo;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Devin on 2016/8/1.
*/
public class FriendsActivity extends AppCompatActivity {
private RecyclerView rv_friends;
private FriendsAdapter mAdapter;
private List<UserInfo> mUserInfos;
private int userID;
private FriendHandler mHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_friends);
userID = getIntent().getIntExtra("userID", 0);
mHandler = new FriendHandler();
rv_friends = (RecyclerView) findViewById(R.id.rv_friends);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
rv_friends.setLayoutManager(linearLayoutManager);
rv_friends.setItemAnimator(new DefaultItemAnimator());
mUserInfos = new ArrayList<>();
initData(userID);
mAdapter = new FriendsAdapter(this, mUserInfos);
rv_friends.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
UserInfo userInfo = mUserInfos.get(position);
Intent intent = new Intent(FriendsActivity.this, ChatActivity.class);
intent.putExtra("sendID", userID);
intent.putExtra("receiveID", userInfo.getUserID());
intent.putExtra("userName", userInfo.getUserName());
startActivity(intent);
}
});
}
private void initData(final int userID) {
new Thread(new Runnable() {
@Override
public void run() {
Socket socket = new Socket();
try {
socket.connect(new InetSocketAddress(Constans.SERVER_ADDRESS, Constans.SERVER_PORT), 5000);
PrintWriter writer = new PrintWriter(socket.getOutputStream());
writer.println(Constans.TAG_FRIENDS + ":" + userID);
writer.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String result = reader.readLine();
System.out.println("-----result---->>"+result);
Bundle bundle = new Bundle();
bundle.putString("result", result);
Message message = mHandler.obtainMessage();
message.setData(bundle);
mHandler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private class FriendHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
String result = bundle.getString("result");
List<UserInfo> infos = new ArrayList<>();
try {
JSONArray jsonArray = new JSONArray(result);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
UserInfo userInfo = new UserInfo();
userInfo.setUserID(jsonObject.getInt("userID"));
userInfo.setUserIcon(jsonObject.getInt("userIcon"));
userInfo.setUserName(jsonObject.getString("userName"));
userInfo.setUserSign(jsonObject.getString("userSign"));
userInfo.setUserState(jsonObject.getInt("userState"));
infos.add(userInfo);
}
} catch (JSONException e) {
e.printStackTrace();
}
mAdapter.addList(infos);
}
}
}
实现效果图
这样可以实现简单的Socket通信,当然只是很简单的应用。