(Java)socket网络编程及处理socket粘包拆包问题

目录

1.socket简介

2.TCP/IP协议

3.tcp三次握手

4.socket的一些接口函数原理

5.java socket 长连接粘包拆包问题

6.socket模拟服务端客户端发消息

7.UDP广播发请求


C socket编程代码及简介

java socket代码样例 详细

WebSocket与Socket、TCP、HTTP的关系和异同点

最近因为客户需求开始接触网络编程,过程很心酸,收获很丰满。。。

1.socket简介

socket编程是一门技术,他主要在网络通信中经常用到。

既然是一门技术,由于现在是面向对象的编程,一些计算机行业的大神通过抽象的理念,在现实中通过反复或者实际的推导,提出了抽象的一些通信协议,基于tcp/ip协议,提出大致的构想,一些泛型的程序大牛在这个协议的基础上,将这些抽象化的理念接口化,针对协议提出的每个理念,专门的编写指定的接口,与其协议一一对应,形成了现在的socket标准规范,然后将其接口封装成可以调用的接口,供开发者使用。

目前开发者开发出了很多封装的类来完善socket编程,都是更加方便的实现刚开始socket通信的各个环节,所以我们必须首先了解socket的通信原理,只有从本质上理解socket的通信,才可能快速方便的理解socket的各个环节,才能从底层上真正的把握。

2.TCP/IP协议

要理解socket必须的得理解tcp/ip,它们之间好比送信的线路和驿站的作用,比如要建议送信驿站,必须得了解送信的各个细节。

 TCP/IP协议不同于iso的7个分层,它是根据这7个分层,将其重新划分,好比打扫卫生,本来有扫帚,垃圾斗,抹布,涂料,盆栽等就好比OSI的标准几个分层,tcp/ip根据用途和功能,将扫帚,垃圾斗放到粗略整理层,抹布涂料放到中度整理层,盆栽放到最终效果层。这里TCP/IP也对OSI的网络模型层进行了划分:大致如下:

OSI模型:

TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中:

应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等

传输层:TCP,UDP

网络层:IP,ICMP,OSPF,EIGRP,IGMP

数据链路层:SLIP,CSLIP,PPP,MTU

每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的:

通过上面的图形,由于底一层的需要向高一层的提供服务,我们大致的理解应用程序需要传输层的tcp和网络层的ip协议提供服务,但是我们这章要分析的socket它是在tcpip协议的那一部分呢,就好比,我们的通讯线路已经有明确的规定,我们的驿站要设计在哪个地方一样。

回过头再来理解socket

到目前为止,大致的了解了应用程序和tcpip协议的大致关系,我们只是知道socket编程是在tcp/IP上的网络编程,但是socket在上述的模型的什么位置呢?这个位置被一个天才的理论家或者是抽象的计算机大神提出并且安排出来:

我们可以发现socket就在应用程序的传输层和应用层之间,设计了一个socket抽象层,传输层的底一层的服务提供给socket抽象层,socket抽象层再提供给应用层,问题又来了,应用层和socket抽象层之间和传输层,网络层之间如何通讯的呢,了解这个之前,我们还是回到原点。

要想理解socket编程怎么通过socket关键词实现服务器和客户端通讯,必须得实现的了解tcp/ip是怎么通讯的,在这个的基础上在去理解socket的握手通讯。

3.tcp三次握手

在tcp/ip协议中,tcp通过三次握手建立起一个tcp的链接,大致如下:

第一次握手:客户端尝试连接服务器,向服务器发送syn包,syn=j,客户端进入SYN_SEND状态等待服务器确认

第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

 三次握手如下图:

 根据tcp的三次握手,socket也定义了三次握手,也许是参考tcp的三次握手,一些计算机大神们画出了socket的三次握手的模型图

     模型图如下:

在上面图的基础上,如果我们得到上面的图形,需要我们自己开发一些接口,来满足上面的通讯的三次握手,问题就出来了,我们会需要开发哪些函数?

4.socket的一些接口函数原理

 通过上面的图,我们清楚,我们好比一些泛型的程序员,一些理论提供者提供给了我们上面的图形的理论,我们需要做的就是讲上面的图形的抽象化的东西具体化。

第一次握手:客户端需要发送一个syn j 包,试着去链接服务器端,于是客户端我们需要提供一个链接函数

第二次握手:服务器端需要接收客户端发送过来的syn J+1 包,然后在发送ack包,所以我们需要有服务器端接受处理函数

 第三次握手:客户端的处理函数和服务器端的处理函数

三次握手只是一个数据传输的过程,但是,我们传输前需要一些准备工作,比如将创建一个套接字,收集一些计算机的资源,将一些资源绑定套接字里面,以及接受和发送数据的函数等等,这些功能接口在一起构成了socket的编程

下面大致的按照客户端和服务端将所需的函数详细的列举出来:

上面的两个图都概述了socket的通讯原理

socket 样例可点击java socket代码样例 详细

5.java socket 长连接粘包拆包问题

网上看了很多资料,没有很好可实行的成熟的方案,最后在我师傅的英明带领下,socket粘包拆包问题算是解决了,demo所做的工作就是将通过socket长连接发过来的包,拆成一条一条完整的包供安卓使用。直接上代码:

package socket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * java客户端
 */
public class SocketClient extends Thread {
    private static final Logger logger = LoggerFactory.getLogger(SocketClient.class);
    private static boolean flag = false;
    public static Socket socket = new Socket();

    public static void main(String[] args) throws IOException {
        try {
            connect();
            String bookAlarm = "POST /merlin/BookAlarmCenter.cgi HTTP/1.1\r\n" +
                    "Content-Length: 0\r\n" +
                    "Content-Type: application/json\r\n" +
                    "User-Agent: 172.18.30.120:80\r\n" +
                    "Connection: keep-alive\r\n" +
                    "Authorization: Basic YWRtaW46MTIzNDU2\r\n";
            // 向服务端发消息
            sendMessage(bookAlarm);
            // 持续接受服务端消息
            receiveMessage();
        } catch (Exception e) {
            logger.info("异常关闭,异常信息:{}", e);
        }
        socket.close();
        logger.info("客户端已退出~");
    }


    /**
     * 接受服务端消息
     * @throws IOException
     */
    private static void receiveMessage() throws IOException {
        // 得到socket输入流,并转换为BufferedReader
        InputStream inputStream = socket.getInputStream();

        String tag = "\r\n\r\n";
        String tag2 = "\r\n";
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        int n;
        while ((n = inputStream.read()) != -1) {
            if (flag) {
                output = new ByteArrayOutputStream();
                flag = false;
            }
            output.write(n);
            String headStr = output.toString();
            int contentLength = 0;
            if (headStr.contains(tag)) {
                    String[] split = headStr.split(tag2);
                    for (String contentLine : split) {
                        if (contentLine.contains("Content-Length:")) {
                            contentLength = Integer.parseInt(contentLine.split("Content-Length:")[1].trim());
                            break;
                        }
                    }
                int count = 0;
                while ((n = inputStream.read()) != -1) {
                    output.write(n);
                    count++;
                    if (count == contentLength) {
                        System.out.println(output.toString());
                        System.out.println("========================================================================================================");
                        flag = true;
                        break;
                    }
                }
            }
        }
    }

    private static void connect() {
        try {
            // 超时时间
            socket.setSoTimeout(30 * 1000);
            // 连接本地,超时时间3s
            socket.connect(new InetSocketAddress("172.18.30.107", 80), 30 * 1000);
            logger.info("已发起服务器连接,并进入后续流程");
            logger.info("客户端信息:" + socket.getLocalAddress() + " port:" + socket.getLocalPort());
            logger.info("服务端信息:" + socket.getInetAddress() + " port:" + socket.getPort());
        } catch (Exception e) {
            logger.error("连接服务器异常,异常信息:{}", e);
        }
    }


    /**
     * 向服务端发消息
     * @param message
     * @throws IOException
     */
    public static void sendMessage(String message) {
        // 得到socket输出流,并转换为打印流
        OutputStream outputStream;
        PrintStream socketPrintStream;
        try {
            outputStream = socket.getOutputStream();
            // 用此方法发送可收到回包
            socketPrintStream = new PrintStream(outputStream);
            logger.info("向服务端发消息:{}", message);
            socketPrintStream.println(message);
        } catch (Exception e) {
            logger.error("客户端发送消息异常:{}", e);
        }
    }



}

6.socket模拟服务端客户端发消息

客户端:

import java.io.*;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) throws IOException{
//        Socket socket = new Socket();
        Socket socket = new Socket("192.168.9.113", 80);
        // 超时时间
        socket.setSoTimeout(3000);

        // 连接本地,端口2000;超时时间3s
        socket.connect(new InetSocketAddress(Inet4Address.getLocalHost(), 2000), 3000);

        System.out.println("已发起服务器连接,并进入后续流程");
        System.out.println("客户端信息:" + socket.getLocalAddress() + ",port:" + socket.getLocalPort());
        System.out.println("服务端信息:" + socket.getInetAddress() + ",port:" + socket.getPort());

        try {
            todo(socket);
        } catch(Exception e) {
            System.out.println("异常关闭");
        }

        socket.close();
        System.out.println("客户端已退出~");
    }

    private static void todo(Socket socket) throws IOException {
        // 构建键盘输入流
        InputStream in = System.in;
        BufferedReader input = new BufferedReader(new InputStreamReader(in));

        // 得到socket输出流,并转换为打印流
        OutputStream outputStream = socket.getOutputStream();
        PrintStream socketPrintStream = new PrintStream(outputStream);

        // 得到socket输入流,并转换为BufferedReader
        InputStream inputStream = socket.getInputStream();
        BufferedReader socketBufferedReader = new BufferedReader(new InputStreamReader(inputStream));

        boolean flag = true;
        do {
            // 键盘读取一行
            String str = input.readLine();
            // 发送到服务器
            socketPrintStream.println(str);

            // 从服务器读取一行
            String echo = socketBufferedReader.readLine();
            if ("bye".equalsIgnoreCase(echo)) {
                flag = false;
            } else {
                System.out.println(echo);
            }
        } while (flag);

        // 资源释放
        socketBufferedReader.close();
        socketPrintStream.close();
    }
}

 服务端:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 基于TCP协议的Socket通信,实现用户登录,服务端
 */
public class Server {
    public static void main(String[] args) throws IOException{
        ServerSocket server = new ServerSocket(80);

        System.out.println("服务器准备就绪~");
        System.out.println("服务端信息:" + server.getInetAddress() + "port:" + server.getLocalPort());

        // 等待客户端连接
        for (;;) {
            // 得到客户端
            Socket client = server.accept();
            // 客户端构建异步线程
            ClientHandler clientHandler = new ClientHandler(client);
            // 启动线程
            clientHandler.start();
        }

    }

    /**
     * 客户端消息处理
     */
    private static class ClientHandler extends Thread {
        private Socket socket;
        private boolean flag = true;
        ClientHandler (Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            super.run();
            System.out.println("新客户端连接:" + socket.getInetAddress() + ",port:" + socket.getPort());
            try {
                // 得到打印流,用于数据输出,服务器回送数据使用
                PrintStream socketOutPut = new PrintStream(socket.getOutputStream());
                BufferedReader socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                do {
                    // 客户端拿到一条数据
                    String str = socketInput.readLine();
                    if ("bye".equalsIgnoreCase(str)) {
                        flag = false;
                        // 回送
                        socketOutPut.println("bye");
                    } else {
                        // 打印到屏幕,并回数据长度
                        System.out.println("客户端发来消息:" + str);
                        socketOutPut.println("回送:" + str.length());// 回送客户端
                    }
                } while (flag);
                socketInput.close();
                socketOutPut.close();
            } catch(Exception e) {
                System.out.println("连接异常断开");
            } finally {
                // 连接关闭
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("客户端已退出:" + socket.getInetAddress() + "port:" + socket.getPort());
        }
    }

}

7.UDP广播发请求

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * @Description:     DUP提供者,用于提供服务
 * @Author:          nicecloud
 * @CreateDate:      2019/6/29 22:55
 * @Version:         1.0
 */
public class UDPProvider {
    public static void main(String[] args) throws IOException {
        System.out.println("UDPProvider started.");
        // 作为接收者,指定一个端口用于数据接收
        DatagramSocket ds = new DatagramSocket(20000);

        // 构建接收实体
        final byte[] buf = new byte[512];
        DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
        // 接收
        ds.receive(receivePacket);

        // 打印接收到的信息与发送者的信息
        // 发送者的IP地址
        String ip = receivePacket.getAddress().getHostAddress();
        int port = receivePacket.getPort();
        int dataLen = receivePacket.getLength();
        String data = new String(receivePacket.getData(), 0, dataLen);
        System.out.println("UDPProvider receive from ip:" + ip + "\tport:" + port + "\tdata:" + data);

        // 构建一份回送数据
        String responseData = "Receive data with len:" + dataLen;
        byte[] responDataBytes = responseData.getBytes();
        // 直接根据发送者构建一份回送信息
        DatagramPacket responsePacket = new DatagramPacket(responDataBytes,
                responDataBytes.length,
                receivePacket.getAddress(),
                receivePacket.getPort());
        ds.send(responsePacket);

        // 完成
        System.out.println("UDPProvider Finished.");
        ds.close();


    }
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * @Description:
 * @Author:          nicecloud
 * @CreateDate:      2019/6/29 22:56
 * @Version:         1.0
 */
public class UDPSearcher {
    public static void main(String[] args) throws IOException {
        System.out.println("UDPSearcher started.");
        // 作为搜索方,无需指定端口,系统自动分配
        DatagramSocket ds = new DatagramSocket(20000);

        // 构建一份回送数据
        String receiveData = "hello udp!";
        byte[] requestDataBytes = receiveData.getBytes();
        // 直接根据发送者构建一份回送信息
        DatagramPacket requestPacket = new DatagramPacket(requestDataBytes,
                requestDataBytes.length);
        // 本机端口20000
        requestPacket.setAddress(InetAddress.getLocalHost());
        requestPacket.setPort(20000);

        // 发送
        ds.send(requestPacket);

        // 构建接收实体
        final byte[] buf = new byte[512];
        DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
        // 接收
        ds.receive(receivePacket);

        // 打印接收到的信息与发送者的信息
        // 发送者的IP地址
        String ip = receivePacket.getAddress().getHostAddress();
        int port = receivePacket.getPort();
        int dataLen = receivePacket.getLength();
        String data = new String(receivePacket.getData(), 0, dataLen);
        System.out.println("UDPSearcher receive from ip:" + ip + "\tport:" + port + "\tdata:" + data);

        // 完成
        System.out.println("UDPSearcher Finished.");
        ds.close();
    }
}
发布了91 篇原创文章 · 获赞 11 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/hjing123/article/details/90710432