网络编程-socket编程
1.什么是c/s架构?
顾名思义,就是客户端端/服务器架构。不同的人可能回答不一,但是有一点是相同的:服务器是一个软件或硬件,用于向一个或多个客户端提供所需要的服务,服务器存在的唯一目的就是等待客户的请求,给这些客户服务,然后等待其他的请求。
2.客户端与服务端如何通信?
其实说白了就是互联网中两个主机该如何通信,首先我们用ip地址可以标示一台主机,这样就可以通信了么?当然也不行,我们还得标示主机中的进程,当然协议也是不可或缺的,是udp报文协议还是tcp数据流协议,这的看你个人的需求。用套介子来作为一个标示符,唯一标识网络进程。
下面图示:
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
服务端:
采用socket()函数 定义socket描述字
bind()函数来把地址族中一个特定地址赋给socket,简单来说就是把一个ipv4/ipv6地址+端口号组合赋给socket
listen()函数把socket变成被动类型的,等待客户连接请求。
当客户端发来消息时用accept()函数来接受请求,同时给服务端返回客户端的socket描述字,接下来服务端以接收到的描述字来向客户端发送信息以及接受信息。
下面我们模拟发送消息和接收消息的过程:
服务器:
#usr/bin/python #-*-coding:utf-8-*- import socket #1先创建一个服务端 server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定IP和端口 server.bind(('127.0.0.1.',8009)) #设置监听 server.listen(5) print('服务端启动') #等待客户端链接 如果有链接 将返回一个链接对象和对方的地址 conn,address=server.accept() #接受客户端发过来 多大子节点的数据 msg=conn.recv(1024) #给客户端回复数据 print('客户端发来:',msg) conn.send(msg.upper()) #发消息 #结束了,连接关闭,服务关闭 conn.close() server.close()
客户端:
#usr/bin/python #-*-coding:utf-8-*- import socket #创建客户端 server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.connect(('192.168.0.143',8011)) print('客户端成功连接服务器 n结束') server.send('hello'.encode('utf-8'))#发消息 data=server.recv(1024) print('服务器: ',data) server.close()
上述结果服务端会打印:客户端发来:b'HELLO'。
上述C/S构架只能发送一条消息,且不能发送中文。我们稍作更改,使其可以聊天且可以发送中文:
服务端:
#usr/bin/python
#-*-coding:utf-8-*-
import socket
#1先创建一个服务端
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定IP和端口
server.bind(('127.0.0.1.',8022))
#设置监听
server.listen(5)
print('服务端启动')
#等待客户端链接 如果有链接 将返回一个链接对象和对方的地址
conn,address=server.accept()
#接受客户端发过来 多大子节点的数据
while True:
msg=conn.recv(1024)
#给客户端回复数据
print('客户端发来:',msg.decode()) #decode解码 encode编码
conn.sendall(input('服务端:').encode('utf-8'))
#结束了,连接关闭,服务关闭
conn.close()
server.close()
注:
socket.send(string[, flags]) 发送TCP数据,返回发送的字节大小。这个字节长度可能少于实际要发送的数据的长度。换句话说,这个函数执行一次,并不一定能发送完给定的数据,可能需要重复多次才能发送完成。
socket.sendall(string[, flags]) 看懂了上面那个,这个函数就容易明白了。发送完整的TCP数据,成功返回None,失败抛出异常
在接受客户端发来的数据后做了一个while无限循环,先将接受到的信息做解码decode,然后发送信息并做转码encode,使可以书写中文。
客户端:
#usr/bin/python #-*-coding:utf-8-*- import socket #创建客户端 server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.connect(('192.168.0.143',8022)) print('客户端成功连接服务器 n结束') while True: data=input('客户端:') if data.__eq__('n'):break server.send(data.encode('utf-8')) data=server.recv(1024) print('服务器: ',data.decode()) server.close()
与服务端一样,做一个while无循环,停止条件是输入n,将输入的信息转码发送过去,将接受到的信息解码输出。
我们已经粗略了解了C/S结构,下面我们做一个可以多人本地连接的C/S结构。
服务端:
#usr/bin/python #-*-coding:utf-8-*- #群聊客户端 import socket,threading #创建一个服务端 server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定IP和端口 server.bind(('127.0.0.1',8025)) #设置最大挂起连接数5 server.listen(5) print('服务端 启动,等待客户端连接...') #中间转换数据的变量 message='' #创建一把锁 lock=threading.Lock() cond=threading.Condition(lock=lock) #不停的收 def recv_msg(conn,address): while True: msg=conn.recv(1024) global message cond.acquire() message=str(address)+':'+msg.decode() print('收到:'+message) cond.notify_all() #唤醒其他发消息的线程 cond.release() #不停的发 def send_msg(conn,address): while True: cond.acquire() cond.wait() cond.release() conn.sendall(message.encode('utf-8')) print('发送'+message) while True: conn,address=server.accept() threading._start_new_thread(recv_msg,(conn,address)) threading._start_new_thread(send_msg,(conn,address))
这个服务器与上面的区别在于,它最后执行了一个while无限循环,一直执行了收发两个线程,同时两个线程之间有锁,通过不停的等待和唤醒交替进行。我们定义了两个函数,一个用来接收消息,一个用来发送消息。
接收消息:首先用msg存储接收到的数据,然后引入全局变量message,message的值为接收到信息的地址加上msg的解码,然后将信息打印,同时唤醒发送消息的锁,对应的在唤醒锁前后写上加锁与解锁。
发送消息:一开始如果服务端没有接到到信息,发送消息线程不会启动,所以我们设置一个锁,使其等待,同时在前后写上加锁与解锁。在唤醒之后,将接受到的信息编码并发送给所有客户端,同时打印message。
客户端:
#usr/bin/python #-*-coding:utf-8-*- import socket,threading #创建客户端 client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #建立连接 client.connect(('127.0.0.1',8026)) #收取其他用户的数据 def recv_msg(): while True: msg=client.recv(1024) print('收到:',msg.decode()) threading._start_new_thread(recv_msg,()) #不停的发 while True: msg=input('请数据消息:') client.send(msg.encode('utf-8'))在客户端中,我们需要定义一个线程用于接受其他用户的消息即recv_msg,就是将接受到的信息解码打印,然后创建一个while循环,用于不停的发送消息,即将我们input的值编码发送到服务器,然后由服务器发送给连接到的所有客户端。
以上服务端与客户端的IP与端口要保持一致!!!!!!!!