想写一个能让两个不同的主机间能进行通讯的软件??想仿造一个类似app store一样的软件???
如果你打算开发和网络有关的程序, 比如聊天软件,那么你绝对绕不过网络编程这一关,而socket通信就是网络编程的一个重点。
1.Socket的定义
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。(引自百度百科)Emmmm... 想了解理论部分的同学我建议你们到其他文章中查阅相关内容,咱们重点说说实战方面吧
2一个简单的Socket通信例子
# -*-coding:utf-8 -*-
# Server端
# Server.py
import socket
HOST = '127.0.0.1'
PORT = 33333
ADDR = (HOST,PORT)
# AF_INET 表示连接使用ipv4地址族 SOCK_STREAM表示用流式套接字
tcpSerSock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcpSerSock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 将套接字绑定该地址
tcpSerSock.bind(ADDR)
# 参数1表示阻塞模式 0表示非阻塞模式 默认为阻塞模式
tcpSerSock.setblocking(1)
# 开始监听TCP传入连接。参数指定在拒绝连接之前,操作系统可以挂起的最大连接数量。
tcpSerSock.listen(5)
print "Waiting connect..."
# tcpCliSock 是该链接的套接字,addr表示对方的地址
tcpCliSock, addr = tcpSerSock.accept()
# 设置超时时间
tcpCliSock.settimeout(20.0)
print '...connected from', addr
# recv(param)用于接收对方发送的数据 param为缓冲区大小
data = tcpCliSock.recv(1024)
print data
tcpCliSock.sendall("here is server")
# 关闭套接字
tcpCliSock.close()
# -*- coding: utf-8 -*-
# client端
# Client.py
import socket
address = ('127.0.0.1', 33333)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.connect(address)
s.sendall("here is client")
data = s.recv(1024)
print data
s.close()
运行结果服务器端运行结果客户端运行结果
listen(x)方法用于监听TCP传入连接。参数指定在拒绝连接之前,操作系统可以挂起的最大连接数量。假设a主机connect服务器,服务器会与a建立连接进行socket通信。如果在a与服务器断开连接前b主机也connect服务器,那么服务器会挂起b连接,直到a与服务器断开连接后,服务器才会与b进行连接。而listen方法的参数x正是表示服务器最多可以挂起多少个连接数的。
想了解更多socket模块的方法详细使用方法及参数的朋友们可以看看这篇文章(非知乎连接,用浏览器打开观看更佳)
但是实际应用中经常需要服务器同时与多个客户端进行连接呀,那应该怎么办呢????解决方案可以开启多线程或者是多进程。多进程的消耗是比多线程大的,但对于当今服务器(基于Linux)的配置来说,这些消耗基本可以忽略不计。
我们今天先讨论多线程服务器。
3.1多线程
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
通过一个简单例子了解多线程
# -*- coding:utf-8 -*-
# simpleThread_1.py
import threading
import time
def process():
n = 0
while(n<5):
print str(n)+": the threading%sis running"%threading.current_thread().name
n = n + 1
time.sleep(1)
# 创建新线程 target为线程要执行的方法 name为进程的名字,name参数可以省略,其实名字基本无多大用处
t1 = threading.Thread(target=process,name="One")
t2 = threading.Thread(target=process,name="Two")
t1.start()
t2.start()
那么线程t1和t2将能同时运行simpleThread_1.py 运行结果
如果在t1.start()和t2.start()中间加多个:t1.join() 那么线程t2将在t1运行结束后才会开始运行
3.2多线程服务器
#-*- coding:utf-8 -*-
# 多线程服务器
# threads_Server.py
import socket
import select
import threading
import time
def process(tcpCliSock,addr):
print "connect from "+str(addr)
pattern_data = tcpCliSock.recv(1024)
print data
tcpCliSock.sendall("here is server")
def run():
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("127.0.0.1",33333))
server.listen(5)
while True:
r,w,e = select.select([server,],[],[],1)
# enumerate()分别列举出list r中的序号和内容
for i,server in enumerate(r):
conn,addr = server.accept()
t = threading.Thread(target=process,args=(conn,addr))
t.start()
if __name__=="__main__":
run()
当然,其实多线程服务器更普遍的应用场景在于长连接。
那么那么那么,我们又遇到了一个问题。客户端可能会忽然断开连接,然而服务器却没有发现,那服务器该多伤心呀,被劈腿了还不知道[逃]。多线程服务器面对这么多个客户端的连接,那么它怎么知道客户端是否还与服务器保持连接呢?于是我们又推出了一个神器:心跳包机制
心跳包可以定时向对方发送一个数据包,通过发送情况判断该TCP的连接情况。
关于心跳包机制有很多种设计方法,比如:client定时向server发送心跳包,server收到心跳包后向client发送个包给予回应,这样双方都能知道对方是否在线。
server定时向client发送心跳包,如果发送失败则表示client已断开连接,否则表示client还在连接中。这样就属于server的单方面监测了
设计方案不止以上两种,具体情况具体分析!!!记住记住!!
那么我们接下来就来看看加上了心跳包的多线程服务器长啥样~
#-*- coding:utf-8 -*-
# 多线程服务器(心跳包版)
# hreatBeat_Server.py
import socket
import select
import threading
import time
# 心跳包线程
def hreatBeat(conn):
sum = 0 # 无回应次数
while True:
time.sleep(10)
if sum<3:
try:
conn.sendall("hreatBeat")
sum=0
except socket.error:
sum = sum + 1
continue
else:
conn.close()
break
def process(tcpCliSock,addr):
print "connect from "+str(addr)
pattern_data = tcpCliSock.recv(1024)
print data
tcpCliSock.sendall("here is server")
# 创建心跳包线程
# 须记住,创建新线程时 args参数如果只有一个的话一定要加个逗号!!
thr = threading.Thread(target=hreatBeat, args=(tcpCliSock,))
thr.start()
# thr.is_alive() 用于监测进程thr是否还存在(即client是否还在连接中)
while thr.is_alive():
print "do everything you like here"
def run():
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("127.0.0.1",33333))
server.listen(5)
while True:
r,w,e = select.select([server,],[],[],1)
# enumerate()分别列举出list r中的序号和内容
for i,server in enumerate(r):
conn,addr = server.accept()
t = threading.Thread(target=process,args=(conn,addr))
t.start()
if __name__=="__main__":
run()
4.总结
Socket通信入门很简单,但深入可不容易。想进步就多学习,多练习!相信以上的知识,足够你搭建出一个成功服务器的雏形了。作为程序猿,需求摆在第一位,一切都要结合具体需求进行分析。