Socket通信

Posted 小小工匠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket通信相关的知识,希望对你有一定的参考价值。

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方法给接受者发送数据报包

以上是关于Socket通信的主要内容,如果未能解决你的问题,请参考以下文章

socket通信

与另一个片段通信的片段接口

Python 之 Socket编程(TCP/UDP)

C#:Socket通信

Socket通信客户端和服务端代码

socket网络编程:在简单套接字基础上加上通信循环(代码完善)