TCP通讯流程
First, TCP is unwieldy for protocols where clients want to send single, small requests to a server, and then are done and will not talk to it further. It takes three packets for two hosts to set up a TCP
connection—the famous sequence of SYN, SYN-ACK, and ACK (which mean “I want to talk, here is the packet sequence number I will be starting with”; “okay, here’s mine”; “okay!”)—and then another three or four to shut the connection back down (either a quick FIN, FIN-ACK, ACK, or a slightly longer pair of separate FIN and ACK packets). That is six packets justto send a single request! Protocol designers quickly turn to UDP in such cases.
调用Send所产生的结果
如果网卡处在空闲状态或者缓冲区足够,那么就会立刻返回,返回值是要发送的数据长度。
如果网卡处于忙碌状态,并且缓冲区也已经满了,那么程序将会挂起,直到需要发送的数据被网卡或者缓冲区所接收。
如果网卡处于忙碌状态,并且缓冲区不足以存放所有需要发送的数据,将只会缓冲从头开始的可以容纳的数据量,剩下的数据无法再缓冲了,返回已经被缓冲的数据长度。
调用Recv所产生的结果
如果没有数据到达,则recv操作则会让程序挂起,直到有数据到达。
如果缓冲区里面有大量的数据,那么recv会返回程序所请求的数据量。
如果缓冲区里面有数据,但是小于所请求的数据量,那么recv会返回现有的数据。
如果对面连接已经关闭,那么recv直接返回0.
绑定127.0.0.1,或者局域网IP地址,或者0.0.0.0的区别
127.0.0.1时,只会接收connecet的IP地址为127.0.0.1的连接
局域网IP地址,只会接收connecet的IP地址为对应的局域网IP地址的连接
0.0.0.0时,相当于绑定本地的所有网卡的IP地址,包括127.0.0.1
TCP地址和端口已经被使用,重复bind
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
这里面的SO_REUSEADDR端口可重用,可以重复bind()相同的IP地址和端口号,但是真正能够正常运行的只有一个。
原理:暂时不清楚,应该是为了防止该端口上次使用时,没有关闭干净,对面没有发送回复的数据包,从而使端口处于CLOSE-WAIT或者 TIME-WAIT状态,使用这个标记用于表示等状态正常时立刻使用该端口。FIN ACK是什么东东?
缓冲区满,死锁
收到的数据如果没有被即时取出,操作系统就会将其放到缓冲区里面,缓冲区的大小不是无限的。假如发送端一直发送数据,而接收端不从接收数据,就有可能造成缓冲区满, 这个时候接收端就会告知发送端,缓冲区已经满了,请不要再发了,在阻塞模式下调用sendall()就会导致程序被阻塞,挂起。
关闭连接,半开连接
当接收数据读取到了end-of-file时,read返回0/False,这个时候就表示,远端已经关闭连接。
Close()用来完整的关闭一个连接
Shutdown()通过设置缓冲区的可读还是可写标记来半关闭一个连接,标记位有SHUT_WR, SHUT_RD, SHUT_RDWR,其中SHUT_RDWR可以让所有引用该套接字的地方都不能再收发数据,而close()只是关闭当前模块的,而不会去管理其他共享模块的引用。
半开连接的功能,可以在只需要发送数据或者只需要处理接收的数据的地方使用。当套接字创建时,立刻调用shutdown()。
TCP流当做文件流使用
在python中,文件对象可以read()和write(), 字可以send()和recv(),但是没有任何对象可以同时执行这两对操作。
Socket的makefile()操作,可以让使其可以像文件流一样进行read()和write()操作。
>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> hasattr(s, 'read')
False
>>> f = s.makefile()
>>> hasattr(f, 'read')
True
其他
1.代码片段,学习python
hasattr, 查看对象是否有某个属性
>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> hasattr(s, 'read')
False
>>> f = s.makefile()
>>> hasattr(f, 'read')
True
String.upper全部转为大写,string.title每个单词的第一个字母大写,join()连接所有的子字符串为一个
>>> message = 'the tragedy of macbeth'
>>> blocks = message[:16], message[16:]
>>> ''.join( b.upper() for b in blocks ) # works fine
'THE TRAGEDY OF MACBETH'
>>> ''.join( b.title() for b in blocks ) # whoops
'The Tragedy Of MAcbeth'
List的使用,argv的读取
import sys
print(sys.argv[1:])
print(sys.argv[1])
C:\Users\winter\Desktop>python test.py server 1
['server', '1'] #得到的是一个List
Server #得到的是单个元素
if sys.argv[1:] == ['server']: #这个就表明只有一个参数,并且参数的值是’server’
2.接收的bytes转为string,发送的string转为bytes
message = sc.recv(1024)
if not message:
break
message = message.decode('utf-8')
sc.sendall(message.encode('utf-8'))
3.TCP使用的接口总结
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建一个TCP套接字
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #将端口设置为可以重复使用
s.bind((HOST, PORT)) #绑定IP地址和端口, TCP还是UDP上面已经确定
s.listen(1) #开始进行监听, 参数的意思表明最大的等待连接数,不是指现有的连接数量
sc, sockname = s.accept() #接收一个连接,sc是套接字, sockname 是IP地址和端口号
sc.sendall('Farewell, client') #发送数据,直到所有的数据都被发送
sc.close() #关闭套接字,该对象将不能发送和数据
s.recv(42) #接收数据,数据大小小于或者等于参数的值
s.connect((HOST, PORT)) #连接指定的IP地址和端口号, TCP或者UCP由s创建的时候定义
sc.getsockname() #获取套接字本地所使用的IP地址和端口号
sc.getpeername() #获取对端的IP地址和端口号
4.该注意的地方
处理阻塞套接字时,需要想办法解决缓冲区满的问题,多进程或者线程读写缓冲区。
代码
简单的Tcp服务器和客户端
#!/usr/bin/env python
# Foundations of Python Network Programming - Chapter 3 - tcp_sixteen.py
# Simple TCP client and server that send and receive 16 octets
import socket,sys
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = sys.argv.pop() if len(sys.argv) == 3 else '127.0.0.1'
PORT = 1060
def recv_all(sock,length):
data = ''
while len(data) < length:
more = sock.recv(length - len(data))
if not more:
raise EOFError('socket closed %d bytes into a %d-byte message'%(len(data),length))
data += more.decode('utf-8')
return data
if sys.argv[1:] == ['server']:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST,PORT))
s.listen(1)
while True:
print('Listening at', s.getsockname())
sc,sockname = s.accept()
print('We have accepted a connection from', sockname)
print('Socket connects', sc.getsockname(), 'and', sc.getpeername())
message = recv_all(sc,16)
print('The incoming sixteen-octet message says', repr(message))
sc.sendall(b'Farewell, client')
sc.close()
print('Reply sent, socket closed')
elif sys.argv[1:] == ['client']:
s.connect((HOST,PORT))
print('Client has been assigned socket name', s.getsockname())
s.sendall(b'Hi thers, server')
reply = recv_all(s,16)
print('The server said', repr(reply))
s.close()
else:
print('usage:tcp_local.py server|client [host]', file=sys.stderr)
简单的Tcp服务器和客户端,不可以重复bind
#!/usr/bin/env python
# Foundations of Python Network Programming - Chapter 3 - tcp_sixteen.py
# Simple TCP client and server that send and receive 16 octets
import socket,sys
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = sys.argv.pop() if len(sys.argv) == 3 else '127.0.0.1'
PORT = 1060
def recv_all(sock,length):
data = ''
while len(data) < length:
more = sock.recv(length - len(data))
if not more:
raise EOFError('socket closed %d bytes into a %d-byte message'%(len(data),length))
data += more.decode('utf-8')
return data
if sys.argv[1:] == ['server']:
#s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST,PORT))
s.listen(1)
while True:
print('Listening at', s.getsockname())
sc,sockname = s.accept()
print('We have accepted a connection from', sockname)
print('Socket connects', sc.getsockname(), 'and', sc.getpeername())
message = recv_all(sc,16)
print('The incoming sixteen-octet message says', repr(message))
sc.sendall(b'Farewell, client')
sc.close()
print('Reply sent, socket closed')
elif sys.argv[1:] == ['client']:
s.connect((HOST,PORT))
print('Client has been assigned socket name', s.getsockname())
s.sendall(b'Hi thers, server')
reply = recv_all(s,16)
print('The server said', repr(reply))
s.close()
else:
print('usage:tcp_local.py server|client [host]', file=sys.stderr)
缓冲区满造成死锁的例子
python tcp_deadlock.py server
python tcp_deadlock.py client 1073741824 #发送超大量的数据
#!/usr/bin/env python
# Foundations of Python Network Programming - Chapter 3 - tcp_deadlock.py
# TCP client and server that leave too much data waiting
import socket,sys
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST= '127.0.0.1'
PORT = 1060
if sys.argv[1:] == ['server']:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
s.bind((HOST,PORT))
s.listen(1)
while True:
print('Listen at', s.getsockname())
sc,sockname = s.accept()
print('Processing up to 1024 bytes at a time from', sockname)
n= 0
while True:
message = sc.recv(1024)
if not message:
break
n += len(message)
message = message.decode('utf-8')
message = message.upper()
sc.sendall(message.encode('utf-8')) #send it back uppercase
print('\r%d bytes processed so far' % (n))
sys.stdout.flush()
print()
sc.close()
print('Completed processing')
elif len(sys.argv) == 3 and sys.argv[1] == 'client' and sys.argv[2].isdigit():
bytes = (int(sys.argv[2]) + 15) // 16*16 #round up to//16
message = 'capitalize this!' #16-byte message to repate over and over
message = message.encode('utf-8')
print('Sending',bytes,'bytes of data, in chunk of 16 bytes')
s.connect((HOST,PORT))
sent = 0
while sent < bytes:
s.sendall(message)
sent += len(message)
print('\r%d bytes sent' % (sent))
sys.stdout.flush()
print()
s.shutdown(socket.SHUT_WR)
print('Receiving all the data the server sends back')
received = 0
while True:
data = s.recv(42)
if not received:
print('The first data received says', repr(data))
received += len(data)
if not data:
break
print('\r%d bytes received' % (received))
s.close()
else:
print('usage:tcp_deadlock.py server | clinet <bytes>', file = sys.stderr)