Socket的连接过程、TCP的一些参数

前置知识

用到的命令
netstat -natp 查看网络连接和占用的端口
tcpdump -nn -i eth0 port 9090 开监听抓取数据包
lsof -p <进程号>查看某个进程已经打开的文件状态

Socket

服务端代码

package com.bjmashibing.system.io;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;public class SocketIOPropertites {//server socket listen property: 这些配置不是JVM层级的,是关联到内核的TCP协议栈的一些选项参数。private static final int RECEIVE_BUFFER = 10;private static final int SO_TIMEOUT = 0;  // 服务端的超时时间private static final boolean REUSE_ADDR = false;private static final int BACK_LOG = 2; // 多少个连接可以被积压//client socket listen property on server endpoint:private static final boolean CLI_KEEPALIVE = false;private static final boolean CLI_OOB = false;private static final int CLI_REC_BUF = 20;private static final boolean CLI_REUSE_ADDR = false;private static final int CLI_SEND_BUF = 20;private static final boolean CLI_LINGER = true;private static final int CLI_LINGER_N = 0;private static final int CLI_TIMEOUT = 0;  // 客户端的超时时间private static final boolean CLI_NO_DELAY = false;
/*StandardSocketOptions.TCP_NODELAYStandardSocketOptions.SO_KEEPALIVEStandardSocketOptions.SO_LINGERStandardSocketOptions.SO_RCVBUFStandardSocketOptions.SO_SNDBUFStandardSocketOptions.SO_REUSEADDR*/public static void main(String[] args) {ServerSocket server = null;try {server = new ServerSocket();server.bind(new InetSocketAddress(9090), BACK_LOG);server.setReceiveBufferSize(RECEIVE_BUFFER);server.setReuseAddress(REUSE_ADDR);server.setSoTimeout(SO_TIMEOUT);} catch (IOException e) {e.printStackTrace();}System.out.println("server up use 9090!");try {while (true) {// System.in.read();  //分水岭:Socket client = server.accept();  //阻塞的,没有 -1  一直卡着不动  accept(4,System.out.println("client port: " + client.getPort());client.setKeepAlive(CLI_KEEPALIVE);client.setOOBInline(CLI_OOB);client.setReceiveBufferSize(CLI_REC_BUF);client.setReuseAddress(CLI_REUSE_ADDR);client.setSendBufferSize(CLI_SEND_BUF);client.setSoLinger(CLI_LINGER, CLI_LINGER_N);client.setSoTimeout(CLI_TIMEOUT);client.setTcpNoDelay(CLI_NO_DELAY);//client.read   //阻塞   没有  -1 0new Thread(() -> {try {InputStream in = client.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(in));char[] data = new char[1024];while (true) {int num = reader.read(data);if (num > 0) {System.out.println("client read some data is :" + num + " val :" + new String(data, 0, num));} else if (num == 0) {System.out.println("client readed nothing!");continue;} else {System.out.println("client readed -1...");System.in.read();client.close();break;}}} catch (IOException e) {e.printStackTrace();}}).start();}} catch (IOException e) {e.printStackTrace();} finally {try {server.close();} catch (IOException e) {e.printStackTrace();}}}
}

客户端代码

package com.bjmashibing.system.io;import java.io.*;
import java.net.Socket;public class SocketClient {public static void main(String[] args) {try {Socket client = new Socket("192.168.150.11",9090);client.setSendBufferSize(20);client.setTcpNoDelay(true);  // 如果数据量比较小,会不会积攒起来再发,默认是trueclient.setOOBInLine(true);OutputStream out = client.getOutputStream();InputStream in = System.in;BufferedReader reader = new BufferedReader(new InputStreamReader(in));while(true){String line = reader.readLine();if(line != null ){byte[] bb = line.getBytes();for (byte b : bb) {out.write(b);}}}} catch (IOException e) {e.printStackTrace();}}
}

下面详细跟踪建立连接的过程

启动服务端

开启服务端后,出现了一个对于 9090 的 listen 状态。
TCP 三次握手是走 listen 的,建立连接之后,后面走文件描述符,那就是另外一个环节了,我们后面再讲。

使用jps得到服务端的进程id号:7932

使用lsof -p 7932查看7932端口的文件描述符的分配情况。

启动客户端
客户端启动,进入代码的阻塞等待用户输入逻辑

在服务端抓到了三次握手的包

在服务端看到建立了连接,虽然连接还未被使用。

在客户端进行用户输入之后(服务端也有的阻塞的逻辑,需要回车才能接收client的数据)

继续查看服务端抓包监听

查看服务端的连接状态:双方开辟了资源。即便你程序不要我,我也在内核里有资源用来接收或者等待一类的。

服务端输入回车之后
接受到了客户端发过来的数据

刚才的socket连接已经被分配给7932了

lsof 得到了新的文件描述符 6

总结一下

TCP:面向连接的,可靠的传输协议

Socket:是一个四元组。ip:port ip:port四元组的任何一个元的不同,都可以区分不同的连接。

面试题 1:服务端80端口接收客户端连接之后,是否需要为客户端的连接分配一个随机端口号?
:不需要。

面试题 2:现在,有一个客户端,有一个服务端,
客户端的ip地址是AIP,程序使用端口号CPORT想要建立连接。
服务端的IP地址是XIP,端口号是XPORT。
现在假设某一个客户端A开了很多连接占满了自己的65535个端口号,那客户端A是否还能与另一个服务端建立建立连接?
:可以,因为只要能保证四元组唯一即可

注:一台服务器是可以与超过65535个客户端保持长连接的,调优到超过百万连接都没问题,只要四元组唯一就可以了。客户端来了之后,服务端是不需要单独给它开辟一个端口号的。

下面这个图可以说明,无论再多的连接,服务端始终是使用的同一个<ip:端口>

那么,我们常见的报错“端口号被占用”是什么原因?

我们常见的报错“端口号被占用”实际上是在启动SocketSocket的时候,而不是Socket,两者不是一个概念。如果两个服务使用了相同的端口号,这时如果来了一个数据包,内核无法区分是哪一个服务在LISTEN,不知道要发给哪一个服务了,如下图例子

每一个独立的进程只要维护它自己的文件描述符唯一即可。

keepalive

三个不同层级的 keepalive

  • TCP协议中规定,如果双方建立的连接(虚无的,并不是物理的连接),如果双方很久都不说话,你能确定对方还活着吗?不能,因为可能突然断电。所以规定了这么一种机制,哪怕是周期性的消耗一些网络资源,也要及时把无效的连接踢掉,节省内存。
  • HTTP级别
  • 负载均衡keepalived

网络IO的变化 演进模型(BIO)

一句话概括BIO?

BIO就是,客户端来一个连接,抛出一个线程,来一个连接,抛出一个线程…

几个维度

同步、异步、阻塞、非阻塞

用到的命令:

strace -ff -o out /usr/java TestSocket
用来追踪Java程序和内核进行了哪些交互(进行了哪些系统调用)

详细追踪 BIO 的连接过程

TestSocket.java


用JDK1.4跑起来

在服务端用jps找到进程的id号是8384

在服务端使用tail监控out.8384文件的输出(8384是main线程的输出,其他的out可能是一些垃圾回收线程等其他线程的输出)
(这里注意一下一共有8个线程,待会儿建立连接之后再看)

可以看到JVM用到了内核系统调用的accept,main线程正在阻塞

在一个客户端上建立一个连接

在服务端我们看到,刚才阻塞 accept(3, 的位置继续执行。34178是客户端连接进来的随机端口号,192.1618.150.12是来自于客户端的ip地址

clone是linux的一个系统调用。Java当中的一个线程,就是操作系统的一个子线程。下图我们看到,(客户端连接进来之后),服务端调用clone函数,开启了一个线程号为8447的新线程。flags里面记录的是子线程共享的文件系统、打开的文件等父线程的系统资源。
下面又开始阻塞的accept

查看用strace输出的out文件,也可以证明8447这个新线程的存在。

在服务端可以看到,多了一个文件描述符5,表示的是从node01(服务端机器名称)node02(客户端机器名称)的已连通的状态(socket四元组)

服务端 8447.out 正在recv阻塞接收

想学好Linux,去学习文档中这些man帮助手册,有时候比网络上的博客文章更准确(也可以 man man 查看帮助文档本身的帮助文档)
使用man 2 socket,你会发现所谓socket系统调用,其实就是调用了一个有返回值(文件描述符)的函数(用于LISTEN)

稍稍总结一下

BIO 模型的整个连接过程

无论哪种IO模型,application想要和外界通信,都要进行上面所展示的一系列的(3步)系统调用,都是不可缺少的。
之后服务端进入阻塞状态accept(3,等待客户端的连接。此次阻塞被成功地连接之后,又进入一的新的阻塞,等待新的客户端连接。
一旦连接成功之后,会为这个连接抛出去一个新的线程,新的线程中又进入一个阻塞状态recv(5,等待接收消息。

网络与IO知识扫盲(三):从系统调用的角度,剖析 Socket 的连接过程、BIO 的连接过程相关推荐

  1. 网络与IO知识扫盲(五):从 NIO 到多路复用器

    NIO 的优劣 优势:相比 BIO 来说,NIO 可以通过1个或几个线程,来解决 N 个 IO 连接的处理 弊端:当有大量文件描述符存在时,不管你用多少个线程,都是O(n)复杂度的recv调用,需要用 ...

  2. 网络与IO知识扫盲(六):多路复用器

    NIO存在的问题 NIO的优势:可以通过一个或几个线程来解决N个IO连接的处理 NIO存在C10K问题:当客户端连接的数量达到10K时,单线程每循环一次所有的fd,成本是O(n)复杂度,每一次循环都会 ...

  3. 网络与IO知识扫盲(四):C10K问题、BIO的弊端与NIO的引入

    C10K 问题 C10K 问题: http://www.kegel.com/c10k.html 我们使用BIO的时候,来一个连接就抛出一个线程.被抛出的独立的线程进行阻塞,等待接收已连接的client ...

  4. 网络与IO知识扫盲(一):Linux虚拟文件系统,文件描述符,IO重定向

    系统IO原理 在 Linux 中: VFS(Virtual Filesystem Switch):虚拟文件系统,是一个目录树.树上不同的节点可以映射到物理的文件地址,可以挂载. 相当于一个解耦层,在具 ...

  5. 网络与IO知识扫盲(七):仿照Netty工作架构图,手写多路复用模型

    Netty工作架构图 从图上看来: 一个线程在 Boss Group 中负责接收 另外两个线程在 Worker Group 中由接收之后的连接分配过去,负责读写 根据上图模型,仿照Netty手写一个多 ...

  6. 网络工程师必备知识:苹果MAC系统下使用USB转console线配置交换机的连接方法

    现在用苹果操作系统的人越来越多,作为网络工程师的使用工具之一USB转串口线的转接头,如果在苹果系统下使用呢? 1.首先自然是先安装转接头光盘自带的驱动程序了.也可以到使用的品牌的官网上去下载. 2.安 ...

  7. 网络知识扫盲,一文搞懂 DNS

    在找工作面试的过程中,面试官非常喜欢考察基础知识,除了数据结构与算法之外,网络知识也是一个非常重要的考察对象. 而网络知识,通常是很抽象,不容易理解的,有很多同学就在这里裁了跟头.为了更好地通过面试, ...

  8. android进阶3step2:Android App通信 ——端口号IP等网络基础知识扫盲

    网络操作基础知识 一.IP 地址和端口号 1) IP 地址用于在网络中唯一标识一台机器(通信实体),是一个 32 位整数,通常 用 4 个 0-255 的十进制数标识; 2) 端口号用于唯一标识通信实 ...

  9. Lwip从入门到放弃之(一)---基础网络知识扫盲

    Lwip从入门到放弃之-基础网络知识扫盲(一) 由于工作中用到了有关Lwip的有关知识,本人作为一个网络通信协议的门外汉,打算系统的学习一下以太网通讯的有关知识.而Lwip作为一款开源的轻量级TCP/ ...

最新文章

  1. 深度神经网络在NLP的应用!
  2. Redux 进阶 - react 全家桶学习笔记(二)
  3. 中兴计算机专业,中兴计算机专业面试题.pdf
  4. PDE9 wave equation: general solution
  5. python alphago_资源 | 如何通过 Python 打造一款简易版 AlphaGo?
  6. TTL电平和CMOS电平的区别及其应用
  7. 关于html 音乐播放器代码|音乐播放器网页代码大全(转),关于HTML 音乐播放器代码|音乐播放器网页代码大全...
  8. 泳池水质监控PH温度浑浊度测量_基于STC89C51单片机
  9. 金蝶KIS专业版单据序时簿看不到的问题
  10. 计算机网络 --- IP地址的详细分类
  11. 码蹄集 - MT3029 - 新月轩就餐
  12. day17-李大人part1
  13. 正菱台体积在线计算机,正多棱台体积,表面积,棱长,斜高,底面积,质量在线计算器_三贝计算网_23bei.com...
  14. 保存系统的操作日志,通过swagger注解获取请求描述(通用版本)
  15. 网优谷程序员都需要知道的8个冷知识
  16. 玩家交互体验—剑侠情缘网络版叁
  17. 怎样能把两张照片拼成一张图片,5种工具分享
  18. R语言read.table读取tsv文件
  19. 将ts视频文件转换为图片
  20. pug模板引擎基本用法

热门文章

  1. 洛谷 - P2762 太空飞行计划问题(最大权闭合图+路径打印)
  2. Docker教程-深度学习环境配置
  3. 基于java的餐饮管理系统_基于java的餐饮管理系统
  4. nth_element
  5. python3爬虫(8)爬虫框架scrapy安装和使用
  6. 基于 ida 的反汇编转换 Obj 的可行性 笔记(1)
  7. C++虚函数---我的理解
  8. 聚美app之 _sign参数分析
  9. 无人值守的自动 dump(二)
  10. 数据结构--图(Graph)详解(二)