Socket理论知识
OSI七层网络模型
OSI七层网络模型(从下往上):
OSI是一个理想的模型,一般的网络系统只涉及其中的几层,在七层模型中,每一层都提供一个特殊 的网络功能,从网络功能角度观察:
下面4层(物理层、数据链路层、网络层和传输层)主要提供数据传输和交换功能, 即以节点到节点之间的通信为主
第4层作为上下两部分的桥梁,是整个网络体系结构中最关键的部分;
上3层(会话层、表示层和应用层)则以提供用户与应用程序之间的信息和数据处理功能为主
简言之,下4层主要完成通信子网的功能,上3层主要完成资源子网的功能。
OSI七层模型详解
TCP/IP四层模型
TCP/UDP区别
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来触发,具体流程图如下:
第一次握手: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来触发,具体流程图如下
第一次挥手: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状态,完成四次挥手。 另外也可能是同时发起主动关闭的情况:
另外还可能有一个常见的问题就是:为什么建立连接是三次握手,而关闭连接却是四次挥手呢? 答:因为服务端在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通信模型
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客户端
步骤
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完成大文件的断点续传
基于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方法给接受者发送数据报包