前言

 在工作开始之前,我们先来了解一下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. @Override
  44. 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端代码:

  1. package tcp_network;
  2.  
  3. import java.io.*;
  4. import java.net.Socket;
  5.  
  6. public class Tcp_client {
  7. public static void main(String arg[]) throws IOException {
  8. Socket client = new Socket("localhost",9660);//新建socket资源
  9. boolean isRuning = true;
  10. while (isRuning) {
  11. //new Send(client).send();
  12. //new Receive(client).receive();
  13. new Thread(new Send(client)).start();//启动发送线程
  14. new Thread(new Receive(client)).start();//启动接收线程
  15. }
  16. }
  17. public static class Send implements Runnable
  18. {
  19. private DataOutputStream out_data;
  20. private BufferedReader console;
  21. private String msg;
  22. public Send(Socket client) throws IOException {
  23. this.console = new BufferedReader(new InputStreamReader(System.in));//接收系统输入
  24. this.msg = init();
  25. try {
  26. this.out_data = new DataOutputStream(client.getOutputStream());//将字符流转化为数据流
  27. }catch (Exception e){
  28. e.printStackTrace();
  29. }
  30. }
  31. private String init() throws IOException {
  32. String msg=console.readLine();
  33. return msg;
  34. }
  35. @Override
  36. public void run() {//在线程体内实现发送数据
  37. try {
  38. out_data.writeUTF(msg);
  39. out_data.flush();
  40. System.out.println("send date !");
  41. }catch (Exception e){
  42. e.printStackTrace();
  43. }
  44. }
  45. }
  46. public static class Receive implements Runnable{//将接收模块单独封装,目的是避免通信时接收一直阻塞
  47. private DataInputStream in_data;
  48. private String msg;
  49. public Receive(Socket client){
  50. try{
  51. in_data = new DataInputStream(client.getInputStream());//转换流
  52.  
  53. } catch (IOException e) {
  54. e.printStackTrace();
  55. }
  56. }
  57. @Override
  58. public void run() {
  59. String data = null;
  60. try {//在线程中实现接收 IO缓冲区数据并输出
  61. data = in_data.readUTF();
  62. } catch (IOException e) {
  63. e.printStackTrace();
  64. }
  65. System.out.print("服务端:"+data+"\n");
  66. }
  67. }
  68.  
  69. }

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

效果如下:

    

参考:

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

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

基于JAVA Socket的底层原理分析及工具实现的更多相关文章

  1. Java NIO使用及原理分析 (四)

    在上一篇文章中介绍了关于缓冲区的一些细节内容,现在终于可以进入NIO中最有意思的部分非阻塞I/O.通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至 ...

  2. Java NIO使用及原理分析 (四)(转)

    在上一篇文章中介绍了关于缓冲区的一些细节内容,现在终于可以进入NIO中最有意思的部分非阻塞I/O.通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至 ...

  3. 原子类java.util.concurrent.atomic.*原理分析

    原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...

  4. HashMap底层原理分析(put、get方法)

    1.HashMap底层原理分析(put.get方法) HashMap底层是通过数组加链表的结构来实现的.HashMap通过计算key的hashCode来计算hash值,只要hashCode一样,那ha ...

  5. 基于Java 生产者消费者模式(详细分析)

    Java 生产者消费者模式详细分析 本文目录:1.等待.唤醒机制的原理2.Lock和Condition3.单生产者单消费者模式4.使用Lock和Condition实现单生产单消费模式5.多生产多消费模 ...

  6. 基于JDK的动态代理原理分析

    基于JDK的动态代理原理分析 这篇文章解决三个问题: What 动态代理是什么 How 动态代理怎么用 Why 动态代理的原理 动态代理是什么? 动态代理是代理模式的一种具体实现,是指在程序运行期间, ...

  7. JAVA常用数据结构及原理分析

    JAVA常用数据结构及原理分析 http://www.2cto.com/kf/201506/412305.html 前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源码,balaba ...

  8. (6)Java数据结构-- 转:JAVA常用数据结构及原理分析

    JAVA常用数据结构及原理分析  http://www.2cto.com/kf/201506/412305.html 前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源码,balab ...

  9. 移动开发首页业界资讯移动应用平台技术专题 输入您要搜索的内容 基于Java Socket的自定义协议,实现Android与服务器的长连接(二)

    在阅读本文前需要对socket以及自定义协议有一个基本的了解,可以先查看上一篇文章<基于Java Socket的自定义协议,实现Android与服务器的长连接(一)>学习相关的基础知识点. ...

随机推荐

  1. springbooot+restful目录规则

    dao是访问数据层,dto是数据传出层,po实体类

  2. PHP大文件分片上传

    前段时间做视频上传业务,通过网页上传视频到服务器. 视频大小 小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题:1,文件过大,超出服务端的请求大小限制:2,请求时间过长, ...

  3. Appium基础教程

    目录 Appium教程 Appium简介 App自动化测试工具对比 Appium实现原理 环境搭建 Andorid介绍 基本架构 常见布局/视图 基本控件 控件常见属性 Adb介绍 Adb常用命令 A ...

  4. 下载 OllyDbg

    http://www.ollydbg.de/

  5. SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xce in position 0: invalid continuatio

    点击  文档>>设置文件编码>>Unicode>>Unicode(UTF-8)

  6. synchronized的对象锁和类锁

    概念 synchronized 是 Java 中的关键字,是利用锁的机制来实现同步的. 锁机制有如下两种特性: 互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制, ...

  7. epoll反应堆

    /* * epoll基于非阻塞I/O事件驱动 */ #include <stdio.h> #include <sys/socket.h> #include <sys/ep ...

  8. axios中出现两次请求,OPTIONS请求和GET请求

    在项目中发现ajax中出现两次请求,OPTIONS请求和GET请求 查看到浏览器NetWork有两次请求,请求url一样: 查找原因是浏览器对简单跨域请求和复杂跨域请求的处理区别. XMLHttpRe ...

  9. css实现元素在div底部显示

    #CSS .1 {position:relative;} .2 {;} #HTML <div class="1"> <div class="2" ...

  10. nginx实现负载均衡、缓存功能实战

    nginx实现负载均衡.缓存功能实战 什么是正向代理?应用场景:翻墙 什么是反向代理?例如:haproxy和nginx   Nginx实现反向代理 nginx代理基于是ngx_http_proxy_m ...