udp通信模型中,在通信开始之前,不需要建立相关的链接,只需要发送数据即可
tcp通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话""
总结:
tcp传输控制协议:稳定、相对于udp而言,要慢一些、web服务器都是使用的tcp
udp用户数据包协议:不稳定、适当比tcp要快一些。
想要完成一个tcp服务器的功能,需要的流程如下:
socket:创建一个套接字,默认是主动的套接字(相当于买个手机)
bind:绑定ip和port (插上手机卡)
listen:使套接字变为可以被动链接 (设计手机为正常接听状态(即能够响铃)) listen(5)代表:最多可以接受5个客户端的连接
accept:等待客户端的链接 (静静的等着别人拨打)
recv/send:接收发送数据
NAT模式下收发数据:
#coding =utf-8
from socket import *
tcpSerSocket = socket(AF_INET,SOCK_STREAM)
tcpSerSocket.bind(("",7788))
tcpSerSocket.listen(5)
newSocket,clientAddr=tcpSerSocket.accept()
recvData=newSocket.recv(1024)
print("接收到的数据是:%s"%recvData)
newSocket.send(b"xiexie!")
newSocket.close()
tcpSerSocket.close()
实现客户端收发功能:
#coding=utf-8
from socket import *
tcpCliSocket =socket(AF_INET,SOCK_STREAM)
serAddr=('192.168.20.125',7788)
tcpCliSocket.connect(serAddr)
sendData = raw_input("请输入要发送的数据")
tcoCliSocket.send(sendData)
recvData = tcpCliSocket.recv(1024)
print("接收到的数据是%s"%recvData)
tcpCliSocket.close()
注意:对于套接字,服务器端有两个,客户端有一个。
注意:参数类型是一个元组的。
运行的时候先启动服务器端,再运行脚本文件:
应用:模拟QQ聊天
客户端参考代码:
from socket import *
tcpCliSocket=socket(AF_INET,SOCK_STREAM)
serAddr = ("192.168.242.133",7788)
tcpCliSocket.connect(serAddr)
while True:
sendData = input("send")
if len(sendData)>0:
tcpCliSocket.send(sendData.encode("gb2312"))
else:
break
recvData = tcpCliSocket.recv(1024)
print('recv:',recvData.decode("gb2312"))
tcpCliSocket.close()
服务端参考代码
from socket import *
tcpSerSocket=socket(AF_INET,SOCK_STREAM)
tcpSerSocket.bind("192.168.242.133",7788)
tcpSerSocket.listen(5)
while True:
newSocket,seraddr=tcpSerSocket.accept()
while True:
recvdata =newSocket.recv(1024)
if len(recvData)>0:
print("recv",recvData.decode("gb2312"))
else:
break
sendData = input("send:")
newSocket.send(sendData.encode("gb2312"))
newSocket.close()
tcpSerSocket.close()
listen参数问题
from socket import *
tcpSerSocket=socket(AF_INET,SOCK_STREAM)
tcpSerSocket.bind("192.168.242.133",7788)
tcpSerSocket.listen(int(input("请输入最大链接数')))
while True:
newSocket,cliAddr=tcpSerSocket.accept()
print(cliAddr)
sleep(1)
客户端的代码:
from socket import *
for i in range(int(input("请输入最大的链接数")))
s=socket(AF_INET,SOCK_STREAM)
s.connect(("192.168.2420133",7788))
print(i)
mac系统就是写几就是几,而linux系统它自己有最大值,你写的不管用,这就是为什么linux系统合适做web服务器开发。
在此期间,如果20个客户端调用了connect链接服务器,那么这个服务器的linux底层会自动维护2个队列(半链接和已链接)其中半链接和已链接的总数为listen中的值,如果这个值为5,那么意味着此时最多有5个客户端能够链接成功,而剩余的15个客户端阻塞在connect函数。
如果服务器调用了accept,那么linux底层中的那个半链接和已连接中的客户端的个数就少了一个,因此此时那15个因为connect堵塞的客户端又会在进行链接来争抢这一个空出来的位置。
多进程服务器
from socket import *
from multiprocessing import Process
from time import sleep
def dd(newSocket,destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0:
print("recv[%s]:%s"%(str(destAddr),recvData))
else:
print("[%s]客户端已经关闭"%str(destAddr))
newSocker.close()
def main():
serSocket=socket(AF_INET,SOCK_STREAM)
serSocket.setSockopt(SOL_SOCKET,SO_REUSEADDR,1)
serSocket.bind(('192.168.242.133',7788))
serSocket.listen(5)
try:
while True:
print("---主进程,等待")
newSocket,destAddr = serSocket.accept()
print("主进程,创建进程")
client = Process(target=dd,args=(newSocket,destAddr))
client.start()
newSocket.close()
finally:
serSocket.close()
if __name__='__main__':
main()
默认情况下,两个独立的套接字不可与同一本地接口(在TCP/IP情况下,则是端口)绑定在一起。但是少数情况下,还是需要使用这种方式,来实现对一个地址的重复使用。设置了这个套接字,服务器便可在重新启动之后,在相同的本地接口以端口上进行监听。
一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。 SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,允许重复绑定使用。server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。
通过为每个客户端创建一个进程的方式,能够同时为多个客户端进行服务,当客户端不是特别多的时候,这种方式还行,如果有几百上千个,就不可取了,因为每次创建进程等过程需要好较大的资源。
多线程服务器
from socket import *
from threading import Thread
from time import sleep
def dd(newSocket,destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0:
print("recv[%s]:%s"%(str(destAddr),recvData))
else:
print("[%s]客户端已经关闭"%str(destAddr))
break
newSocker.close()
def main():
serSocket=socket(AF_INET,SOCK_STREAM)
serSocket.setSockopt(SOL_SOCKET,SO_REUSEADDR,1)
serSocket.bind(('192.168.242.133',7788))
serSocket.listen(5)
try:
while True:
print("---主进程,等待")
newSocket,destAddr = serSocket.accept()
print("主进程,创建进程")
client = Thread(target=dd,args=(newSocket,destAddr))
client.start()
newSocket.close()
finally:
serSocket.close()
if __name__='__main__':
main()
对于效率这一块,差不多的,但是进程耗费的资源要大,线程耗费的资源要小。不管是多进程还是多线程代码几乎一样,
区别在于:在多线程中newSocket.close()不能关闭,因为线程是共享同一份资源的,也就是共享全局变量的,传递东西也就是传递的引用,一个进程里面所有的线程都有共用同一份东西的。
如果serSocket不小心被close了,那么意味着:不能再接收新的客户端的链接了。
如果newSocket被close了,那么意味着:这个套接字就不能再使用recv和send来收发数据了。
网友评论