1、tcp相关介绍

TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。

TCP通信需要经过创建连接数据传送终止连接三个步骤。

TCP通信模型中,在通信开始之前, 一定要先建立相关的链接, 才能发送数据, 类似于生活中, "打电话""

TCP特点

  1. 面向连接

通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输。

双方间的数据传输都可以通过这一个连接进行。

完成数据交换后,双方必须断开此连接,以释放系统资源。

这种连接是一对一的,因此TCP不适用于广播的应用程序,基于广播的应用程序请使用UDP协议。

  1. 可靠传输

    1. TCP采用发送应答机制

    TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功

    1. 超时重传

    发送端发出一个报文段之后就启动定时器,如果在定时时间内没有收到应答就重新发送这个报文段。

    TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。

    1. 错误校验

    TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。

    1. 流量控制和阻塞管理

    流量控制用来避免主机发送得过快而使接收方来不及完全收下。

TCP与UDP的不同点

  • 面向连接(确认有创建三方交握,连接已创建才作传输。)
  • 有序数据传输
  • 重发丢失的数据包
  • 舍弃重复的数据包
  • 无差错的数据传输
  • 阻塞/流量控制

2、TCP服务器

实际生活中:

  1. 买个手机
  2. 插上手机卡
  3. 设计手机为正常接听状态(即能够响铃)
  4. 静静的等着别人拨打

在程序中, 如果想要完成一个TCP服务器的功能, 需要的流程如下:

  1. socket创建一个套接字
  2. bind绑定ip和port
  3. listen使套接字变为可以被动链接
  4. accept等待客户端的链接
  5. recv/send接收发送数据

一个很简单的TCP服务器如下:

from socket import *

# 1.创建一个socket套接字(买手机卡)
tcpSerSocket = socket(AF_INET, SOCK_STREAM) # 2.bind绑定本机ip和port(插上手机卡)
address = (''. 7777)
tcpSerSocket.bind(address)
# 3.listen使套接字变为可以被动链接(设置为手机正常接听状态)
tcpSerSocket.listen(5) # 4.accept等待客户端的链接(静静的等着别人拨打)
# newSocket用来为这个客户端服务
# tcpSerSocket就可以省下来专门等待其他新客户端的链接
newSocket, clientAddr = tcpSerSocket.accept() # 5.recv接收数据,最大接收1024个字节
recvData = newSocket.recv(1024).decode('gbk')
print('接收到的数据为:',recvData) # 6.send发送一些数据到客户端
newSocket.send("服务器已经收到了!".encode('gbk')) # 关闭为这个客户端服务的套接字, 只要关闭了, 就意味着为不能再为这个客户端服务
newSocket.close()
# 关闭监听套接字, 只要这个套接字关闭了, 就意味着整个程序不能再接收任何新的客户端的连接
tcpSerSocket.close()

3、TCP客户器

TCP的客户端要比服务器端简单很多,如果说服务器端是需要自己买手机、查手机卡、设置铃声、等待别人打电话流程的话,那么客户端就只需要找一个电话亭,拿起电话拨打即可,流程要少很多

生活中的电话机,如果想让别人能更够打通咱们的电话获取相应服务的话,需要做以下几件事情:

  1. 买个手机
  2. 插上手机卡
  3. 设计手机为正常接听状态(即能够响铃)
  4. 静静的等着别人拨打
from socket import *
from time import * # 1.socket创建一个套接字
tcpClient = socket(AF_INET,SOCK_STREAM)
# 2.绑定本机端口号,也可以不绑定
tcpClient.bind(('',8888))
# 3.连接服务器
tcpClient.connect(('192.168.14.72',5678))
#4.接收消息
data1 = tcpClient.recv(1024)
print(data1.decode('gbk')) #5.发消息
while True:
data = input('>>\t').encode('gbk')
tcpClient.send(data)
if data=='886':
sleep(4)
tcpClient.close()
break

4.应用:模拟QQ聊天

客户端参考代码

from socket import *

# 创建socket
tcpClientSocket = socket(AF_INET, SOCK_STREAM) # 链接服务器
serAddr = ('192.168.1.102', 7788)
tcpClientSocket.connect(serAddr) while True: # 提示用户输入数据
sendData = input("send:") if len(sendData)>0:
tcpClientSocket.send(sendData)
else:
break # 接收对方发送过来的数据,最大接收1024个字节
recvData = tcpClientSocket.recv(1024)
Print('recv:',recvData) # 关闭套接字
tcpClientSocket.close()

服务器端参考代码

#coding=utf-8
from socket import * # 创建socket
tcpSerSocket = socket(AF_INET, SOCK_STREAM) # 绑定本地信息
address = ('', 7788)
tcpSerSocket.bind(address) # 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了
tcpSerSocket.listen(5) while True: # 如果有新的客户端来链接服务器,那么就产生一个信心的套接字专门为这个客户端服务器
# newSocket用来为这个客户端服务
# tcpSerSocket就可以省下来专门等待其他新客户端的链接
newSocket, clientAddr = tcpSerSocket.accept() while True: # 接收对方发送过来的数据,最大接收1024个字节
recvData = newSocket.recv(1024) # 如果接收的数据的长度为0,则意味着客户端关闭了链接
if len(recvData)>0:
print 'recv:',recvData
else:
break # 发送一些数据到客户端
sendData = input("send:")
newSocket.send(sendData) # 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接
newSocket.close() # 关闭监听套接字,只要这个套接字关闭了,就意味着整个程序不能再接收任何新的客户端的连接
tcpSerSocket.close()

5.应用:tcp通信-多线程服务器端

from socket import *
from threading import * class Mythread(Thread):
def __init__(self, socketClient, clientAddress):
Thread.__init__(self)
self.socketClient = socketClient
self.clientAddress = clientAddress def run(self):
data1 = '哈哈。。。可以聊天了'.encode('utf_8')
self.socketClient.send(data1) while True:
try:
# 接收客户端发的消息
data = self.socketClient.recv(1024).decode('utf-8')
except:
print('<<\t%s-%s 已经断开了' % (self.clientAddrdess[0], self.clientAddrdess[1]))
break
if data == '886':
break
print('<<\t%s-%s:%s' % (self.clientAddrdess[0], self.clientAddrdess[1], data)) def main():
tcpServer = socket(AF_INET, SOCK_STREAM)
tcpServer.bind(('', 5678))
tcpServer.listen(5) while True:
print('服务器等待客户端连接......')
socketClient, clientAddress = tcpServer.accept()
Mythread(socketClient, clientAddress).start()
print('%s-%s 连接成功......' % (clientAddrdess[0], clientAddrdess[1])) if __name__ == '__main__':
main() #tcpServer.close() 通常不用关服务器的

6、tcp三次握手

  • 当客户端调用connect时,发送syn x(一个序号)给服务端,服务端有listen和accept,服务端收到后ack x+1给客户端,同时会一并传递一个新的syn y,客户端收到后,acd y+1给服务端,此时三次握手完成。
  • 生活中的例子理解以上内容:好比AB两人打电话,A发出hello后,不清楚B是否收到信息,B收到后回复听到 了,并发送hello,A收到后确定第一次发出的信息B可以收到,但是B发送信息后,无法确定A是否收到,因此A应当回复收到

7、tcp四次挥手

  • 通过close关闭套接字来实现四次挥手,一般情况下,服务器不会主动关闭,而是让客户端关闭连接。以下内容假定以客户端关闭socket来描述,指定A为客户端,B为服务器
  • socket是全双工的,可以同时接收和发送
  • 当A调用close后,自身关闭send(socket依然存在),并告知B(第一次)不会再发送内容了,B收到后,返回消息给A(第二次)表明已收到,就会去关闭recv,此时的实现是底层的,为了解除堵塞,B会设定recv得到的内容为空字符串,此时如果有代码判定,如果recv收到内容为空,就要调用close来关闭B的send,调用完成后,发送消息给A(第三次),A收到后关闭recv,并反馈消息给B(第四次),至此四次挥手完成,但是A的socket并未关闭,因为它不确定最后一次发送消息B是否收到,假设B没有收到,那么B会在超时等待(每次通信过程,都存在超时等待,超过等待时间,就会重新发送数据)后再次发送数据,此时如果A不存在了,此次过程就会失败,所以A会等待两个MSL(数据在网络中存储的最大时间,因为要发和收,所以是两个)的时间后才会关闭,这也就是出现地址占用问题的原因,解决办法见Address already in use
  • 谁先调用close,谁就会在最后等待两分钟左右,所以会有端口占用问题,因为服务器会绑定端口,客户端不绑定端口,所以一般先让客户端close,客户端如果再次运行,又会随机分配端口
  • 与三次握手,将服务器端的确认信息及反馈信息同时发送不同,四次挥手第二次和第三次无法合并,因为是不同的情况下发生的,第三次是需要服务器调用close才会发生的

8、tcp十种状态

注意:

  • 当一端收到一个FIN,内核让read返回0来通知应用层另一端已经终止了向本端的数据传送
  • 发送FIN通常是应用层对socket进行关闭的结果

9、tcp长连接和短连接

9.1TCP短连接

模拟一种TCP短连接的情况:

  1. client 向 server 发起连接请求
  2. server 接到请求,双方建立连接
  3. client 向 server 发送消息
  4. server 回应 client
  5. 一次读写完成,此时双方任何一个都可以发起 close 操作

在第 步骤5中,一般都是 client 先发起 close 操作。当然也不排除有特殊的情况。

从上面的描述看,短连接一般只会在 client/server 间传递一次读写操作!

9.2TCP长连接

再模拟一种长连接的情况:

  1. client 向 server 发起连接
  2. server 接到请求,双方建立连接
  3. client 向 server 发送消息
  4. server 回应 client
  5. 一次读写完成,连接不关闭
  6. 后续读写操作...
  7. 长时间操作之后client发起关闭请求

tcp注意点

  • tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器
  • tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机
  • tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的
  • 当客户端需要链接服务器时,就需要使用connect进行链接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信
  • 当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
  • listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的
  • 关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信。
  • 关闭accept返回的套接字意味着这个客户端已经服务完毕
  • 当客户端的套接字调用close后,服务器端会recv解堵塞,并且返回的长度为0(是空字符串),因此服务器可以通过返回数据的长度(或者判断是否是空字符串)来区别客户端是否已经下线

最新文章

  1. 下载php扩展笔记
  2. QuickRun-快速运行助手
  3. Android学习系列(38)--Android源码下载和编译
  4. linux vmstat 系统结果说明
  5. Linux 命令ln
  6. Phar文件
  7. bzoj1797
  8. Cocos2d-X 动作展示《一》
  9. 开源的.Net ORM微型框架SuperHelper
  10. dfs.replication 参数 动态修改
  11. 关于python下安装PIL库遇到的问题及解决办法
  12. 201521123008《Java程序设计》第十二周学习总结
  13. MTK机器原始OTA更新方法
  14. ros_indigo使用keyboard键盘控制虚拟或实际机器人
  15. jQuery插件之-----弹性运动
  16. [Luogu P1495]曹冲养猪
  17. SAI窗口无法移动
  18. JSONArray数据转换成java List
  19. Using Android Phone to recover SD card formatted with DD command under linux
  20. pyqt 不规则形状窗口显示

热门文章

  1. HDU4882ZCC Loves Codefires(贪心)
  2. velocity.js 中文文档 (教程)
  3. Java命名规则详细总结
  4. Android+Jquery Mobile学习系列(3)-创建Android项目
  5. poj 2763(在线LCA+树状数组)
  6. 新建项目git clone
  7. aspectC++常用命令
  8. Hardwood Species(map)
  9. 手机网站下拉加载数据js(简单版)
  10. ASP之ViewState和IsPostBack