基于JAVA Socket的底层原理分析及工具实现

前言

 在工作开始之前,我们先来了解一下Socket

  所谓Socket,又被称作套接字,它是一个抽象层,简单来说就是存在于不同平台(os)的公共接口。学过网络的同学可以把它理解为基于传输TCP/IP协议的进一步封装,封装到以至于我们从表面上使用就像对文件流一样的打开、读写和关闭等操作。此外,它是面向应用程序的,应用程序可以通过它发送或接收数据而不用过多的顾及网络协议。

 那么,Socket是存在于不同平台的公共接口又是什么意思呢?  

  形象的说就是“插座”,是不同OS之间进行通信的一种约定或一种方式。通过 Socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。Socket 的典型应用就是 Web 服务器和浏览器,浏览器获取用户输入的 URL,通过解析出服务器的IP地址,向服务器IP发起请求,服务器分析接收到的 URL,将对应的网页内容返回给浏览器,浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。

 问题又来了,不通过系统之间能否进行Socket通信呢?

  首先,我们了解一下常用操作系统中的Socket。

  在 UNIX/Linux 系统中,为了统一对硬件的操作,简化接口,不同的硬件设备都被看成一个文件。对这些文件的操作,就等同于对磁盘上普通文件的操作。

  你也许听很多高手说过,UNIX/Linux 中的一切都是文件!那个家伙说的没错。

  学过操作系统的同学可能知道,当对文件进行I/O操作时,系统通常会为文件分配一个ID,也就是文件描述符。简单来讲就是系统对文件的操作转化为

对文件描述符的操作,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。

  同样的,网络连接也被定义为是一种类似的I/O操作,类似于文件,它也有文件描述符。

  所以当我们可以通过 Socket来进行一次通信时,也可以被称作操作网络文件的过程。在网络建立时,socket() 的返回值就是文件描述符。有了这个文

件描述符,我们就可以使用普通的文件操作函数来传输数据了,例如:

  • 用 read() 读取从远程计算机传来的数据;
  • 用 write() 向远程计算机写入数据。

  不难发现,除了不同主机之间的Socket建立过程我们还不清楚,Socket的通信过程就是简单的文件流处理过程。

  在Windows系统中,也有类似“文件描述符”的概念,但通常被称为“文件句柄”。因此,本教程如果涉及 Windows 平台将使用“句柄”,如果涉及Linux

平台则使用“描述符”。与UNIX/Linux 不同的是,Windows 会区分 socket 和文件,Windows 就把 socket 当做一个网络连接来对待,因此需要调用专们

针对 socket 而设计的数据传输函数,针对普通文件的输入输出函数就无效了。

步入正题

  说了这么多,到底不同系统间不同定义的Socket是怎么通信的呢?

  在此,我们以JAVA Socket 与 Linux Socket的关系分析为例进行说明。首先,拿TCP Socket通信过程来讲,就是客户端与服务器进行TCP数据交互,它分为一下几个步骤:

  1. 系统分配资源,服务端开启Socket进程,对特定端口号进行监听
  2. 客户端针对服务端IP进行特定端口的连接
  3. 连接建立,开始通信
  4. 通信完成,关闭连接

  以TCP的通信过程为例,过程如下:

  具体来说,JAVA是怎样完成对底层Linux Socket接口的调用的呢?以下图为例,当我们在JAVA创建一个TCP连接时,需要首先实例化JAVA的ServerSocket类,其中封装了底层的socket()方法、bind()方法、listen()方法。

其中,socket()方法是JVM对Linux API的调用,详细如下

    1 创建socket结构体
    2 创建tcp_sock结构体,刚创建完的tcp_sock的状态为:TCP_CLOSE
    3 创建文件描述符与socket绑定

  bind ()方法在Linux 的底层详细如下:

    1.将当前网络命名空间名和端口存到bhash()

    可以理解为,绑定到系统能够找到的地方。

  listen()方法在Linux 的底层详细如下:

    1.检查侦听端口是否存在bhash中

    2.初始化csk_accept_queue

    3.将tcp_sock指针存放到listening_hash表

    简单来讲就是验证连接请求的端口是否被开启。

  accpet()方法在Linux 的底层详细如下:  

1.调用accept方法

2.创建socket(创建新的准备用于连接客户端的socket)

3.创建文件描述符

4.阻塞式等待(csk_accept_queue)获取sock

我们知道在listen阶段,会为侦听的sock初始化csk_accept_queue,此时这个queue为空,所以accept()方法会在此时阻塞住,直到后面有客户端成功握手后,这个queue才有sock.如果csk_accept_queue不为空,则返回一个sock.后续的逻辑如accept第二个图所示,其步骤如下:

5.取出sock

6.socket与sock互相关联

7.socket与文件描述符关联

8.将socket返回给线程

  到此,JAVA调用Linux API的初始步骤完成。

  让我们来用JAVA Socket进行简单的编码实现。目标:

  1. 能够实现数据通信

  2. 能够实现客户端和服务端多对一连接。

  在此,以基于TCP连接过程为例,完成以上编码。服务端较为容易实现,它只需要开启监听端口,等待连接。同时对发送和接收模块进行封装,以证上面所说Socket通信相似于IO过程。

Server端代码:

1 package tcp_network; 2 3 import java.io.DataInputStream; 4 import java.io.DataOutputStream; 5 import java.io.IOException; 6 import java.net.ServerSocket; 7 import java.net.Socket; 8 9 public class Tcp_server {10 public static void main(String arg[]) throws IOException { 11 System.out.print('服务端启动.......\n');12 ServerSocket server = new ServerSocket(9660); //初始化一个监听端口,让系统分配相关socket资源13 boolean isRunable = true;14 while (isRunable){//循环等待连接的建立15 Socket client = server.accept();16 System.out.print('一个客户端建立了连接.......\n');17 new Thread(new Channel(client)).start();//每有一个通信连接,将它放到新的线程中去,实现一个服务端对多个客户端18 }19 server.close();20 }21 public static class Channel implements Runnable{//封装服务的类,完成接收和发送的实现22 private Socket client;23 private DataInputStream in_data;24 private DataOutputStream out_data;25 public Channel(Socket client) throws IOException { //构造函数加载,简单初始化相关输入输出流26 this.client = client;27 in_data = new DataInputStream(client.getInputStream());//将通信的字节流封装为IO的输入输出流28 out_data = new DataOutputStream(client.getOutputStream());29 }30 public String receive() throws IOException {//通过对输入流31 String data = in_data.readUTF();32 return data;33 }34 public void send(String msg) throws IOException {35 out_data.writeUTF(msg);//将数据写到数据流当中36 out_data.flush();//刷新缓冲,发送数据37 }38 public void release() throws IOException {//连接结束时,释放系统资源39 in_data.close();40 out_data.close();41 client.close();42 }43 @Override44 public void run() {45 try {46 String receive_data;47 while (true){48 receive_data = receive();49 if(!receive_data.equals(''))50 {51 if(receive_data.equals('Hello'))52 {53 System.out.print('IP:'+client.getInetAddress()+' 客户端信息:'+receive_data+'\n');54 send('Hi');55 }56 else {57 System.out.print('IP:'+client.getInetAddress()+' 客户端信息:'+receive_data+'\n');58 send(receive_data.toUpperCase());59 }60 }61 }62 } catch (IOException e) {63 try {64 release();65 } catch (IOException ex) {66 ex.printStackTrace();67 }68 e.printStackTrace();69 }70 }71 }72 }

Client端代码:

package tcp_network;import java.io.*;import java.net.Socket;public class Tcp_client {    public static void main(String arg[]) throws IOException {        Socket client = new Socket('localhost',9660);//新建socket资源        boolean isRuning = true;        while (isRuning) {            //new Send(client).send();            //new Receive(client).receive();            new Thread(new Send(client)).start();//启动发送线程            new Thread(new Receive(client)).start();//启动接收线程        }    }    public static class Send implements Runnable    {        private DataOutputStream out_data;        private BufferedReader console;        private String msg;        public Send(Socket client) throws IOException {            this.console = new BufferedReader(new InputStreamReader(System.in));//接收系统输入            this.msg = init();            try {                this.out_data = new DataOutputStream(client.getOutputStream());//将字符流转化为数据流            }catch (Exception e){                e.printStackTrace();            }        }        private String init() throws IOException {            String msg=console.readLine();            return msg;        }        @Override        public void run() {//在线程体内实现发送数据            try {                out_data.writeUTF(msg);                out_data.flush();                System.out.println('send date !');            }catch (Exception e){                e.printStackTrace();            }        }    }    public static class Receive implements Runnable{//将接收模块单独封装,目的是避免通信时接收一直阻塞        private DataInputStream in_data;        private String msg;        public Receive(Socket client){            try{                in_data = new DataInputStream(client.getInputStream());//转换流            } catch (IOException e) {                e.printStackTrace();            }        }        @Override        public void run() {            String data = null;            try {//在线程中实现接收  IO缓冲区数据并输出                data = in_data.readUTF();            } catch (IOException e) {                e.printStackTrace();            }            System.out.print('服务端:'+data+'\n');        }    }}

注意:在客户端需要把发送和接收模块放到两个线程中去,否则会出现客户端一直阻塞等待接收,不能进行下次发送数据的情况(解决办法:放到不同线程中接收发送能够互不影响)。

效果如下:

 

参考:

  https://blog.csdn.net/vipshop_fin_dev/article/details/102966081

  http://c.biancheng.net/view/2128.html

(0)

相关推荐

  • day21 网络编程(下)

    版权声明:本博客转载自路飞学城Python全栈开发培训课件,仅用于学习之用,严禁用于商业用途. 欢迎访问路飞学城官网:https://www.luffycity.com/ 课程目标:学会网络编程开发的 ...

  • 基于Socket编程的聊天工具【Java实现】

    思路: 要实现聊天功能,我们就必须有服务器和客户端.客户端连接到服务器,然后通过发送消息到服务器及从服务器读取消息来达到多客户端通信的目的.简单来说,所有客户端都是通过服务器来进行身份验证和消息发送的 ...

  • MySQL索引失效底层原理分析,MySQL索引为什么失效,这篇文章全部告诉你

    前言 吊打面试官又来啦,今天我们讲讲MySQL索引为什么会失效,很多文章和培训机构的教程,都只会告诉你,在什么情况下索引会失效. 比如:没遵循最佳左前缀法则.范围查询的右边会失效.like查询用不到索 ...

  • Activiti工作流学习笔记(三)——自动生成28张数据库表的底层原理分析

    原创/朱季谦 网上关于工作流引擎Activiti生成表的机制大多仅限于四种策略模式,但其底层是如何实现的,相关文章还是比较少,因此,觉得撸一撸其生成表机制的底层原理. 我接触工作流引擎Activiti ...

  • 《Java 底层原理》String字符串详解

    前言 Java 字符串底层是如何存储的,如何提高性能的,今天就来好好了解一下. 字符串的存储结构 Jvm 有专门的字符串常量池用于存放字符串,存放字符串的数据结构是HashTable. HashTab ...

  • 直流电源屏蔽机制是怎样的?基于原理分析

    直流高频变压器屏蔽层的绕组层原理变压器屏蔽层是指变压器外部的绕组或铜皮以及接地吗? 今天我们要讨论的主题是直流电源屏蔽机制,与许多客户工程师联系,了解到了与变压器有关的EMI问题,得知增加DC屏蔽会改 ...

  • 基于属性偏序原理分析刘凤斌教授治疗重症肌无力方剂配伍规律

    目的:分析刘凤斌教授治疗重症肌无力方剂配伍规律.方法:基于属性偏序原理,筛选门诊患者首诊处方,共147个,生成形式背景,将方剂与药物信息归入不同的簇集,得出刘凤斌教授治疗重症肌无力的常用药物及基础方, ...

  • 基于杠杆平衡的防汛挡水板设计原理分析

    图1 创客焦作 来自瑞典的挡水神器 竟然是初中的杠杆原理 图2 这些看似单薄的L型板,就是能抵御住汹涌的洪水,有图有真相,有视频为证. 视频 细细想来,这款挡水板的原理并不复杂:可以假设挡水板上方有一 ...

  • 基于能量流理论的谐振式无线电能传输原理分析与验证

    武汉加油 共渡难关 点击下面标题,了解通知详情 第九届电工技术前沿问题学术论坛征文通知 摘要 中国电力科学研究院有限公司.国网辽宁省电力有限公司电力科学研究院.辽宁东科电力有限公司的研究人员诸嘉慧.刘 ...

  • HR必看—基于价值链的人效分析

    自去年开始,"人力资源效能"这个词非常火,火到人力资源从业者不了解一点好像就落伍了一样,但是实际操作过程中,犹如海市蜃楼.伴随着市场竞争的加剧,商业环境发生着巨大变化,企业在能力迭 ...

  • 电水壶的电路原理分析与检测

    电水壶的基本功能是烧水.电水壶根据结构分为一体式和分体式两种,根据功能分为非保温型和保温型两种. 一.分体非保温式电水壶的检测 下面以格来德 WEF-115S 电水壶为例,介绍使用万用表检修分体非保温 ...