IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> java网络编程Socket实现客户端向服务端发送信息 -> 正文阅读

[系统运维]java网络编程Socket实现客户端向服务端发送信息

(可按目录按需阅读,我一般会整理的比较细)

前置知识

java IO

Socket

什么是socket?socket字面意思其实就是一个插口或者套接字,包含了源ip地址、源端口、目的ip地址和源端口。
但是socket在那个位置呢 ,在TCP/IP网络的四层体系和OSI七层好像都找不到他的影子,如下图所示, Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。一般由操作系统或者JVM自己实现。java.net中的socket其实就是对底层的抽象调用。有一点需要注意,运行在同一主机上的其他应用程序可能也会通过底层套接字抽象来使用网络,因此会与java socket实例竞争资源,如端口。
在这里插入图片描述

工作流程
对于服务器来说,服务器先初始化socket,然后端口绑定(bind),再对端口监听(listen),调用accept阻塞,等待客户端连接请求。对于客户端来说,客户端初始化socket,然后申请连接(connection)。客户端申请连接,服务器接受申请并且回复申请许可(这里要涉及TCP三次握手连接),然后发送数据,最后关闭连接,这是一次交互过程。
在这里插入图片描述

角色

服务器

服务器的socket程序有以下几个任务:

  • 创建ServerSocket。
  • 绑定并监听端口
  • 阻塞,等待客户端连接。
  • 与客户端连接成功后,进行数据交互

客户端

客户端的socket程序有以下几个任务:

  • 创建Socket。
  • 连接服务器。
  • 与服务器连接成功后,进行数据交互。

代码

服务端代码

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    public static void main(String[] args) {

        final String QUIT = "quit";
        final int DEFAULT_PORT = 8000;
        ServerSocket serverSocket = null;
        BufferedReader reader = null;
        BufferedWriter writer = null;

        try {
            // 绑定监听端口
            serverSocket = new ServerSocket(DEFAULT_PORT);
            System.out.println("启动服务器,监听服务器本地端口" + DEFAULT_PORT);

            while (true) {
                // 等待客户端连接
                Socket socket = serverSocket.accept();
                System.out.println("客户端["+socket.getInetAddress()+":"+ socket.getPort() + "]已连接");

                reader = new BufferedReader(
                        new InputStreamReader(socket.getInputStream())
                );

                writer = new BufferedWriter(
                        new OutputStreamWriter(socket.getOutputStream())
                );

                String msg = null;
                while ((msg = reader.readLine()) != null) {
                    // 读取客户端发送的消息
                    System.out.println("客户端["+socket.getInetAddress()+":"+ socket.getPort() + "]: " + msg);

                    // 回复客户发送的消息
                    writer.write("服务器已收到: " + msg + "\n");
                    writer.flush();

                    // 查看客户端是否退出
                    if (QUIT.equalsIgnoreCase(msg)) {
                        System.out.println("客户端["+socket.getInetAddress()+":"+ socket.getPort() + "]已断开连接");
                        break;
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                serverSocket.close();
                reader.close();
                writer.close();
                System.out.println("关闭serverSocket");
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }
}

客户端代码

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

public class Client {


    public static void main(String[] args) {

        final String QUIT = "quit";
        final String DEFAULT_SERVER_HOST = "127.0.0.1";
        final int DEFAULT_SERVER_PORT = 8000;
        Socket socket = null;

        BufferedWriter writer = null;
        BufferedReader reader = null;
        BufferedReader consoleReader = null;

        try {
            // 创建socket
            socket = new Socket(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);

            // 创建IO流
            reader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            writer = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );

            // 等待用户输入信息
            consoleReader = new BufferedReader(new InputStreamReader(System.in));

            while (true) {
                String input = consoleReader.readLine();

                // 发送消息给服务器
                writer.write(input + "\n");
                writer.flush();

                // 读取服务器返回的消息
                String msg = reader.readLine();
                System.out.println(msg);

                // 查看用户是否退出
                if (QUIT.equalsIgnoreCase(input)) {
                    break;
                }
            }

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

            try {
                writer.close(); //关闭之前还会flush一次
                socket.close();
                reader.close();
                consoleReader.close();
                System.out.println("关闭socket");
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }
}

运行展示

客户端
在这里插入图片描述

服务端
在这里插入图片描述

CMD查看

开启Server端之后,在Windows cmd 终端里面输入命令,会发现8000端口处于LISTENING状态,先前没开启Server代码运行是没有的

netstat -ano|findstr "8000"

netstat 用于显示套接字内容 , -ano 是可选选项
a不仅显示正在通信的套接字,还显示包括尚未开始通信等状态的所有套接字
n 显示 IP 地址和端口号
o 显示套接字的程序 PID

在这里插入图片描述
第一列表示通信协议,这里是TCP
第二列表示,运行netstat命令的主机ip和port,这里也就是服务器的IP和port,0.0.0.0表示还没有绑定IP地址
第三列表示,通信对象的IP和port,0.0.0.0:0表示还没连接到对象,所以IP和port都不知道
第四列表示,LISTENING表示等待对方连接
最后一列,PID进程号
图中的每一行都相当于一个套接字,每一列也被称为一个元组,所以一个套接字就是五元组(协议、本地地址、外部地址、状态、PID),有的时候也被叫做四元组,四元组不包括协议。

开启客户端建立连接通信之后
在这里插入图片描述
可以看到开了两个进程,因为我们客户端和服务端都是在一个电脑上跑的,所以会出现这种情况。127.0.0.1是本机的环回地址。

端口号2381和我们程序中拿到的也是一样的。
在这里插入图片描述

扩展

服务端大致流程

在创建ServerSocket 实例的时候,他就已经监听了服务器本地的DEFAULT_PORT端口

serverSocket = new ServerSocket(DEFAULT_PORT);

进入构造函数(CTRL进入)

    public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
    	// new 实例,里面还有一系列逻辑,里面有工厂可以new 实例
 		setImpl();
        //删除了判断输入参数的代码 
        try {
        	//绑定指定端口
            bind(new InetSocketAddress(bindAddr, port), backlog);
        }
        // 删除了出错和异常状态处理的代码
    }

bind 函数

public void bind(SocketAddress endpoint, int backlog) throws IOException {
        // 删除一系列if语句,用于判断业务的前提环境是否异常
        InetSocketAddress epoint = (InetSocketAddress) endpoint;
        try {
			//删除 SecurityManager 安全管理器的检查,检查该线程是否可以监听该端口
				
			//将此Serversocket绑定到指定的本地 IP 地址和端口号
            getImpl().bind(epoint.getAddress(), epoint.getPort());
            //实现对该端口的监听,backlog参数是socket上请求的最大挂起连接数
            getImpl().listen(backlog);
            bound = true; // 标志字段,表示绑定成功
        } 
        //删除了出错和异常状态处理的代码
    }

所以,serversocket一经诞生就已经绑定监听了端口,不绑定监听端口说明没有构造完,这也是他天生的职责。

Socket socket = serverSocket.accept();

监听要与此套接字建立的连接并接受它。 该方法阻塞,直到建立连接

    public Socket accept() throws IOException {
        if (isClosed())
            throw new SocketException("Socket is closed");
        if (!isBound())
            throw new SocketException("Socket is not bound yet");
        // 老规矩,if检查
        Socket s = new Socket((SocketImpl) null);
        //安全管理器的checkAccept方法将使用s.getInetAddress().getHostAddress() s.getPort()和s.getPort()作为其参数调用,以确保允许操作。 这可能会导致 SecurityException。
        implAccept(s);
        return s;
    }

获取向客户端读、写的字符流。(socket的数据肯定是通过运输层协议通信而来的,而网络通信的数据一般为字节流数据,便于网络传输)
InputStreamReader 是字节流通向字符流的桥梁,它将字节流转换为字符流.
OutputStreamWriter是字符流通向字节流的桥梁,它将字符流转换为字节流.

BufferedReader
BufferedWriter
BufferedReader和BufferedWriter 获取到字符流后,可直接缓存,以增加缓冲的方式来提高输入和输出的效率

从read()方法理解,若使用InputStreamReader的read()方法,可以发现存在每2次就会调用一次解码器解码,但若是使用BufferedReader包装InputStreamReader后调用read()方法,可以发现只会调用一次解码器解码,其余时候都是直接从BufferedReader的缓冲区中取字符即可

从read(char cbuf[], int offset, int length)方法理解,若使用InputStreamReader的方法则只会读取leng个字符,但是使用BufferedReader类则会读取读取8192个字符,会尽量提取比当前操作所需的更多字节;

例如文件中有20个字符,我们先通过read(cbuf,0,5)要读取5个字符到数组cbuf中,然后再通过read()方法读取1个字符。那么使用InputStreamReader类的话,则会调用一次解码器解码然后存储5个字符到数组中,然后又调用read()方法调用一次解码器读取2个字符,然后返回1个字符;等于是调用了2次解码器,若使用BufferedReader类的话则是先调用一次解码器读取20个字符到字符缓冲区中,然后复制5个到数组中,在调用read()方法时,则直接从缓冲区中读取字符,等于是调用了一次解码器

因此可以看出BufferedReader类会尽量提取比当前操作所需的更多字节,以应该更多情况下的效率提升,因此在设计到文件字符输入流的时候,我们使用BufferedReader中包装InputStreamReader类即可

 reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

服务端一行一行的读取读取客户端发送的消息,客户端发送“quit”给服务器时,才表示此客户端要退出

String msg = null;
while ((msg = reader.readLine()) != null) {
    // 读取客户端发送的消息
    System.out.println("客户端["+socket.getInetAddress()+":"+ socket.getPort() + "]: " + msg);

    // 回复客户发送的消息
    writer.write("服务器已收到: " + msg + "\n");
    writer.flush();

    // 查看客户端是否退出
    if (QUIT.equalsIgnoreCase(msg)) {
        System.out.println("客户端["+socket.getInetAddress()+":"+ socket.getPort() + "]已断开连接");
        break;
    }
}

最后close()各种资源

finally {
    try {
        serverSocket.close();
        reader.close();
        writer.close();
        System.out.println("关闭serverSocket");
    } catch (IOException e) {
        e.printStackTrace();
    }

}

客户端大致流程:

socket = new Socket(DEFAULT_SERVER_HOST , DEFAULT_PORT);

构造函数

//创建一个流套接字并将其连接到指定主机上的指定端口号。
public Socket(String host, int port) throws UnknownHostException, IOException{
    this(host != null ? new InetSocketAddress(host, port) :
         new InetSocketAddress(InetAddress.getByName(null), port),
         (SocketAddress) null, true);
}

他的跳转太多太细,我这里放一个执行到connect0()的调用栈,后缀一般带0的都是native方法。
connect0()方法会实现到服务器的连接
我们可以看到:连接业务的开始也是写到socket的构造函数里面的。我们new Socket(DEFAULT_SERVER_HOST , DEFAULT_PORT,里面会有专门的方法比如上面的new InetSocketAddress(host, port)来检查host,port的合法性然后生成/127.0.0.1:8000合法的格式(一个SocketAddress实例),总结起来就是一条业务链上,会横插入很多检查性的或者其他的业务代码,然后代码就会跳来跳去。
在这里插入图片描述
具体怎么连接传递数据已经被封装好了。

连接好了之后,同理获取向服务器读、写的字符流。

reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

写代码获取我们控制台的输入流

consoleReader = new BufferedReader(new InputStreamReader(System.in));

将从控制台获取的数据input,通过writer写入,来与服务器交互信息

while (true) {
    String input = consoleReader.readLine();

    // 发送消息给服务器
    writer.write(input + "\n");
    writer.flush();

    // 读取服务器返回的消息
    String msg = reader.readLine();
    System.out.println(msg);

    // 查看用户是否退出
    if (QUIT.equalsIgnoreCase(input)) {
        break;
    }
}

最后记得close() 各种资源

finally {
    try {
        writer.close(); //关闭之前还会flush一次
        socket.close();
        reader.close();
        consoleReader.close();
        System.out.println("关闭socket");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

References:

  • https://kaven.blog.csdn.net/article/details/104149443
  • https://coding.imooc.com/class/381.html
  • https://www.cnblogs.com/liusxg/p/3917624.html
  • https://blog.csdn.net/jiaomingliang/article/details/45950591
  • https://www.cnblogs.com/winterfells/p/8745297.html
  • https://blog.csdn.net/ai_bao_zi/article/details/81134801
  • https://www.jianshu.com/p/42918db85f19
  • https://mp.weixin.qq.com/s/3Ma4nnkZNWiXacS7Ds2x4Q
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-12-15 18:43:00  更:2021-12-15 18:44:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 5:37:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码