博客教学1,[译]Python 中的 Socket 编程(指南), sockerserver原理解析
一、scoket
socket 只是做为了一个接口,供用户以api使用,而不用直接操作tcp协议,极大的增加了开发的效率
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
1.1、套接字家族
# 基于文件类型的套接字家族:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
# 基于网络类型的套接字家族:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候只使用AF_INET)
1.2、套接字工作流程
为了支持socket网络编程,Python 提供了两个级别访问网络的服务:
低级别的网络服务支持基本的`socket`模块,它提供了标准的`BSD Sockets API`,可以访问底层操作系统socket接口的全部方法。
高级别的网络服务模块`socketserver`,它提供了服务器中心类,可以简化网络服务器的开发。
socket逻辑通信
在Python中,import socket
后,用socket.socket()
方法来创建套接字,语法格式如下:
sk = socket.socket([family[, type[, proto]]])
参数说明:
- family: 套接字家族,可以使
AF_UNIX
或者AF_INET
。 - type: 套接字类型,根据是面向连接的还是非连接分为
SOCK_STREAM
或SOCK_DGRAM
,也就是TCP和UDP的区别。 - protocol: 一般不填默认为0。
直接socket.socket(),则全部使用默认值。
1.3、套接字方法
socket类型 | 描述 |
---|---|
socket.AF_UNIX | 只能够用于单一的Unix系统进程间通信,基于文件类型的套接字 |
socket.AF_INET | IPv4, 基于网络类型的套接字 |
socket.AF_INET6 | IPv6 |
socket.SOCK_STREAM | 流式socket , for TCP |
socket.SOCK_DGRAM | 数据报式socket , for UDP |
socket.SOCK_RAW | 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 |
socket.SOCK_SEQPACKET | 可靠的连续数据包服务 |
创建TCP Socket: | s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) |
创建UDP Socket: | s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) |
通过s = socket.socket()
方法,我们可以获得一个socket对象s,也就是通常说的获取了一个“套接字”,该对象具有一下方法:
服务端方法 | 描述 |
---|---|
s.bind() | 绑定地址(host,port)到套接字,在AF_INET下,以元组(host,port)的形式表示地址。 |
s.listen(backlog) | 开始监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。 |
s.accept() | 被动接受客户端连接,(阻塞式)等待连接的到来,并返回(conn,address)二元元组,其中conn是一个通信对象,可以用来接收和发送数据。address是连接客户端的地址。 |
客户端方法 | |
---|---|
s.connect(address) | 客户端向服务端发起连接。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 |
s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
公共方法 | |
---|---|
s.recv(bufsize) | 接收数据,数据以bytes类型返回,bufsize指定要接收的最大数据量。 |
s.send() | 发送数据。返回值是要发送的字节数量。 |
s.sendall() | 完整发送数据。将数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。 |
s.recvform() | 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收的数据,address是发送数据的套接字地址。 |
s.sendto(data,address) | 发送UDP数据,将数据data发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。 |
s.close() | 关闭套接字,必须执行。 |
s.settimeout(timeout) | 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()) |
s.gettimeout() | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 |
s.fileno() | 返回套接字的文件描述符。 |
s.setblocking(flag) | 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。 |
s.makefile() | 创建一个与该套接字相关连的文件 |
s.getsockname() | 获取连接的本地地址 |
s.getpeername() | 获取连接的远程地址 |
socket.gethostname() | 获取主机名称 |
socket.gethostbyname(name) | 获取主机名称并解析成地址 |
注意事项:
- Python3以后,socket传递的都是bytes类型的数据,字符串需要先转换一下,
string.encode()
即可;另一端接收到的bytes数据想转换成字符串,只要bytes.decode()
一下就可以。 - 在正常通信时,
accept()
和recv()
方法都是阻塞的。所谓的阻塞,指的是程序会暂停在那,一直等到有数据过来。
1.4、socket编程思路
服务端:
- 创建套接字,绑定套接字到本地IP与端口:socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.bind()
- 开始监听连接:s.listen()
- 进入循环,不断接受客户端的连接请求:s.accept()
- 接收传来的数据,或者发送数据给对方:s.recv() , s.sendall()
- 传输完毕后,关闭套接字:s.close()
客户端:
- 创建套接字,连接服务器地址:socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.connect(地址:port)
- 连接后发送数据和接收数据:s.sendall(), s.recv()
- 传输完毕后,关闭套接字:s.close()
TCP编程
# 服务端
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
sk = socket.socket() # 创建套接字
sk.bind(('127.0.0.1', 9999)) # 绑定服务地址
sk.listen(5) # 监听连接请求
print('启动socket服务,等待客户端连接...')
conn, address = sk.accept() # 等待连接,此处自动阻塞
#( 三次握手建立的双向连接, (客户端 的IP,port) )
# print(conn)
# <socket.socket fd=212, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 62679)>
while True: # 一个死循环,直到客户端发送‘exit’的信号,才关闭连接
client_data = conn.recv(1024).decode() # 接收信息
if client_data == "exit": # 判断是否退出连接
exit("通信结束")
print("来自%s的客户端向你发来信息:%s" % (address, client_data))
conn.sendall('服务器已经收到你的信息'.encode()) # 回馈信息给客户端
conn.close() # 关闭连接
# 客户端
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
s = socket.socket() # 创建套接字
s.connect(('127.0.0.1', 9999)) # 连接服务器
while True: # 通过一个死循环不断接收用户输入,并发送给服务器
inp = input("请输入要发送的信息: ").strip()
if not inp: continue # 防止输入空信息,导致异常退出
s.sendall(inp.encode())
if inp == "exit": print("结束通信!") break # 如果输入的是‘exit’,表示断开连接
server_reply = s.recv(1024).decode()
print(server_reply)
s.close() # 关闭连接
UDP编程
# 服务端
ss = socket() #创建一个服务器的套接字
ss.bind() #绑定服务器套接字
inf_loop: #服务器无限循环
cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
ss.close() # 关闭服务器套接字
# 客户端
cs = socket() # 创建客户套接字
comm_loop: # 通讯循环
cs.sendto()/cs.recvfrom() # 对话(发送/接收)
cs.close() # 关闭客户套接字
# 服务端
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 2000))
while True:
msg, addr = sock.recvfrom(1024)
print(msg, addr)
sock.sendto("服务端收到数据 {}".format(msg).encode("utf-8"), addr)
# 客户端
import socket
st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip_port = ("127.0.0.1", 2000)
while True:
inp = input(">>>: ").strip()
if not inp: continue
st.sendto(inp.encode("utf-8"), ip_port)
s_data, addr = st.recvfrom(1024)
print(s_data.decode("utf-8"), addr)
socket.close()
二、socket–黏包
只有TCP有粘包现象,UDP永远不会粘包,一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。
发送端可以是一K一K地发送数据,而接收端的应用程序可以2k/3k提走数据或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。
而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。
# 怎样定义消息呢?
可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。
# 粘包问题?
例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束, 所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
# 数据流协议
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难以分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头
# 消息说明
udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
拆包
# 拆包的发生情况
当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。
# 补充问题一:为何tcp是可靠传输,udp是不可靠传输
tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的, 而udp发送数据,对端是不会返回确认信息的,因此不可靠
# 补充问题二:send(字节流)和recv(1024)及sendall
recv里指定的1024意思是从缓存里一次拿出1024个字节的数据
send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失
2.1、不粘包的udp
# 服务端 --- 伪代码
import socket
import subprocess
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip_port = ("127.0.0.1", 2004)
server.bind(ip_port)
while True:
data, addr = server.recvfrom(1024)
res = subprocess.Popen("{}".format(data.decode("utf-8")), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
server.sendto(res.stdout.read().decode("gbk").encode("utf-8"), addr)
# 客户端-- udp
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip_port = ("127.0.0.1", 2004)
while True:
inp = input(">>>>: ").strip()
if not inp: continue
server.sendto("{}".format(inp).encode("utf-8"), ip_port)
data, addr = server.recvfrom(4096)
print(data.decode("utf-8"))
2.2、low版tcp黏包
获取长度,并通过sendall 将数据全部发送在返回,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据,极度依赖于网络
############################# 服务端 #############################################
import socket
import subprocess
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 2005))
server.listen(3)
conn, addr = server.accept()
while True:
data = conn.recv(512)
if data == "exit": exit(3)
res = subprocess.Popen("{}".format(data.decode("utf-8")), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = res.stdout.read()
stderr = res.stderr.read()
sub_res = stderr if stderr else stdout
data_length = len(sub_res)
conn.send(str(data_length).encode("utf-8"))
data = conn.recv(1024).decode("utf-8")
if data == "recv_ready":
# sendall 完整发送数据。将数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None
conn.sendall(sub_res.decode("gbk").encode("utf-8"))
conn.close()
############################# 客户端 #############################################
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect_ex(("127.0.0.1", 2005))
while True:
inp = input(">>>>: ").strip()
if not inp: continue
client.send("{}".format(inp).encode("utf-8"))
length = int(client.recv(1024).decode("utf-8"))
client.send("recv_ready".encode("utf-8"))
send_size, recv_size = 0, 0
data = b""
while recv_size < length:
data += client.recv(1024)
recv_size += len(data)
print(data.decode("utf-8"))
2.3、黏包解决
使用struct 获取长度, 与上一个相比较,少了一个传输,优化了一下网络传输速度,但还是比较low
############################# 服务端 #############################################
import socket
import struct
import subprocess
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 2006))
s.listen(2)
conn, addr = s.accept()
while True:
command = conn.recv(1024)
# 获取客户端发过来的命令,并执行返回结果
res = subprocess.Popen(command.decode("utf-8"), shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
)
stdout = res.stdout.read() # 这里的获取最好与命令的stdout err一致
stderr = res.stderr.read()
# 1、添加报头长度
length = len(stdout) + len(stderr)
command_length = struct.pack("i", length)
# 2、发送报头
conn.send(command_length)
# 3、发送数据
conn.sendall(stdout)
conn.sendall(stderr)
conn.close()
############################# 客户端 #############################################
import socket
import struct
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect_ex(("127.0.0.1", 2006))
while True:
inp = input(">>>: ").strip()
if not inp: continue
# 1、发送命令
client.send(inp.encode("utf-8"))
# 2、获取报头长度
header = client.recv(4)
total_size = struct.unpack("i", header)[0]
recv_size = 0 # 接收数据大小
res = b"" # 数据拼接
# 3、循环获取数据
while recv_size < total_size:
data = client.recv(1024) # 1024-8096之间
res += data # 数据的拼接
recv_size += len(data) # 每次增加接收的长度
print(res.decode("gbk"))
2.4、黏包(自定义报头)
服务端
############################# 服务端 #############################################
#coding:utf-8
#
import json
import struct
import socket
import subprocess
obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
obj.bind(('127.0.0.1', 2007))
obj.listen()
while True:
conn, client = obj.accept()
print("客户端地址: {}".format(client))
while True:
try:
data = conn.recv(1024)
if len(data) == 0: break
obj = subprocess.Popen(data.decode("utf-8"), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
# 制作一个命令的文件字典,用于传递文件的大小, 固定header的长度
header_dic = {
# 发送一个字典到客户端, 用于在操作缓存中读取数据
"header_lenth": len(stdout) + len(stderr)
}
# 将字典转换成字符串格式, 用于服务端之间传递数据
header_json = json.dumps(header_dic).encode("utf-8")
# # 1、设置报头大小
header = struct.pack("i", len(header_json)) # 以4个字节的固定长度发到客户端上,然后在操作字典
# 2、将报头发给客户端
conn.send(header) # 先前字典的固定长度4个字节,带上命令的长度发送第一次
conn.send(header_json) # 第二次将字典发送过去
conn.send(stderr)
conn.send(stdout)
except Exception:
break
conn.close()
obj.close()
############################# 客户端 #############################################
import json
import socket
import struct
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect_ex(("127.0.0.1", 2007))
while True:
inp = input(">>>: ").strip()
if not inp: continue
client.send(inp.encode("utf-8"))
# 1、获取报头,先接收第一个4个字节
data = client.recv(4)
total_size = struct.unpack("i", data)[0]
# 2、 获取到字典的固定长度之后, 直接获取字典的大小
header_dic = client.recv(total_size)
# 接收到的是bytes格式,转换成utf-8格式, 然后在使用json
header_json = json.loads(header_dic)
recv_size = 0
res = b""
while recv_size < header_json["total_size"]:
data = client.recv(1024)
res += data
recv_size += len(data)
print(res.decode("gbk"))
三、socketserver
Python为了满足我们对多线程网络服务器的需求,提供了socketserver
模块。socketserver 在内部使用IO多路复用以及多线程/进程机制 ,实现了并发处理多个客户端请求的socket服务端。每个客户端请求连接到服务器时,socketserver服务端都会创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。
示例-demo
# sockerserver创建说明
class Myhandle(socketserver.BaseRequestHandler): # 必须继承sockerserver的基类
def handle(self): # 调用基类的函数
self.request # 等于是 socket.accept 元组的第一个值conn
# --- __main__ -------
s = socketserver.ThreadingTCPServer((ip,port), MyServer)
# 相当于 创建socker对象,bind, listen
s.server_forever() # 循环建立连接,每建立一个连接就会启动一个线程(专门与建立好的链接通信)
# 相当于 while True: s.accept()
############################# 服务端 #############################################
#coding:utf-8
# 服务端
import socketserver
class Myhandle(socketserver.BaseRequestHandler):
def handle(self):
print(self.request) # = 等于是 socket.accept 元组的第一个值conn
while True: # 通信循环
try:
data = self.request.recv(1024)
print(self.client_address, data)
if len(data) == 0:break
self.request.send(data)
except Exception:
break
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(("127.0.0.1",1234), Myhandle)
server.serve_forever()
############################# 客户端 #############################################
#coding:utf-8
# 客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1",1234))
while True:
msg = input(">>>>>: ").strip()
if len(msg) == 0: continue
client.send(msg.encode("utf-8"))
data = client.recv(1024)
print(data)
client.close()
源码分析
在当前文件右键-》Diagrams-》Show Diagrams-》Python Class Diagrams. 点击UML图界面上方的m图片可以显示成员函数,点击f图标可以显示成员变量
# class Myhandle(socketserver.BaseRequestHandler):
class BaseRequestHandler: # 继承类
"""Base class for request handler classes.
This class is instantiated for each request to be handled. The
constructor sets the instance variables request, client_address
and server, and then calls the handle() method. To implement a
specific service, all you need to do is to derive a class which
defines a handle() method.
这个类用于处理每个request的通信.构建类的时候需要request,客户地址和server对象 ,然后调用handle()方法.想完成一个特定的服务,需要继承此类然后定义一个handle()方法.
The handle() method can find the request as self.request, the
client address as self.client_address, and the server (in case it
needs access to per-server information) as self.server. Since a
separate instance is created for each request, the handle() method
can define other arbitrary instance variables.
handle()方法里通过self.request找到request.由于针对每个request都生成独立的对象,handle()方法里还可以定义任意的变量.
"""
使用ThreadingTCPServer的要点:
- 创建一个继承自
socketserver.BaseRequestHandler
的类; - 这个类中必须定义一个名字为
handle
的方法,不能是别的名字! - 将这个类,连同服务器的ip和端口,作为参数传递给
ThreadingTCPServer()
构造器 - 手动启动ThreadingTCPServer。
ThreadingTCPServer
这个类是一个支持多线程和TCP协议的socketserver
,它的继承关系是这样的:
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
多线程类 功能父类
右边的TCPServer
实际上是主要的功能父类,而左边的ThreadingMixIn
则是实现了多线程的类, ThreadingTCPServer
自己本身则没有任何代码。
MixIn
在Python的类命名中很常见,称作“混入”,戏称“乱入”,通常为了某种重要功能被子类继承。
threadingMinIn源码分析
class ThreadingMixIn:
daemon_threads = False
def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
def process_request(self, request, client_address):
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()
在ThreadingMixIn
类中,其实就定义了一个属性,两个方法。其中的process_request()
方法实际调用的正是Python内置的多线程模块threading
。这个模块是Python中所有多线程的基础,socketserver本质上也是利用了这个模块。
socketserver通过threading模块,实现了多线程任务处理能力,可以同时为多个客户提供服务。
如果想同时连接多个客户端, 服务器无法同时对多个客户端提供服务。为什么会这样呢?因为Python的socket模块,默认情况下创建的是单进程单线程,同时只能处理一个连接请求,如果要实现多用户服务,那么需要使用多线程机制
下面我们使用Python内置的threading模块,配合socket模块创建多线程服务器
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import threading # 导入线程模块
def link_handler(link, client):
"""
该函数为线程需要执行的函数,负责具体的服务器和客户端之间的通信工作
:param link: 当前线程处理的连接
:param client: 客户端ip和端口信息,一个二元元组
:return: None
"""
print("服务器开始接收来自[%s:%s]的请求...." % (client[0], client[1]))
while True: # 利用一个死循环,保持和客户端的通信状态
client_data = link.recv(1024).decode()
if client_data == "exit":
print("结束与[%s:%s]的通信..." % (client[0], client[1]))
break
print("来自[%s:%s]的客户端向你发来信息:%s" % (client[0], client[1], client_data))
link.sendall('服务器已经收到你的信息'.encode())
link.close()
ip_port = ('127.0.0.1', 9999)
sk = socket.socket() # 创建套接字
sk.bind(ip_port) # 绑定服务地址
sk.listen(5) # 监听连接请求
print('启动socket服务,等待客户端连接...')
while True: # 一个死循环,不断的接受客户端发来的连接请求
conn, address = sk.accept() # 等待连接,此处自动阻塞
# 每当有新的连接过来,自动创建一个新的线程,
# 并将连接对象和访问者的ip信息作为参数传递给线程的执行函数
t = threading.Thread(target=link_handler, args=(conn, address))
t.start()
demo_udp
#coding:utf-8
# 服务端
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议 udp
server.bind(("127.0.0.1",12346))
while True:
data, client_add = server.recvfrom(1024)
server.sendto(data, client_add)
#coding:utf-8
# 客户端
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议 udp
while True:
msg = input(">>>>>: ").strip()
server.sendto(msg.encode("utf-8"), ("127.0.0.1", 12346))
data, server_add = server.recvfrom(1024)
print(data)
,自动创建一个新的线程,
# 并将连接对象和访问者的ip信息作为参数传递给线程的执行函数
t = threading.Thread(target=link_handler, args=(conn, address))
t.start()
demo_udp
```python
#coding:utf-8
# 服务端
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议 udp
server.bind(("127.0.0.1",12346))
while True:
data, client_add = server.recvfrom(1024)
server.sendto(data, client_add)
#coding:utf-8
# 客户端
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议 udp
while True:
msg = input(">>>>>: ").strip()
server.sendto(msg.encode("utf-8"), ("127.0.0.1", 12346))
data, server_add = server.recvfrom(1024)
print(data)