Python Http 百万并发客户端实现 _ V1.0

1. 源代码

#!/usr/bin/env python
# coding=utf8
# si.hairui  :  @2016.11.24
# Http Client:  @Python 2.7.5
# Platform   :  @windows 7
# ---------------------------------------------
# 0.先设置机器注册表中TIME_WAIT的值,再运行本程序:
#   reg /?
#   reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Tcpip\Parameters" /v "TcpTimedWaitDelay" /t REG_DWORD /d 2 /f
#   reg delete "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Tcpip\Parameters" /v "TcpTimedWaitDelay" /f
# 1.上电之后读取配置文件,获取客户端列表和服务器列表;
# 2.校验配置文件中客户端IP的真实性(有效性);
# 3.解析服务端URL字段内容
# 4.注册线程,开始并发请求
# ---------------------------------------------


# ---------------------------------------------
#    模块导入
# ---------------------------------------------
import socket
import httplib
import threading
from time import sleep, ctime


# ---------------------------------------------
#    配置开关
# ---------------------------------------------
PRINT_SW = 0  # 业务打印开关,0-关,1-开
PRINT_THR_SW = 0  # 线程打印开关,0-关,1-开
WHILE_TRUE_SW = 1  # 死循环开关,0-关,1-开


# ---------------------------------------------
#    全局变量
# ---------------------------------------------
#TCP_REQUEST = 0  # TCP请求总数,线程需要互斥锁访问
#TCP_REQUEST_LOCK = threading.Lock()  # 互斥锁访问
#TCP_ESTABLISHED = 0  # TCP建链成功计数器,线程需要互斥锁访问
#TCP_ESTABLISHED_LOCK = threading.Lock()  # 互斥锁访问


# 客户端信息列表
HTTP_CLIENT_LIST = []
'''
HTTP_CLIENT_LIST = [
    {'ip': '192.168.0.218', 'portbegin': 464,   'portend': 464},
    {'ip': '192.168.0.220', 'portbegin': 34684, 'portend': 34685}
]
'''
# 服务器信息
HTTP_SERVER_LIST = []
'''
HTTP_SERVER_LIST = [
    'http://192.168.0.236/web/page/',
    'http://192.168.0.235:80/web/video/aboutHFS/HFS.txt'
]
'''


# ---------------------------------------------
#    函数实现
# ---------------------------------------------
# 修改默认buff(详见socket.py Line:238, default_bufsize = 8192)
def buffInit():
	socket._fileobject.default_bufsize = 8192 * 10
	socket._fileobject._wbuf_len = 8192 * 10
	
	
# 读取配置文件,返回配置文件字符串
def readCfgFile():
	try:
		with open('config.cfg', 'r') as f:
			return f.read()  # 返回字符串
	except:
		print "Error: ReadCfgFile fail!"
		return None


# 获取本地业务地址,放入序列clientIps
def getServiceIp(clientIps):
	hostname = socket.gethostname()
	localIP = socket.gethostbyname(hostname)
	if PRINT_SW:
		print "Hostname:\n    %s" % hostname
		print "Local IP:\n    %s" % localIP
	ipList = socket.gethostbyname_ex(hostname)
	if PRINT_SW: print "ipList", ipList, "\n"  # ('Harry-PC', [], ['192.168.1.103', '192.168.1.250', '192.168.1.251', ...)
	for ips in ipList:
		# 过滤空序列、主机名
		if ips and (not isinstance(ips, str)):  # ips是一个序列,其中每个元素是IP地址字符串
			if PRINT_SW: print "External IP:"
			for ip in ips:
				if PRINT_SW: print "    %s" % ip
				clientIps.append(ip)




# 获取客户端列表,eg: HTTP_CLIENT_LIST = list(getClientInfo())
def getClientInfo():
	cfginfo = eval(readCfgFile())
	# 校验配置文件中的IP
	# a.首先获取本地业务IP地址
	locIps = []
	getServiceIp(locIps)
	# b.然后对比配置文件中的地址
	validDicts = []  # 保存配置文件中的合法元素
	for sockdict in cfginfo['clients']:
		try:
			locIps.index(sockdict['ip'])
		except ValueError:  # 如果发生异常,说明sockdict中的IP地址不在本PC上,则丢掉
			print "Error: " + sockdict['ip'] + " in <config.cfg> is not valid, it will be discarded!!!"
			continue
		else:  # 没有异常,则保存该字典
			validDicts.append(sockdict)
	if PRINT_SW: print validDicts
	return validDicts




# 获取服务端列表,eg: HTTP_SERVER_LIST = list(getServerInfo())
def getServerInfo():
	cfginfo = eval(readCfgFile())
	return cfginfo['servers']




# 根据URL获取Server的IP、port、filepath,以字典的形式返回
def getServerSocket(url):
	strIp = ""
	port = 0
	filepath = ""
	if 0 != url.find("http://"):  # URL必须以“http://”开头(暂时不考虑https的场景)
		print "Error: URL is invalid: %s !!!" % url
		return None
	# 首先,去掉“http://”
	urlUnhttp = url[7:]
	# 然后判断有无“/”
	firstXieGangLoc = urlUnhttp.find("/")  # urlUnhttp中第一个“/”下标
	firstMaoHaoLoc = urlUnhttp.find(":")  # urlUnhttp中第一个“:”下标
	if (-1) == firstXieGangLoc and (-1) == firstMaoHaoLoc:  # "192.168.0.235"
		strIp = urlUnhttp
		port = 80
		filepath = "/"
	elif (-1) == firstXieGangLoc and firstMaoHaoLoc > 0:  # "192.168.0.235:8988"
		strIp = urlUnhttp[: firstMaoHaoLoc]
		port = int(urlUnhttp[firstMaoHaoLoc + 1:])
		filepath = "/"
	elif firstXieGangLoc > 0 and (-1) == firstMaoHaoLoc:  # "192.168.0.235/..."
		strIp = urlUnhttp[: firstXieGangLoc]
		port = 80
		filepath = urlUnhttp[firstXieGangLoc:]
	elif firstXieGangLoc > 0 and firstMaoHaoLoc > 0:  # "192.168.0.235:8988/..."
		strIp = urlUnhttp[: firstMaoHaoLoc]  # 这种情况下,默认URL中“:”在“/”左边
		port = int(urlUnhttp[firstMaoHaoLoc + 1: firstXieGangLoc])
		filepath = urlUnhttp[firstXieGangLoc:]
	else:
		print "Error: URL is unable reslute: %s!!!" % url
	
	svrdict = {}.fromkeys(["ip", "port", "filepath"])
	svrdict["ip"] = strIp
	svrdict["port"] = port
	svrdict["filepath"] = filepath
	return svrdict




# 串行轮询服务器和客户端发起HTTP请求,eg: runHttpCliet(HTTP_CLIENT_LIST, HTTP_SERVER_LIST)
'''
def runHttpCliet(clients=[], servers=[]):
    if (None == clients) or (None == servers):  # 保护一下
        print "Error: clients or servers is None!!!"
        return
    for urlstr in servers:
        svrdict = getServerSocket(urlstr)  # 解析URL
        if None == svrdict or None == svrdict["ip"] or None == svrdict["port"]:
            print "Error: Server is invalid!!!", svrdict
            continue  # 执行下一个server
        if PRINT_SW:
            print "svrdict =", svrdict
        for httpClient in clients:
            if PRINT_SW:
                print "httpClient =", httpClient
            for clientPort in range(httpClient['portbegin'], httpClient['portend'] + 1):
                try:
                    myHttpConn = httplib.HTTPConnection(svrdict["ip"], svrdict["port"],
                        timeout = 2, # 设置连接超时等待(单位:s)
                        source_address = (httpClient["ip"], clientPort))
                    myHttpConn.debuglevel = 0  # 调试模式开关
                    if 0 == myHttpConn.debuglevel:
                        print "    ---- Http Request ----"
                        print "    " + httpClient["ip"] + ":", clientPort, \
                            " --> " + svrdict["ip"], svrdict["port"], ": " + svrdict['filepath']
                    myHttpConn.request("GET", svrdict['filepath'])  # 资源路径以“/”开始
                    myHttpResp = myHttpConn.getresponse()
                    if 0 == myHttpConn.debuglevel:
                        print "   ", myHttpResp.status, myHttpResp.reason  # 响应码和状态信息
                        # 此处必须读响应内容,如果不读取,buff将溢出!!!
                        data1 = myHttpResp.read()
                        pass
                except:
                    print "!!!---------- Exception ------------------------"
                    continue
                finally:
                    # print data1   # 打印响应内容
                    print "    Http(%s:%s --> %s:%s) will be closed!" % \
                          (httpClient["ip"], str(clientPort), svrdict["ip"], str(svrdict["port"]))
                    myHttpConn.close()  # 关闭连接
'''




# 并发轮训服务器发起HTTP请求,eg: runHttpCliet(clientsDic, HTTP_SERVER_LIST, delaySeconds)
# delaySeconds: 0-5,从程序开始运行时到发起链接的时延,单位:秒
def runHttpCliet(clientsDic={}, servers=[], delaySeconds=0, whileTrue=0):
	'''
	关于函数中sleep(m)的解释:


	在TCP/IP终止连接的四次握手中,当最后的ACK回复发出后,有个2MSL的时间等待,MSL指一个片段在网络中最大的存活时间,
	这个时间一般是30秒,所以基本上过60秒后就可以重新连接,详见:
	http://www.bkjia.com/Pythonjc/868893.html
	http://www.dewen.net.cn/q/8606
	http://www.51testing.com/html/48/202848-249774.html
	http://blog.csdn.net/mhfh611/article/details/8769617/
	对于 Windows,修改注册表(win+R运行输入regedit),在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
	上添加一个DWORD类型的值TcpTimedWaitDelay,一般认为不要少于60,不然可能会有麻烦。


	进入Windows命令行窗口:
	reg /?
	reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Tcpip\Parameters" /v "TcpTimedWaitDelay" /t REG_DWORD /d 30 /f
	reg delete "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Tcpip\Parameters" /v "TcpTimedWaitDelay" /f
	'''
	if (None == clientsDic) or (None == servers):  # 保护一下
		print "Error: clients or servers is None!!!"
		return
	for urlstr in servers:
		svrdict = getServerSocket(urlstr)  # 解析URL
		if None == svrdict or None == svrdict["ip"] or None == svrdict["port"]:
			print "Error: Server is invalid!!!", svrdict
			continue  # 执行下一个server
		if PRINT_SW: print "svrdict =", svrdict
		sleep(delaySeconds)  # tcp建链时间间隔
		whileTrueSw = 1
		while whileTrueSw:  # 开始死循环
			for clientPort in range(clientsDic['portbegin'], clientsDic['portend'] + 1):
				try:
					# TCP建链统计
					#global TCP_REQUEST, TCP_REQUEST_LOCK  # 多线程是共享资源的,使用全局变量
					#if TCP_REQUEST_LOCK.acquire():
						#TCP_REQUEST += 1
						#print "    TCP_REQUEST=", TCP_REQUEST
						#TCP_REQUEST_LOCK.release()
					
					myHttpConn = httplib.HTTPConnection(svrdict["ip"], svrdict["port"],
					                                    # timeout = 3, # 设置连接超时等待(单位:s)
					                                    source_address=(clientsDic["ip"], clientPort))
					myHttpConn.debuglevel = 0  # 调试模式开关
					if 0 == myHttpConn.debuglevel:
						print "    ---- Http Request ----"
						print "    " + clientsDic["ip"] + ":", clientPort, \
							" --> " + svrdict["ip"], svrdict["port"], ": " + svrdict['filepath']
					try:
						myHttpConn.request("GET", svrdict['filepath'])  # 资源路径以“/”开始
					except:
						print "    !!!---------- Exception: myHttpConn.request ----------------"
						continue
					
					# TCP成功建链统计
					#global TCP_ESTABLISHED, TCP_ESTABLISHED_LOCK  # 多线程是共享资源的,使用全局变量
					#if TCP_ESTABLISHED_LOCK.acquire():
						#TCP_ESTABLISHED += 1
						#print "    TCP_ESTABLISHED=", TCP_ESTABLISHED
						#TCP_ESTABLISHED_LOCK.release()
					try:
						myHttpResp = myHttpConn.getresponse()
					except:
						print "    !!!---------- Exception: myHttpConn.getresponse ----------------"
						continue
					if 0 == myHttpConn.debuglevel:
						print "   ", myHttpResp.status, myHttpResp.reason  # 响应码和状态信息
						# 此处必须读响应内容,如果不读取,buff将溢出!!!
						data1 = myHttpResp.read()
						pass
				except (), e:
					print "    !!!---------- Exception: %s ------------------------" % e
					continue
				finally:
					# print data1   # 打印响应内容
					print "    Http(%s:%s --> %s:%s) will be closed!" % \
					      (clientsDic["ip"], str(clientPort), svrdict["ip"], str(svrdict["port"]))
					myHttpConn.close()  # 关闭连接
					sleep(20)
			if 0 == WHILE_TRUE_SW:
				whileTrueSw -= 1
			else:
				pass




# 客户端并发线程子类,父类:threading.Thread
# 每一个客户端IP独占一个线程,线程对客户端列表和服务端列表数据只读不写
class clientThread(threading.Thread):
	def __init__(self, func, args, name='', printThrSw=0):
		threading.Thread.__init__(self)
		self.func = func
		self.args = args
		self.name = name
		self.printThrSw = printThrSw
	
	def getResult(self):
		return self.res
	
	def run(self):
		if self.printThrSw:
			print 'starting', self.name, 'at:', ctime(), ". delaySeconds=", self.args[2]
		self.res = apply(self.func, self.args)
		if self.printThrSw:
			print self.name, 'finished at:', ctime()




# 客户端注册线程
def clientRegThr():
	HTTP_CLIENT_LIST = list(getClientInfo())  # 构造客户端
	if PRINT_SW: print "HTTP_CLIENT_LIST =", HTTP_CLIENT_LIST
	HTTP_SERVER_LIST = list(getServerInfo())  # 构造服务端
	if PRINT_SW: print "HTTP_SERVER_LIST =", HTTP_SERVER_LIST
	
	threadNum = len(HTTP_CLIENT_LIST)  # 计算线程个数,也就是客户端的个数
	if PRINT_SW: print "threadNum =", threadNum
	clientThreads = []  # 线程列表
	for i in range(threadNum):  # 线程编号从0开始
		thr = clientThread(
			func=runHttpCliet,
			# args = (HTTP_CLIENT_LIST[i], HTTP_SERVER_LIST, random.randint(1,5)), # random.randint(1,5) == [1,5]
			args=(HTTP_CLIENT_LIST[i], HTTP_SERVER_LIST, i, WHILE_TRUE_SW),  # 打散并发时间
			name="clientThread_" + str(HTTP_CLIENT_LIST[i]['ip']),
			printThrSw=PRINT_THR_SW
		)
		clientThreads.append(thr)  # 添加线程至列表
	for i in range(threadNum):
		clientThreads[i].start()
	for i in range(threadNum):
		clientThreads[i].join()




# App的main()入口
def main():
	buffInit()
	clientRegThr()




# ---------------------------------------------
#    main模块
# ---------------------------------------------
if __name__ == "__main__":
	main()



2.配置文件

# ----------------------------------------------------------------------------
# config.cfg
# clients: {"ip": "客户端的IP地址", "portbegin": 起始端口号, "portend": 结尾端口号}
# servers: 暂时只支持 “http://IP地址”的格式的URL 
# 同一个IP地址不允许在下面出现2次或多次
# ----------------------------------------------------------------------------

{
    "clients" : [
            { "ip": "192.168.1.254",   "portbegin": 2343,    "portend": 12343  },
            { "ip": "192.168.1.253",   "portbegin": 16357,   "portend": 26357  },
            { "ip": "192.168.1.252",   "portbegin": 27134,   "portend": 37134  },
            { "ip": "192.168.1.251",   "portbegin": 38900,   "portend": 50000  }
    ],

    "servers" : [
        #"http://192.168.1.103/KMP/yfwy.mp3",
        #"http://192.168.1.103/KMP/16.jpg"
        "http://www.cplusplus.com",
        "http://www.appinn.com/",
        "http://www.downxia.com/"
    ]
}


3.客户端运行时



4.服务器运行时




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值