1.基础概念
1.1.网络七层协议有哪些?
OSI是一个开放性的通信系统互连参考模型,他是一个定义得非常好的协议规范。OSI模型有7层结构,每层都可以有几个子层。 OSI的7层从上到下分别是 7 应用层 6 表示层 5 会话层 4 传输层 3 网络层 2 数据链路层 1 物理层 ;其中高层(即7、6、5、4层)定义了应用程序的功能,下面3层(即3、2、1层)主要面向通过网络的端到端的数据流。应用层的协议有TELNET,HTTP,FTP,NFS,SMTP等,传输层的协议有TCP,UDP,SPX,本文主要介绍基于传输层的TCP自定义协议的服务器和客户端的长连接。
具体的各层含义有兴趣的可以参考:https://baike.baidu.com/item/网络七层协议/6056879
1.2.Socket是什么
建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
1.3.创建Socket连接和通信过程示例图
依据连接和通信我们可以看到整个Socket流程大概可以分为三阶段,第一阶段建立连接,第二阶段数据通信,第三阶段就是关闭连接;
a.第一个阶段建立连接(三次握手)
(1)tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下: 客户端向服务器发送一个SYN J 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1 客户端再想服务器发一个确认ACK K+1
只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:
(2)示意图:
(3)步骤: 1.当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态; 2.服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入 阻 塞 状 态;
3.客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
(4)总结: 客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
b.第二阶段数据通信
(1)通信示意图
两端(客户端和服务器端)Socket分别调用getInputStream()方法和getOutputStream()方法进行读写数据处理;
c.第三阶段就是关闭连接(四次握手);
(1)示意图:
(2)过程: 1.某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M; 2.另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据; 3.一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
4.接收到这个FIN的源发送端TCP对它进行确认。
1.4.Java中Socket关键函数
(1)Socket构造函数
Socket(InetAddress address, int port)throws UnknownHostException, IOException Socket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException Socket(String host, int port)throws UnknownHostException, IOException Socket(String host, int port, InetAddress localAddress, int localPort)throws IOException
还可以通过以下方式生成socket:
SocketFactory.getDefault().createSocket(String address, String port) throws ConnectException
(2)Socket方法 getInetAddress(); // 远程服务端的IP地址 getPort(); // 远程服务端的端口 getLocalAddress(); // 本地客户端的IP地址 getLocalPort(); // 本地客户端的端口 getInputStream(); // 获得输入流
getOutStream(); // 获得输出流
(3)Socket状态
isClosed(); // 连接是否已关闭,若关闭,返回true;否则返回false isConnect(); // 如果曾经连接过,返回true;否则返回false isBound(); // 如果Socket已经与本地一个端口绑定,返回true;否则返回false 判断Socket的状态是否处于连接中
boolean isConnected = socket.isConnected() && !socket.isClosed(); // 判断当前是否处于连接
2.简单使用
a.服务器端监听线程
public class ListenerThread extends Thread {
private ServerSocket serverSocket = null;
private Handler handler;
private int port;
private Socket socket;
public ListenerThread(int port, Handler handler){
setName("ListenerThread");
this.port = port;
this.handler = handler;
try {//关键代码
serverSocket = new ServerSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true){
//阻塞,等待设备连接
try {//关键代码
socket = serverSocket.accept();
Message message = Message.obtain();
message.what = ConnConstants.DEVICE_CONNECTING;
handler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public Socket getSocket() {
return socket;
}
}
serverSocket = new ServerSocket(port);
创建Socket服务器端,绑定当前Server的指定端口监听;
socket = serverSocket.accept();
阻塞状态,直到监听到客户端的连接;
Socket的服务器端Server使用介绍如下:
if(listenerThread == null){
listenerThread = new ListenerThread(PORT, connHandler);
listenerThread.start();
}
以上是启动服务器端监听线程;
case ConnConstants.DEVICE_CONNECTING:
activity.serverconnectThread = new ConnectThread(activity.listenerThread.getSocket(),activity.connHandler);
activity.serverconnectThread.start();
break;
以上是当监听到客户端连接服务器端时开始连接线程;
b.连接线程
/**
* 连接线程
*/
public class ConnectThread extends Thread {
private final Socket socket;
private Handler handler;
private InputStream inputStream;
private OutputStream outputStream;
public ConnectThread(Socket socket, Handler handler){
setName("ConnectThread");
this.socket = socket;
this.handler = handler;
}
@Override
public void run() {
if(socket == null){
return;
}
handler.sendEmptyMessage(ConnConstants.DEVICE_CONNECTED);
//获取数据流
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
byte[] buffer = new byte[1024];
int bytes;
while (true){
//读数据
bytes = inputStream.read(buffer);
if(bytes > 0){
final byte[] data = new byte[bytes];
System.arraycopy(buffer, 0, data, 0, bytes);
Message message = Message.obtain();
message.what = ConnConstants.GET_MSG;
Bundle bundle = new Bundle();
bundle.putString("MSG",new String(data));
message.setData(bundle);
handler.sendMessage(message);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送消息
* @param msg
*/
public void sendData(String msg){
if(outputStream != null){
try {
outputStream.write(msg.getBytes());
Message message = Message.obtain();
message.what = ConnConstants.SEND_MSG_SUCCSEE;
Bundle bundle = new Bundle();
bundle.putString("MSG", msg);
message.setData(bundle);
handler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
Message message = Message.obtain();
message.what = ConnConstants.SEND_MSG_ERROR;
Bundle bundle = new Bundle();
bundle.putString("MSG", msg);
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
连接线程部分服务器或者客户端,他们的线程是对等的,只需要把服务器或者客户端的Socket传给连接线程即可;
启动客户端代码如下:
Socket socket = null;
// SocketFactory
try {
socket = new Socket(IP, PORT);
clientconnectThread = new ConnectThread(socket, connHandler);
clientconnectThread.start();
} catch (IOException e) {
e.printStackTrace();
}
socket = new Socket(IP, PORT);
新建连接服务器端的Socket指定连接服务器的IP和端口;
clientconnectThread = new ConnectThread(socket, connHandler);
将客户端的Socket传递给连接线程;
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
获取Socket的输入流和输出流,通过输入流读取数据,通过输出流写入数据;
参考:
https://blog.csdn.net/legend050709/article/details/39804519
https://baike.baidu.com/item/socket/281150?fr=aladdin
|