Socket理论知识

OSI七层网络模型

OSI七层网络模型(从下往上):

Socket通信_Android教程

Socket通信_Android开发_02

OSI是一个理想的模型,一般的网络系统只涉及其中的几层,在七层模型中,每一层都提供一个特殊 的网络功能,从网络功能角度观察:

  • 下面4层(物理层、数据链路层、网络层和传输层)主要提供数据传输和交换功能, 即以节点到节点之间的通信为主
    第4层作为上下两部分的桥梁,是整个网络体系结构中最关键的部分;
  • 上3层(会话层、表示层和应用层)则以提供用户与应用程序之间的信息和数据处理功能为主

简言之,下4层主要完成通信子网的功能,上3层主要完成资源子网的功能。

​OSI七层模型详解​

TCP/IP四层模型

Socket通信_Android开发_03

TCP/UDP区别

Socket通信_Android开发_04

Socket通信_Android教程_05

Socket开发相关的一些概念名词:

IP地址

IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

端口

  • 用于区分不同的应用程序
  • 端口号的范围为0-65535,其中0-1023未系统的保留端口,我们的程序尽可能别使用这些端口!
  • IP地址和端口号组成了我们的Socket,Socket是网络运行程序间双向通信链路的终结点, 是TCP和UDP的基础!
  • 常用协议使用的端口:HTTP:80,FTP:21,TELNET:23

TCP协议与UDP协议

TCP协议流程详解

首先TCP/IP是一个协议簇,里面包括很多协议的。UDP只是其中的一个。之所以命名为TCP/IP协议, 因为TCP,IP协议是两个很重要的协议,就用他两命名了。

下面我们来讲解TCP协议和UDP协议的区别:

TCP(Transmission Control Protocol,传输控制协议)是面向连接的协议,即在收发数据钱 ,都需要与对面建立可靠的链接,这也是面试经常会问到的TCP的三次握手以及TCP的四次挥手

三次握手: 建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立, 在Socket编程中,这一过程由客户端执行connect来触发,具体流程图如下:

Socket通信_Android开发_06

  • 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server, Client进入SYN_SENT状态,等待Server确认。
  • 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位 SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求 ,Server进入SYN_RCVD状态。
  • 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK 置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则 连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以 开始传输数据了。

四次挥手: 终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。 在Socket编程中,这一过程由客户端或服务端任一方执行close来触发,具体流程图如下

Socket通信_Android教程_07

  • 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入 FIN_WAIT_1状态
  • 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同, 一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
  • 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK 状态。
  • 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。 另外也可能是同时发起主动关闭的情况:

Socket通信_Android开发_08

另外还可能有一个常见的问题就是:为什么建立连接是三次握手,而关闭连接却是四次挥手呢? 答:因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里 发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还 能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些 数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会 分开发送。

UDP协议详解

UDP(User Datagram Protocol)用户数据报协议,非连接的协议,传输数据之前源端和终端不 建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。 在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽 的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。 相比TCP就是无需建立链接,结构简单,无法保证正确性,容易丢包

Java中对于网络提供的几个关键类:

针对不同的网络通信层次,Java给我们提供的网络功能有四大类:

  • InetAddress: 用于标识网络上的硬件资源
  • URL: 统一资源定位符,通过URL可以直接读取或者写入网络上的数据
  • Socket和ServerSocket: 使用TCP协议实现网络通信的Socket相关的类
  • Datagram: 使用UDP协议,将数据保存在数据报中,通过网络进行通信

InetAddress:

public class InetAddressTest {
public static void main(String[] args) throws Exception{
//获取本机InetAddress的实例:
InetAddress address = InetAddress.getLocalHost();
System.out.println("本机名:" + address.getHostName());
System.out.println("IP地址:" + address.getHostAddress());
byte[] bytes = address.getAddress();
System.out.println("字节数组形式的IP地址:" + Arrays.toString(bytes));
System.out.println("直接输出InetAddress对象:" + address);
}
}


基于TCP协议的Socket通信-简易聊天室

基本介绍和使用

什么是Socket

Socket通信_Android开发_09

Socket通信模型

Socket通信_Android开发_10

Socket通信实现步骤解析:

  • Step 1:创建ServerSocket和Socket
  • Step 2:打开连接到的Socket的输入/输出流
  • Step 3:按照协议对Socket进行读/写操作
  • Step 4:关闭输入输出流,以及Socket

我们接下来写一个简单的例子,开启服务端后,客户端点击按钮然后链接服务端, 并向服务端发送一串字符串,表示通过Socket链接上服务器~

Socket服务端的编写

步骤

Step 1:创建ServerSocket对象,绑定监听的端口

Step 2:调用accept()方法监听客户端的请求

Step 3:连接建立后,通过输入流读取客户端发送的请求信息

Step 4:通过输出流向客户端发送响应信息

Step 5:关闭相关资源

Code

在Eclipse下创建一个Java项目,代码如下:

package com.turing.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
* Step 1:创建ServerSocket对象,绑定监听的端口
* Step 2:调用accept()方法监听客户端的请求
* Step 3:连接建立后,通过输入流读取客户端发送的请求信息
* Step 4:通过输出流向客户端发送响应信息
* Step 5:关闭相关资源
* *
*/
public class SocketServer {

public static void main(String[] args) {

try {
// 1. 创建一个服务端Socket,即ServerSocket,指定绑定的端口,并监听此端口
ServerSocket serverSocket = new ServerSocket(9999);
// 获取服务端IP
InetAddress inetAddress = InetAddress.getLocalHost();
String ip = inetAddress.getHostAddress();
System.out.println("~~~服务端已就绪,等待客户端接入~,服务端ip地址: " + ip);
// 2. 调用accept等待客户端连接
Socket socket = serverSocket.accept();
// 3. 连接后获取输入流,读取客户端信息
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
OutputStream os = null;
PrintWriter pw = null;
// 获取输入流
is = socket.getInputStream();
isr = new InputStreamReader(is, "UTF-8");
br = new BufferedReader(isr);

String info = null;
while ((info = br.readLine()) != null) {
System.out.println("客户端发送过来的信息" + info);
}

socket.shutdownInput();// 关闭输入流
socket.close();

} catch (IOException e) {
e.printStackTrace();
}

}

}

运行服务端

Socket通信_Android教程_11

Socket客户端的编写

Android客户端

步骤

Step 1:创建Socket对象,指明需要链接的服务器的地址和端号

Step 2:链接建立后,通过输出流向服务器发送请求信息

Step 3:通过输出流获取服务器响应的信息

Step 4:关闭相关资源

Code

package com.turing.base.activity.socket.baseuse;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import com.turing.base.R;
import com.turing.base.utils.AlertUtil;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;

/**
* Step 1:创建Socket对象,指明需要链接的服务器的地址和端号
Step 2:链接建立后,通过输出流向服务器发送请求信息
Step 3:通过输出流获取服务器响应的信息
Step 4:关闭相关资源

服务端Java工程:D:\workspace\ws-java-base\SocketServer
*/
public class SocketClientAct extends AppCompatActivity implements View.OnClickListener {

private Button socketSend ;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_socket_client);

socketSend = (Button) findViewById(R.id.id_btn_sendMsg);
socketSend.setOnClickListener(this);

}

@Override
public void onClick(View v) {

AlertUtil.showToastShort(this,"观察服务端日志~");
//Android不允许在主线程(UI线程)中做网络操作,
// 所以这里需要我们自己 另开一个线程来连接Socket!
new Thread() {
@Override
public void run() {
try {
acceptServer();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}

/**
* 连接ServerSocket发送消息
*/
private void acceptServer() throws IOException{

//1.创建客户端Socket,指定服务器地址和端口
Socket socket = new Socket("192.168.56.1", 9999);
//2.获取输出流,向服务器端发送信息
OutputStream os = socket.getOutputStream();//字节输出流
PrintWriter pw = new PrintWriter(os);//将输出流包装为打印流
//获取客户端的IP地址
InetAddress address = InetAddress.getLocalHost();
String ip = address.getHostAddress();
// 将这个信息发送给服务端
pw.write("客户端:~" + ip + "~ 接入服务器!!");
pw.flush();
socket.shutdownOutput();//关闭输出流
socket.close();
}
}

因为Android不允许在主线程(UI线程)中做网络操作,所以这里需要我们自己 另开一个线程来连接Socket!

运行结果:

点击按钮后,服务端控制台打印

Socket通信_Android教程_12

简易聊天室

​简易聊天室​

基于Socket完成大文件的断点续传

​断点续传​


基于UDP协议的Socket通信

UDP以数据报作为数据的传输载体,在进行传输时 首先要把传输的数据定义成数据报(Datagram),在数据报中指明数据要到达的Socket(主机地址 和端口号),然后再将数据以数据报的形式发送出去,然后就没有然后了,服务端收不收到我就 不知道了,除非服务端收到后又给我回一段确认的数据报。

服务端实现步骤

Step 1:创建DatagramSocket,指定端口号

Step 2:创建DatagramPacket

Step 3:接收客户端发送的数据信息

Step 4:读取数据

public class UPDServer {
public static void main(String[] args) throws IOException {
/*
* 接收客户端发送的数据
*/
// 1.创建服务器端DatagramSocket,指定端口
DatagramSocket socket = new DatagramSocket(12345);
// 2.创建数据报,用于接收客户端发送的数据
byte[] data = new byte[1024];// 创建字节数组,指定接收的数据包的大小
DatagramPacket packet = new DatagramPacket(data, data.length);
// 3.接收客户端发送的数据
System.out.println("****服务器端已经启动,等待客户端发送数据");
socket.receive(packet);// 此方法在接收到数据报之前会一直阻塞
// 4.读取数据
String info = new String(data, 0, packet.getLength());
System.out.println("我是服务器,客户端说:" + info);

/*
* 向客户端响应数据
*/
// 1.定义客户端的地址、端口号、数据
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] data2 = "欢迎您!".getBytes();
// 2.创建数据报,包含响应的数据信息
DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port);
// 3.响应客户端
socket.send(packet2);
// 4.关闭资源
socket.close();
}
}

客户端实现步骤

Step 1:定义发送信息

Step 2:创建DatagramPacket,包含将要发送的信息

Step 3:创建DatagramSocket

Step 4:发送数据

public class UDPClient {
public static void main(String[] args) throws IOException {
/*
* 向服务器端发送数据
*/
// 1.定义服务器的地址、端口号、数据
InetAddress address = InetAddress.getByName("localhost");
int port = 8800;
byte[] data = "用户名:admin;密码:123".getBytes();
// 2.创建数据报,包含发送的数据信息
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
// 3.创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();
// 4.向服务器端发送数据报
socket.send(packet);

/*
* 接收服务器端响应的数据
*/
// 1.创建数据报,用于接收服务器端响应的数据
byte[] data2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
// 2.接收服务器响应的数据
socket.receive(packet2);
// 3.读取数据
String reply = new String(data2, 0, packet2.getLength());
System.out.println("我是客户端,服务器说:" + reply);
// 4.关闭资源
socket.close();
}
}

总结

将数据转换为字节,然后放到DatagramPacket(数据报包中),发送的 时候带上接受者的IP地址和端口号,而接收时,用一个字节数组来缓存!发送的时候需要创建一个 DatagramSocket(端到端通信的类)对象,然后调用send方法给接受者发送数据报包