点击上方“刘望舒”,选择“星标”
8点43分打卡 就是真爱
本文转载自公众号 玉刚说,由
玉刚说写作平台
[1]提供写作赞助原作者:水晶虾饺
[2]版权声明:本文版权归微信公众号玉刚说
所有,未经许可,不得以任何形式转载
本篇我们先简单了解一下 TCP/IP,然后通过实现一个 echo 服务器来学习 Java 的 Socket API。最后我们聊聊偏高级一点点的 socket 长连接和协议设计。
TCP/IP 协议简介
IP
首先我们看 IP(Internet Protocol)协议。IP 协议提供了主机和主机间的通信。
为了完成不同主机的通信,我们需要某种方式来唯一标识一台主机,这个标识,就是著名的IP地址。通过IP地址,IP 协议就能够帮我们把一个数据包发送给对方。
TCP
前面我们说过,IP 协议提供了主机和主机间的通信。TCP 协议在 IP 协议提供的主机间通信功能的基础上,完成这两个主机上进程对进程的通信。
有了 IP,不同主机就能够交换数据。但是,计算机收到数据后,并不知道这个数据属于哪个进程(简单讲,进程就是一个正在运行的应用程序)。TCP 的作用就在于,让我们能够知道这个数据属于哪个进程,从而完成进程间的通信。
为了标识数据属于哪个进程,我们给需要进行 TCP 通信的进程分配一个唯一的数字来标识它。这个数字,就是我们常说的端口号。
TCP 的全称是 Transmission Control Protocol,大家对它说得最多的,大概就是面向连接的特性了。之所以说它是有连接的,是说在进行通信前,通信双方需要先经过一个三次握手的过程。三次握手完成后,连接便建立了。这时候我们才可以开始发送/接收数据。(与之相对的是 UDP,不需要经过握手,就可以直接发送数据)。
下面我们简单了解一下三次握手的过程。
首先,客户向服务端发送一个
SYN
,假设此时 sequence number 为x
。这个x
是由操作系统根据一定的规则生成的,不妨认为它是一个随机数。服务端收到
SYN
后,会向客户端再发送一个SYN
,此时服务器的seq number = y
。与此同时,会ACK x+1
,告诉客户端“已经收到了SYN
,可以发送数据了”。客户端收到服务器的
SYN
后,回复一个ACK y+1
,这个ACK
则是告诉服务器,SYN
已经收到,服务器可以发送数据了。
经过这 3 步,TCP 连接就建立了。这里需要注意的有三点:
连接是由客户端主动发起的
在第 3 步客户端向服务器回复
ACK
的时候,TCP 协议是允许我们携带数据的。之所以做不到,是 API 的限制导致的。TCP 协议还允许 “四次握手” 的发生,同样的,由于 API 的限制,这个极端的情况并不会发生。
TCP/IP 相关的理论知识我们就先了解到这里。关于 TCP,还有诸如可靠性、流量控制、拥塞控制等非常有趣的特性,强烈推荐读者看一看 Richard 的名著《TCP/IP 详解 - 卷1》(注意,是第1版,不是第2版)。
下面我们看一些偏实战的东西。
Socket 基本用法
Socket 是 TCP 层的封装,通过 socket,我们就能进行 TCP 通信。
在 Java 的 SDK 中,socket 的共有两个接口:用于监听客户连接的 ServerSocket
和用于通信的 Socket
。使用 socket 的步骤如下:
创建
ServerSocket
并监听客户连接使用
Socket
连接服务端通过
Socket
获取输入输出流进行通信
下面,我们通过实现一个简单的 echo 服务来学习 socket 的使用。所谓的 echo 服务,就是客户端向服务端写入任意数据,服务器都将数据原封不动地写回给客户端。
1. 创建 ServerSocket 并监听客户连接
public class EchoServer {
private final ServerSocket mServerSocket;
public EchoServer(int port) throws IOException {
// 1. 创建一个 ServerSocket 并监听端口 port
mServerSocket = new ServerSocket(port);
}
public void run() throws IOException {
// 2. 开始接受客户连接
Socket client = mServerSocket.accept();
handleClient(client);
}
private void handleClient(Socket socket) {
// 3. 使用 socket 进行通信 ...
}
public static void main(String[] argv) {
try {
EchoServer server = new EchoServer(9877);
server.run();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 使用 Socket 连接服务端