TCP字节流服务
网络传输层中,TCP是面向连接、可靠的、字节流传输。
TCP协议通信双方必须先建立连接,通信双方必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输。TCP是全双工的,即双方的数据读写可通过一个连接进行。完成数据交换之后,通信双方都必须断开连接以释放系统资源。
TCP协议的这种连接是一对一的,所以基于广播和多播(目标是多个主机地址)的应用程序不能使用TCP服务。而无连接的UDP则十分适合这种广播和多播。
TCP协议使用字节流(UDP使用数据报),实际编程中字节流和数据报的主要区别体现在通信双方是否必须执行相同次数的读、写操作(只是表现形式),发送端应用程序连续执行多次写操作时,TCP模块先将这些数据放入TCP发送缓冲区中。当TCP模块真正开始发送数据时,发送缓冲区中这些等待发送的数据可能被封装成一个或多个TCP报文段发出。因此,TCP模块发送出的TCP模块发送出的TCP报文段的个数和应用程序执行的写操作次数之间没有固定的数量关系。
当接收端收到一个或多个TCP报文段后,TCP模块将他们携带的应用程序数据按照TCP报文段的序号依次放入TCP接收缓冲区中,并通知应用程序读取数据。接收端应用程序可以一次性将TCP接收缓冲区中的数据全部都出,也可以分多次读取,这取决于用户制定的应用程序读缓冲区的大小。因此,应用程序执行的读操作次数和TCP模块接收到的TCP报文段个数之间也没有固定的数量关系。
综上所述,发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,这就是字节流的概念:应用程序对数据的发送和接收是没有边界限制的。UDP则然,发送端应用程序没执行一次写操作,UDP模块就将其封装成一个UDP数据包并发送之。接收端必须及时针对每一个UDP数据报执行读操作(通过recvfrom系统调用),否则就会丢包(这经常发生在较慢的服务器上)。并且,如果过用户没有指定足够的应用程序缓冲区来读取UDP数据,则UDP数据将被截断。
以下就是TCP字节流服务和UDP数据报服务的具体框图:(TCP多对多,UDP一对一)
TCP头部结构
以下是TCP头部结构:
16位端口号:告知主机该报文段是来自哪里(源端口Source Port)以及传给哪个上层协议或应用程序(目的端口Destination Port)的。进行TCP通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名服务端口号(比如DNS协议对应端口53,HTTP协议对应80,这些端口号可在/etc/services文件中找到)。
32位序号:一次TCP通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。假设主机A和主机B进行TCP通信,A发送给B的第一个TCP报文段中,序号值被系统初始化为某个随机值ISN(Initial Sequence Number,初始序号值)。那么在该传输方向上(从A到B),后续的TCP报文段中序号值将被系统设置成ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移。例如,某个TCP报文段传送的数据是字节流中的第1025~2048字节,那么该报文段的序号值就是ISN+1025.另外一个传输方向(从B到A)的TCP报文段的序号值也具有相同的含义。
32位确认号(acknowledgement number):用作对另一方发送来的TCP报文段的响应。其值是收到的TCP报文段的序号值加1。假设主机A和主机B进行TCP通信,那么A发送出的TCP报文段不仅携带自己的序号,而且包含对B发送来的TCP报文段的确认号。反之,B发送出的TCP报文段也同时携带自己的序号和对A发送来的报文段的确认号。
4位头部长度(header length):标识该TCP头部有多少个32bit字(4字节)。因为4位最大能标识15,所以TCP头部最长是60字节。
6位标志位包含如下几项:
URG标志,表示紧急指针(urgent pointer)是否有效。
ACK标志,表示确认号是否有效。我们称携带ACK标识的TCP报文段为确认报文段。
PSH标志,提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间(如果应用程序不将接收
到的数据读走,它们就会一直停留在TCP接收缓冲区中)。
RST标志,表示要求对方重新建立连接。我们称携带RST标志的TCP报文段为复位报文段。
SYN标志,表示请求建立一个连接。我们称携带SYN标志的TCP报文段为同步报文段。
FIN标志,表示通知对方本端要关闭连接了。我们称携带FIN标志的TCP报文段为结束报文段。
16位窗口大小(window size):是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
16位校验和(TCP check sum):由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。
16位紧急指针(urgent pointer):是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。
TCP头部选项:TCP头部的最后一个选项字段(options)是可变长的可选信息。这部分最多包含40字节,因为TCP头部最长是60字节(其中还包含前面讨论的20字节的固定部分)。典型的TCP头部选项结构如下图所示。
选项的第一个字段kind说明选项的类型。有的TCP选项没有后面两个字段,仅包含1字节的kind字段。第二个字段length(如果有的的话)指定该选项的总长度,该长度包括kind字段和length字段占据的2字节。第三个字段info(如果有的话)是选项的具体信息。常见的TCP选项有7中,如下图
kind=0是选项表结束选项。
kind=1是空操作(nop)选项,没有特殊含义,一般用于将TCP选项的总长度填充为4字节的整数倍。
kind=2是最大报文段长度选项。TCP连接初始化时,通信双方使用该选项来协商最大报文段长度(Max Segement Size,MSS)。TCP模块通常将MSS设置为(MTU-40)字节(减掉的这40字节包括20字节的TCP头部和20字节的IP头部)。这样携带TCP报文段的IP数据报的长度就不会超过MTU(假设TCP头部和IP头部都不包含选项字段,并且这也是一般情况),从而避免本机发生IP分片。对以太网而言,MSS值是1460(1500-40)字节。
kind=3是窗口扩大因子选项。TCP连接初始化时,通信双方使用该选项来协商接收通告窗口的扩大因子。在TCP的头部中,接收通告窗口大小时用16位表示的,故最大为65535字节,但实际上TCP模块允许的接收通告窗口大小远不止这个数(为了提高TCP通信的吞吐量)。窗口扩大因子解决了这个问题。假设TCP头部中的接收通告窗口大小是N乘2的M次方,或者说N左移M位。注意,M的取值范围是0~14。我们可以通过修改/proc/sys/net/ipv4/tcp_window_scaling内核变量来启用或关闭窗口扩大因子选项。
kind=5是SACK实际工作的选项。该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块。每个块边沿(edge of block)参数包含一个4字节的序号。其中块左边沿表示不连续块的第一个数据的序号,而块右边沿则表示不连续块的最后一个数据的序号的下一个序号。这样一对参数(块左边沿和块右边沿)之间的数据是没有收到的。因为一个块信息占用8字节,所以TCP头部选项中实际上最多可以包含4个这样的不连续数据块(考虑选项类型和长度占用的2字节)。
kind=8是时间戳选项。该选项提供了较为准确的计算通信双方之间的回路时间(Round Trip Time,RTT)的方法,从而为TCP流量控制提供重要信息。我们可以通过修改/proc/sys/net/ipv4/tcp_timestamps内核变量来启用或关闭时间戳选项。
以下是抓到的一个TCP报文段:
从图中可以看出源端口号58991对应e66f,目的端口号443,对应01bb,Flags为SYN,表示请求一个连接,因此它是一个同步报文段。这里seq表示的是相对序号,因为该同步报文段是从180.85.25.164到74.125.204.100的第一个TCP报文段,所以该序号值也就是此次通信过程中该传输方向的ISN值,则相对序号即为0。并且,因为这是通信过程中第一个TCP报文段,所以它没有针对对方发送来的TCP报文段的确认值(尚未收到任何对方发来的TCP报文段)。
window size是接收通告窗口大小,因为这是一个同步报文段,所以win值反映的是实际的接收通告窗口大小。
SpringBoot 使用Swagger2打造在线接口文档(附汉化教程)
链接:
序言:编写和维护接口文档是每个程序员的职责,根据Swagger2可以快速帮助我们编写最新的API接口文档,再也不用担心开会前仍忙于整理各种资料了,间接提升了团队开发的沟通效率。此外,本教程还额外提供了UI汉化教程,去除阅读官方英文界面的烦恼。(目前Swagger汉化教程是找不到的,因为官方手册实在写得太烂。。)
SpringBoot + Swagger2 UI界面-汉化教程1.默认的英文界面UI
想必很多小伙伴都曾经使用过Swagger,但是打开UI界面之后,却是下面这样的画风,纯英文的界面并不太友好,作为国人还是习惯中文界面。
号称世界最流行的API工具总不该不支持国际化属性吧,楼主在官方使用手册找到关于本地化和翻译的说明:
也就是说,只要添加翻译器和对于的译文JS就可以显示中文界面了。(使用IDEA 双击Shift 快速找到swagger-ui.html 入口文件)
2.定制中文界面2.1 添加首页和译文
重点来了,在src/main/resources目录下创建META-INF\resources目录,然后创建一个名称为"swagger-ui.html" 的HTML文件。如图:
注意文件名不要起错,接下来将下面这段内容原封不动的拷贝进去。
Swagger UI
OK 大功告成 我们访问 :8080/swagger-ui.html 看看显示效果:
注:关于国际化,直接在Github下载好Swagger-UI的源码,将swagger-ui.html替换成上文,直接发布到Maven私服仓库,使用效果更佳。
2.2 更详细的译文翻译(非必需)
如果想进一步调整译文,可以在META-INF\resources\webjars\springfox-swagger-ui\lang 目录下添加zh-cn.js文件.
然后在译文(zh-cn.js )根据个人喜好来添加翻译内容,如下
'use strict'; /* jshint quotmark: double */ window.SwaggerTranslator.learn({ "Warning: Deprecated":"警告:已过时", "Implementation Notes":"实现备注", "Response Class":"响应类", "Status":"状态", "Parameters":"参数", "Parameter":"参数", "Value":"值", "Description":"描述", "Parameter Type":"参数类型", "Data Type":"数据类型", "Response Messages":"响应消息", "HTTP Status Code":"HTTP状态码", "Reason":"原因", "Response Model":"响应模型", "Request URL":"请求URL", "Response Body":"响应体", "Response Code":"响应码", "Response Headers":"响应头", "Hide Response":"隐藏响应", "Headers":"头", "Try it out!":"试一下!", "Show/Hide":"显示/隐藏", "List Operations":"显示操作", "Expand Operations":"展开操作", "Raw":"原始", "can't parse JSON. Raw result":"无法解析JSON. 原始结果", "Example Value":"示例", "Click to set as parameter value":"点击设置参数", "Model Schema":"模型架构", "Model":"模型", "apply":"应用", "Username":"用户名", "Password":"密码", "Terms of service":"服务条款", "Created by":"创建者", "See more at":"查看更多:", "Contact the developer":"联系开发者", "api version":"api版本", "Response Content Type":"响应Content Type", "Parameter content type:":"参数类型:", "fetching resource":"正在获取资源", "fetching resource list":"正在获取资源列表", "Explore":"浏览", "Show Swagger Petstore Example Apis":"显示 Swagger Petstore 示例 Apis", "Can't read from server. It may not have the appropriate access-control-origin settings.":"无法从服务器读取。可能没有正确设置access-control-origin。", "Please specify the protocol for":"请指定协议:", "Can't read swagger JSON from":"无法读取swagger JSON于", "Finished Loading Resource Information. Rendering Swagger UI":"已加载资源信息。正在渲染Swagger UI", "Unable to read api":"无法读取api", "from path":"从路径", "server returned":"服务器返回" });
===========接下来,正式进入Swagger2的使用教程===========
SpringBoot + Swagger2 使用教程1. 引入依赖
org.springframework.boot spring-boot-starter-web io.springfox springfox-swagger2 2.7.0 io.springfox springfox-swagger-ui 2.7.0 org.springframework.boot spring-boot-devtools org.springframework.boot spring-boot-starter-test test
2. 添加配置
@Configuration //标记配置类 @EnableSwagger2 //开启在线接口文档 public class Swagger2Config { /** * 添加摘要信息(Docket) */ @Bean public Docket controllerApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(new ApiInfoBuilder() .title("标题:某公司_用户信息管理系统_接口文档") .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") .contact(new Contact("一只袜子", null, null)) .version("版本号:1.0") .build()) .select() .apis(RequestHandlerSelectors.basePackage("com.hehe.controller")) .paths(PathSelectors.any()) .build(); } }
3. 编写接口文档
Swagger2 基本使用:
Swagger2 使用注解来编写文档:
Swagger2编写接口文档相当简单,只需要在控制层(Controller)添加注解来描述接口信息即可。例如:
package com.hehe.controller; @Api("用户信息管理") @RestController @RequestMapping("/user/*") public class UserController { private final static ListuserList = new ArrayList<>(); { userList.add(new User("1", "admin", "123456")); userList.add(new User("2", "jacks", "111111")); } @ApiOperation("获取列表") @GetMapping("list") public List userList() { return userList; } @ApiOperation("新增用户") @PostMapping("save") public boolean save(User user) { return userList.add(user); } @ApiOperation("更新用户") @ApiImplicitParam(name = "user", value = "单个用户信息", dataType = "User") @PutMapping("update") public boolean update(User user) { return userList.remove(user) && userList.add(user); } @ApiOperation("批量删除") @ApiImplicitParam(name = "users", value = "N个用户信息", dataType = "List ") @DeleteMapping("delete") public boolean delete(@RequestBody List users) { return userList.removeAll(users); } } package com.hehe.entity; public class User { private String userId; private String username; private String password; public User() { } public User(String userId, String username, String password) { this.userId = userId; this.username = username; this.password = password; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } User user = (User) o; return userId != null ? userId.equals(user.userId) : user.userId == null; } @Override public int hashCode() { int result = userId != null ? userId.hashCode() : 0; result = 31 * result + (username != null ? username.hashCode() : 0); result = 31 * result + (password != null ? password.hashCode() : 0); return result; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
4. 查阅接口文档
编写文档完成之后,启动当前项目,在浏览器打开:
[ :8080/swagger-ui.html ] , 看到效果如下:
来看看save 方法的具体描述,可以看到Swagger 2.7.0 版本对参数列表进行了改版,直接输入参数,更方便进行测试操作:
5. 测试接口
Swagger2的强大之处不仅在于快速生成整洁优雅的RestAPI文档,同时支持接口方法的测试操作(类似于客户端PostMan)。
以查询用户列表为例,无参数输入,直接点击“试一下”按钮:
然后可以看到以JSON格式返回的用户列表信息,很方便有木有:
好了,关于Swagger2在项目中的使用教程就到这里。