【卷二】网络二—TCP服务器与客户端
经过上回简单地介绍,大家对服务器多少应该清楚一些了吧!还记得TCP: (Transmission Control Protocol)
传输控制协议? 还记得IP: (Internet Protocol)因特网协议, IPv4, IPv6? 如有不清楚,还请回去补充下概念......
传送门: http://www.cnblogs.com/Ruby517/p/5808224.html
参考: 《Python核心编程(3rd)》 P51 ~ P57
-----------------------------------------------------------------------------------------------------------------
名称 描述
服务器套接字方法
s.bind() 将地址(主机名,端口号对)绑定到套接字上
s.listen() 设置并启动TCP监听器
s.accept() 被动接受TCP客户端连接,一直等待直到连接到达(阻塞)
客户端套接字方法
s.connect() 主动发起TCP服务器连接
普通的套接字方法
s.recv() 接收TCP消息
s.send() 发送TCP消息
创建TCP服务器
TCP是控制面向连接的套接字的协议,通常和IP一起用,搭建这样一个类型的服务器有何妙用呢?其实我也不知道!
下面是创建TCP服务器的一般伪代码(就是掺杂了很多中文的代码), 由于只是个框架,所以直接按执行只会报错 !
1 # coding: utf-8 2 3 from socket import * # 导入socket模块的所有特性 4 5 ss = socket() # ss: (server socket)服务器表接字,创接服务器表接字 6 ss.bind() # 套接字与本地地址绑定 7 ss.listen() # 开启TCP监听器 8 9 while True: # 服务器无限循环 10 cs = ss.accept() # cs: (client-side)客户端, 被动接受客户端连接 11 while True: # 通信循环 12 cs.recv()/cs.send() # 对话 (接收/发送) 13 cs.close() # 关闭客户端套接字 14 ss.close() # 关闭服务器套接字
所有套接字都是通过使用socket.socket()函数来创建的。因为服务器需要占用一个端口并等待
客户端的请求, 所以它们必须绑定一个本地地址。因为TCP是一种面向连接的通信系统, 所以在TCP
服务器开始之前,必须安装一些基础设施。特别地,TCP服务器必须监听传入的连接。
默认情况下,accept()是闭塞的,意味着执行将会停止,直到一个连接到达。这也就是说,服务
器只能无可奈何地等待它的顾客,客户端联系它。
一旦服务器接受了一个连接,就会返回(利用accept())一个独立的客户端套接字,用来与即将
到来的消息进行交换。使用新的套接字类似于接线员将客户的电话切换给客服代表。当一个客户电话最
后接进来时,主要的总机接线员会接到这个电话,并使用另一条线路将这个电话转接给合适的人 (如客
服) 来处理客户的需求。
这将能够空出主线(旧的,原始的套接字),以便接线员可以继续等待新的电话(客户请求),
而此时客户及其连接的客服代表能够进行他们自己的谈话。同样地,当一个传入的请求到达时, 服务
器会创建一个新的通信端口来直接来客户端进行通信,再次空出主要端口, 以使其能够接受新的客户
端连接!
TCP时间戳服务器
【时间戳指的是ctime()输出的内容】
注意,格式化字符串%s无法传递元组,所以要先用str()函数将元组转化为字符串
1 # coding: utf-8 2 3 from socket import * 4 from time import ctime 5 6 # Host: 主机, Host变量是空白的,表示它可以使用任何可用的地址 7 # Port: 端口, 端口号范围为0~65535 (尽管小于1024的端口号都预留给了系统) 8 # Buffer Size: 缓冲区大小, 这里设置为1024B, 即1KB 9 # listen()方法/函数的参数是在连接被转接或拒绝之前,请求的最多次数 10 11 Host = "" 12 Port = 21568 13 Bufsiz = 1024 14 Addr = (Host, Port) 15 16 tcpSerSock = socket(AF_INET, SOCK_STREAM) 17 tcpSerSock.bind(Addr) 18 tcpSerSock.listen(5) 19 20 while True: 21 print "Waiting for connection..." 22 tcpCliSock, addr = tcpSerSock.accept() 23 # %s 可以代替str(字符串) int(整数)、float(浮点数)、list(列表)、dict(字典), 就是不能代替tuple(元组) 24 print "...Connected from: %s" % str(addr) 25 26 while True: 27 # 从Buffer: 缓冲区, 取出客户端发来的数据data进行加工 28 data = tcpCliSock.recv(Bufsiz) 29 # 如果data是空白值,那么它的布尔值即为假,not data即为真,那么执行break语句,跳出循环 30 if not data: 31 break 32 # ctime()表示当前的系统时间,即时间戳, 再把处理好的数据发回缓冲区,供客户端利用 33 tcpCliSock.send("[%s] %s" % (ctime(), data)) 34 # 关闭客户端套接字 35 tcpCliSock.close() 36 37 tcpSerSock.close()
一旦进入到服务器的无限循环中,我们就只有被动等待客户端地连接。当一个连接请求出现时,
我们进入到对话循环中(第二个while语句范围), 在该循环中等待客户端发送的信息。 如果信息是空
白的, 这意味着客户端已经退出,所以此时将跳出对话循环,关闭当前客户端连接,然后等待另一个
客户端连接 (只退出第二个while对话循环,第一个服务器循环还在运行)!
错误1, 如果出现这种报错, 说明port: 端口号已经被其他程序占用, 也许你已经打开过的服
务器没有关掉就又新打开一个了! 此时关掉开着的服务器或者换一个端口号就行了。 (有效的端口号
范围是0~65535,小于1024的端口号预留给了系统!)
查看端口使用: >-|->> http://jingyan.baidu.com/article/3c48dd34491d47e10be358b8.html
错误2, bad descriptor, 一定是因为你把客户端的结束语句放在对话循环里面了(第二个while语句范围)
单独运行服务器的话,由于此时尚没有客户端的请求,服务器输出的只有:
----------------------------------------------------------------------------------------------------------------
TCP时间戳客户端
【注意】 由于客户端的connect()函数是主动发起连接的,所以若是先启动客户端而不是服务器的话,
没有服务器的响应,那么将会出现如下错误:
1 # coding: utf-8 2 3 from socket import * 4 5 # Host:主机, Port:端口, 指服务器的主机名和端口号, 因为在同一个计算机进行测试(本例中) 6 # 所以Host包含本地主机名 (如果你的服务器运行在另一台主机上,那么需要进行相应修改)。端 7 # 口号Port 应该与服务器设置的完全相同(否则将无法进行通信)! 8 9 Host = "localhost" 10 Port = 21568 11 Bufsiz = 1024 12 Addr = (Host, Port) 13 14 tcpCliSock = socket(AF_INET, SOCK_STREAM) 15 tcpCliSock.connect(Addr) 16 17 while True: 18 data = raw_input("> ") 19 # 如果data是空白值,那么BOOL(data)布尔值将会是False,not data即是True,执行break语 20 # 句,跳出while循环 21 if not data: 22 break 23 # 把数据data发送到Buffer(缓冲区), 供服务器提取, 进行加工 24 tcpCliSock.send(data) 25 # 把服务器处理好的数据从Buffer(缓冲区), 取出来, 并打印出来! 26 data = tcpCliSock.recv(Bufsiz) 27 28 print data
客户端的主机名和端口号必须与服务器的一致,否则,将无法进行通信! 因为此处是在同一台计算机上进行测
试,所以在Host包含本地主机名(localhost), 如果你的服务器运行在另一台主机上,那么需要进行相应地修改。
如果出现这种错误,说明服务器和客户端没有连接上,可能是还没启动服务器就先启动客户端了,也可能是两者的
端口或地址对不上 (比如忘记写本地主机名称'localhost' !!!)
客户端的输出,可以看出,它先把自行输入的数据发送到缓冲区,然后服务器将数据从缓冲区取出来加工 (还是
通过服务器代码里的tcpCliSock.recv()方法),最后输出的是经过服务器处理过的数据,即我们输入的数据加
上时间戳!
服务器的输出,主要是诊断性的,当客户端发起连接时, 将会收到"...Connected from..."的消息。 当继续接收
"服务"时,服务器会等待新客户端的连接!
【127.0.0.1是回送地址,指本地机,一向用来测试使用!】
-------------------------------------------------------------------------------------------------------------
客户端改进
上面的代码有个问题,就是当服务器与客户端还是通话的时候,我们若是按 "Ctrl + C" 引发服务器异常从而关闭,
那么此时客户端的显示是这样的,输出空白,个人猜测是由于服务器在执行tcpSerSock.recv()时引发异常,但两
者好歹对话过,所以系统返回了空白符给客户端,
可是第二次就出错了,因为此时服务器已关,也就是没有TCP连接了,所以也就没有了交互,系统怎么返回信息给
你所以这回连空白符都没有(纯属个人猜测)
所以我们要在客户端代码的 tcpCliSock.recv() 后面这么改...
1 data = tcpCliSock.recv(BUFSIZ) 2 3 if not data: 4 break 5 6 print data
这样就可以在引发服务器异常,然后客户端收到空白符时直接退出客户端