Ch03 TCP(Blocked)

原创
2013/12/24 20:40
阅读数 254

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所产生的结果

  1. 如果网卡处在空闲状态或者缓冲区足够,那么就会立刻返回,返回值是要发送的数据长度。

  2. 如果网卡处于忙碌状态,并且缓冲区也已经满了,那么程序将会挂起,直到需要发送的数据被网卡或者缓冲区所接收。

  3. 如果网卡处于忙碌状态,并且缓冲区不足以存放所有需要发送的数据,将只会缓冲从头开始的可以容纳的数据量,剩下的数据无法再缓冲了,返回已经被缓冲的数据长度。


调用Recv所产生的结果

  1. 如果没有数据到达,则recv操作则会让程序挂起,直到有数据到达。

  2. 如果缓冲区里面有大量的数据,那么recv会返回程序所请求的数据量。

  3. 如果缓冲区里面有数据,但是小于所请求的数据量,那么recv会返回现有的数据。

  4. 如果对面连接已经关闭,那么recv直接返回0.

 

绑定127.0.0.1,或者局域网IP地址,或者0.0.0.0的区别

   127.0.0.1时,只会接收connecetIP地址为127.0.0.1的连接

   局域网IP地址,只会接收connecetIP地址为对应的局域网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(),但是没有任何对象可以同时执行这两对操作。

     Socketmakefile()操作,可以让使其可以像文件流一样进行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)
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
2 收藏
0
分享
返回顶部
顶部