目录
1.2.2 方法 - getInputStream / getOutputStream
2.3.1 服务器的 Socket 和客户端的 Socket
2.3.3 使用 PrintWriter 的 println 发送信息
1. TCP socket api
在上篇博客中, 我们提到, TCP 协议与 UDP 协议的差别很大, 于是操作系统提供了两套 socket api 分别供这两个协议使用.
TCP 协议的特点是:
- 有连接
- 面向字节流
- 全双工
- 可靠传输
这篇博客, 就来聊一聊和 TCP 相关的 socket api.
1.1 ServerSocket
ServerSocket 是专门供 TCP 服务器端 使用的 api.
1.1.1 构造方法
- 和 UDP 服务器端一样, 创建 TCP 服务器端时, 也需要指定一个固定的端口号.
1.1.2 方法 - accept / close
我们知道, TCP 是 "有连接" 的, 而 ServerSocket 中的 accept 方法就是用来和客户端建立连接:
- 当有客户端发起连接时, accept 用来接收客户端的连接
这里的 accept 可以理解为 "接通电话":
主动打电话的那一方是客户端, 而服务器想要对客户端做出响应, 就必须先 "接通电话", accept 的作用就可以理解为是 "接通电话".
close 方法则是关闭文件资源.
1.2 Socket
Socket 是用来建立 TCP 客户端 的 api.
但是, 在服务器端中, 也会使用 Socket (服务器的 accept 方法返回的就是 Socket 对象, 其中保存了客户端的信息, 并可以对客户端进行相关的交互操作).
也就是说, Socket 在 服务器端和客户端都会使用.
- 服务器端和客户端, 就是通过 Socket 来保存对端的信息, 并进行通信的.
1.2.1 构造方法
TCP 具有 "有连接" 的特性, 所以会在底层保存对端的信息(IP地址和端口), 不需要我们手动创建变量来记录服务器地址, 直接在构造方法中传入即可(TCP 内部自己记住了).
1.2.2 方法 - getInputStream / getOutputStream
TCP 是面向字节流.
所以在 TCP 中, 并不是通过 receive 和 send 来进行通信的, 而是通过 getxxx() 来拿到 "字节流对象" , 通过流对象的方式来进行通信(读和写数据)的.
2. TCP 回显 客户端 - 服务器
2.1 TCP 回显服务器
2.1.1 给服务器指定固定的端口号
- 创建 TCP 服务器, 使用 ServerSocket
- 和 UDP 服务器一样, 对于服务器, 需要指定固定的端口号
2.1.2 建立和客户端的连接
TCP 服务器中, 和客户端进行通信的大前提是, 先和客户端建立起连接:
- 当客户端发起连接时, 服务器端要进行接收, 否则后续无法进行通信
使用 accept 方法来接收客户端发起的连接.
accept 返回的是一个 Socket 对象, 后续服务器就通过这个 Socket 对象来和客户端进行通信.(这个 Socket 对象中包含了对端的信息).
这里需要注意 TCP 服务器中, ServerSocket 对象 和 Socket 对象 的区别:
- ServerSocket 是用来创建 TCP 服务器的 (外场的作用)
- Socket 的对象是 accept 的返回值, 这个对象在底层和对端的 TCP 建立起了连接, 可以和对端进行通信 (所谓连接, 就是保存了对端的信息) (内场的作用)
2.1.3 对字节流对象进行包装
上文说到, TCP 上是通过字节流对象来进行通信的, 要想读写请求或者响应, 那就需要先拿到字节流对象:
但是, 为了方便我们进行请求响应的读写, 我们可以对这两个字节流对象进行 "包装" :
- 使用 Scanner 对 InputStream 进行包装: 直接读到字符串(服务器读取请求时 / 客户端读取响应时)
- 使用 PrintWriter 对 OutputStream 进行包装: 直接写回字符串(服务器返回响应时 / 客户端发送请求时)
经过 Scanner 和 PrintWriter 的包装后, 就不需要以字节为单位进行通信了, 可以直接以字符串的形式来进行通信.
我们之前使用 Scanner 时, 传入的是 System.in , 其实传入的 in 就是一个 InputStream 对象: