是笔记。socket实现聊天室,需要有服务端和客户端,此博客用于记录此程序所用所有方法的功能,便于理解。
先服务端:
import socket
import threading
from Tools.scripts.treesync import raw_input
con = threading.Condition() #判断条件 锁
# 线程A需要等某个条件成立才能继续往下执行,现在这个条件不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A继续执行。
# 在pthread库中通过条件变量(Condition Variable)来阻塞等待一个条件,或者唤醒等待这个条件的线程。
# thread内部Condition条件变量有两个关键函数, await和signal方法,对应python threading Condition是wait和notify方法。
#
# 一个Condition实例的内部实际上维护了两个队列,一个是等待锁队列,mutex内部其实就是维护了一个队列。 另一个队列可以叫等待条件队列,在这队列中的节点都是由于(某些条件不满足而)线程自身调用wait方法阻塞的线程,记住是自身阻塞。最重要的Condition方法是wait和 notify方法。另外condition还需要lock的支持, 如果你构造函数没有指定lock,condition会默认给你配一个rlock。
# 下面是这两个方法的执行流程。
# await方法:
# 1. 入列到条件队列(注意这里不是等待锁的队列)
# 2. 释放锁
# 3. 阻塞自身线程
# ————被唤醒后执行————-
# 4. 尝试去获取锁(执行到这里时线程已不在条件队列中,而是位于等待(锁的)队列中,参见signal方法)
# 4.1 成功,从await方法中返回,执行线程后面的代码
# 4.2 失败,阻塞自己(等待前一个节点释放锁时将它唤醒)
# 注意: 调用wait可以让当前线程休眠,等待其他线程的唤醒,也就是等待signal,这个过程是阻塞的。 当队列首线程被唤醒后,会继续执行await方法中后面的代码。
# signal (notify)方法:
#
# 1. 将条件队列的队首节点取出,放入等待锁队列的队尾
# 2. 唤醒节点对应的线程.
# 注: signal ( notify ) 可以把wait队列的那些线程给唤醒起来。
host = raw_input('input the server ip address:')
port = 8888
data = '' #文本
# TCP编程的服务器端一般步骤是:
# 1、创建一个socket,用函数socket();
# 2、设置socket属性,用函数setsockopt();
# 3、绑定IP地址、端口等信息到socket上,用函数bind();
# 4、开启监听,用函数listen();
# 5、接收客户端上来的连接,用函数accept();
# 6、收发数据,用函数send()
# 和recv(),或者read()
# 和write();
# 7、关闭网络连接;
# 8、关闭监听;
s = socket.socket() #创建了一个服务
print('Socket created')
s.bind((host,port)) #将IP绑定到固定端口上
s.listen(5) #监听连接,listen(n)传入的值, n表示的是服务器拒绝(超过限制数量的)连接之前,操作系统可以挂起的最大连接数量。
# n也可以看作是"排队的数量",即除了正在处理(已接入)的线程数外还可等待进入线程中的数量
#self.Bind(wx.EVT_BUTTON, self.OnButtonClick, self.button)
# self.Bind(事件类型, 事件名, 绑定的控件名)
# self.button.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
# self.button.Bind(事件类型, 事件名)
# Bind是用来将控件事件与控件做绑定的,用self.Bind时,在后面的参数中你可以看到加入了控件,self.button.Bind没有,因为是直接使用了控件的Bind。
print('Socket new listening')
def NotifyAll(sss):
global data
if con.acquire(): #获取锁 使得当前只能进行对data赋值的操作,其他操作不能进行
data = sss
con.notifyAll() #表示当前线程放弃对资源的占用 通知其他线程从wait方法后面执行
con.release() #释放锁
def threadOut(conn,nick): #发送消息
global data
while True:
if con.acquire():
con.wait() #会阻塞在这里,放弃对当前资源的占用,等消息通知
if data:
try:
conn.send(data) #发送
con.release()
except:
con.release()
return
def ThreadIn(conn,nick): #接收消息
while True:
try:
temp = conn.recv(1024)
if not temp:
conn.close()
return
NotifyAll(temp)
print(data)
except:
NotifyAll(nick+'error')
while True:
conn,addr = s.accept() #接收到连接了,返回的是一个元组,conn参数是服务端与客户端的链接,addr返回的是客户端的IP和端口
print('Connected with'+addr[0]+':'+str(addr[1]))
nick = conn.recv(1024).decode(encoding='utf-8')
#在服务器端,socket()返回的套接字用于监听(listen)和接受(accept),这个套接字不能用于与客户端之间发送和接收数据。
NotifyAll('welcome'+nick+'to the room!!!')
print(data)
conn.send(data.encode())
threading.Thread(target=threadOut,args=(conn,nick)).start()
threading.Thread(target=threadOut,args=(conn,nick)).start()
然后是客户端:
#客户端
import socket #自带的模块
import threading
from Tools.scripts.treesync import raw_input
#设置全局变量,全部定义为空,若函数出错,仍可发送空消息
outString = ''
nick = ''
inString = ''
def client_send(sock): #在函数内部改变全局变量,sock为参数
global outString
while True: #使客户端一直处于能够发送的循环状态
#死循环 一直监听输入 若有输入 就会发送至服务端
outString = raw_input() #接收输入,此处有bug,暂无法解决,后续有解决方法再修改
outString = nick+':'+outString
sock.send(outString) #发送outString的数据,发送TCP数据,返回发送的字节大小。这个字节长度可能少于实际要发送的数据的长度。
# 换句话说,这个函数执行一次,并不一定能发送完给定的数据,可能需要重复多次才能发送完成。
# TCP编程的客户端一般步骤是:
# 1、创建一个socket,用函数socket();
# 2、设置socket属性,用函数setsockopt();
# 3、绑定IP地址、端口等信息到socket上,用函数bind();
# 4、设置要连接的对方的IP地址和端口等属性;
# 5、连接服务器,用函数connect();
# 6、收发数据,用函数send()
# 和recv(),或者read()
# 和write();
# 7、关闭网络连接;
def client_accept(sock):
global inString #在生产环境不推荐使用
while True:
try:
inString = sock.recv(1024) #接收数据,不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
# recv函数是阻塞的,也就是会一直等待服务端发送来的数据包。如果没有数据包到来,就一直会等待。
if not inString:
break
if outString != inString:
print(inString)
except:
break
#创建套接字
nick = raw_input('input your nickname:') # raw_input() 用来获取控制台的输入,将所有输入作为字符串看待,返回字符串类型。
ip = raw_input('input the server ip address:') #输入ip地址
port = 8888 #端口
sock = socket.socket() #创建文明的套接字,调用socket包里面的socket方法,AF_INET表示是IPv4协议
# socket通常也称作"套接字",用于描述IP地址和端口,应用程序通常通过"套接字"向网络发出请求或者应答网络请求,可以认为是一种计算机网络的数据结构,接口。
sock.connect((ip,port)) #连接
sock.send(nick.encode()) #把用户名发送给服务端
th_send = threading.Thread(target=client_send,args=(sock,)) #创建发送信息的线程
# target : 指定这个线程去哪个函数里面去执行代码
#
# args: 指定将来调用函数的时候传递什么数据过去
#
# args参数指定的一定是一个元组类型,元组即需用括号括起来的集合,当只有一个参数时,需在此参数后加逗号来表明是元组类型
th_send.start() #启动发送线程
th_accept = threading.Thread(target=client_accept,args=(sock,)) #创建接收信息的线程
th_accept.start() #启动接收线程
outString = raw_input() 此处有bug,暂无法解决,后续有解决方法再修改。错误提示:TypeError: raw_input() missing 1 required positional argument: 'self'