四:客户端生命周期
网络连接有两个重要的组成部分:
1)服务器负责侦听及处理接入的连接;
2)客户端负责向服务器发起连接;(知道特定服务器的位置并创建指向外部服务器的连接)
客户端的生命周期比服务器短一些:
1)创建;
2)绑定;
3)连接;
4)关闭;
第一阶段创建与服务端一致;
4.1 客户端绑定
很少会有服务端不调用bind(绑定特定的地址和端口),也很少有客户端调用bind;
如果客户端套接字(或者服务端套接字)不调用bind,那么他会从临时端口范围内获得一个随机端口号;
为什么会有不调用bind的情况?
客户端之所以不调用bind,是因为他们无需通过某个已知端口访问(也没人需要知道他们的端口号);
服务端绑定到特定端口,是因为客户端需要通过特定的端口访问到服务器;
所以,不要给客户端绑定端口号;
4.2 客户端连接
与服务器主要区别就在于connect调用:该调用发起到远程套接字的连接;
# ./code/snippets/connect.rb
require 'socket'
socket = Socket.new(: INET , : STREAM)
# 发起到google.com端口是80的连接
remote_addr = Socket.pack_sockaddr_in(80 , 'google.com')
socket.connect(remote_addr)
该段代码从本地的临时端口向在google.com的端口80上进行侦听的套接字发起TCP连接(并没有调用bind);
连接故障:
连接了一个没有准备好的服务器;
连接了一个不存在的服务器;
由于TCP所具有的容错性,他会尽最大可能等待远程主机的回应;
如果将上面代码中的端口改为70,那么会很久才得到返回(默认的一段超时时间);
如果出现超时,最终会产生一个Error::ETIMEOUT异常;
当客户端连接到一个已经bind和listen,但没有accept的服务器时,也会出现超时;只有远程服务器接受了连接,connect调用才会成功返回;
4.3 Ruby包装器
相对于较低级的调用,Ruby同样进行了包装,我们还是只是举一个示例(感兴趣的同学请自行学习Ruby):
比如之前的代码可以这样写:
# ./code/snippets/client_easy_way.rb
require 'socket'
socket = TCPSocket.new('google.com' , 80)
还可以使用Socket.tcp类似的构建方法:
# ./code/snippets/client_easy_way.rb
require 'socket'
Socket.tcp('google.com' , 80) do | connection |
connection.write "GET / HTTP/1.1\r\n"
connection.close
end
#如果省略代码块参数,则行为方式同TCPSocket.new()一样
client = Socket.tcp('google.com' , 80)
总结:
·什么是Socket;
·Socket创建;
·服务端生Socket;
·客户端Socket;
·建立连接;
这些都是相对基本的概念,接下来我们看看已经建立好的连接如何交换数据,包括数据交换过程中的常用概念等。