目录
socket工作流程
基于TCP的socket服务端与客户端编程
基础版本
加入连接循环
加入通信循环
支持并发的TCP服务端
常见问题
基于文件类型的套接字家族名字:AF_UNIX
基于网络类型的套接字家族名字:AF_IN
socket(简称 套接字) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
例如我们每天浏览网页、QQ 聊天、收发 email 等等
1.socket介绍
1.什么是socket?
socket套接字,是进程通信的一种实现方式
2.主要特点
主要针对不同主机之间的通信。即网络进程的通信,
几乎所有的网络都是通et流程,实现网络通信
首先拿到一个socket对象,socket对象与IP+PORT绑定才能进行端口监听.
然后socket对象监听端口是否有请求,同时调用accept方法,一直阻塞等待客户端的链接;
如果有客户端的请求发来,那么服务端接收到请求之后调用read方法读取数据;
之后再处理请求,写回数据使用write方法(python中是send方法)
当需要断开连接时服务端发送请求,客户端读取之后在调用close方法断开请求,
服务端read收到断开请求,服务端再调用close方法即可.
首先拿到一个socket对象,调用connect方法,传入IP与PORT,发送建立链接的请求,
之后就可以进行数据交互, 如果需要结束链接,直接调用close方法即可.
TCP是基于链接的, 必须先启动服务端,然后再启动客户端去链接服务器
- import socket
- import time
-
- server = socket.socket()
-
- # 注意: 传入的参数必须是一个元组或者列表
- # ip '127.0.0.1'是本地回环地址,只能自己玩
- # 如果是别的IP地址,可以在同一个局域网里一起使用
- server.bind(('127.0.0.1',8080))
-
-
- # 监听 半连接池为5,相当于队列中最多有5个, 超出5个就会报错
- # 同时智能服务一个人
- server.listen(5)
-
-
- # 等待客户端的链接
- # socket 是连接对象, address是客户端地址
- # 以后这个服务端和客户端使用sock这个连接对象
- sock,address = server.accept()
-
-
-
- # 接收客户端发了的数据
- # 传入的参数为一次接受的字节
- data = sock.recv(1024)
-
- print(f'收到了来自{address}的数据{data.decode("utf8")}')
-
-
- # 服务端给客户端发送消息
- data_send = f'收到了你发送的{data}'.encode('utf8')
- # 注意必须传送二进制格式数据
- sock.send(data_send)
-
- time.sleep(2)
-
- # 关闭连接对象,此时服务器没有关闭
- sock.close()
-
- # 关闭服务端
- server.close()
-
-
- import socket
-
-
- client = socket.socket()
-
-
- # 链接服务端的地址加端口
- client.connect(('127.0.0.1,8080))
- # 连上以后就可以发送消息了
- client.send(b'good evening')
- # 收到服务端返回的消息
- data = client.recv(1024)
- print(data.decode('utf8'))
- client.close()
只加入连接循环, 此时服务端会一直接收消息看相当于死循环, 服务器不会自动断开
服务端
-
-
-
- import socket
-
- server = socket.socket()
-
- server.bind(('127.0.0.1', 8008))
-
- server.listen(5)
- print('等待客户端的链接')
-
- # 等待客户端的链接
- # 链接循环
- while True:
- sock, addr = server.accept()
-
-
- # 接受客户端发了的数据
- data = sock.recv(1024)
-
- print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
-
-
- # 服务端给客户端发送消息
- data_send = f'收到了你发送的 {data}'.encode('utf-8')
- # 注意:必须传送二进制的格式
- sock.send(data_send)
-
-
- # 关闭连接对象,此时服务端并没有关闭!!!
- sock.close()
-
- # 关闭服务端
- server.close()
客户端
-
- import socket
-
- client = socket.socket()
-
- # 连接服务端的地址加端口
- client.connect(('127.0.0.1', 8008))
-
- # 连上以后就可以发送消息了
- client.send(b'good evening')
-
- # 收到服务端返回的消息
- data = client.recv(1024)
- print(data.decode('utf-8'))
-
- client.close()
加入通信循环,此时客户端可以多次发送数据
服务端
-
-
- import socket
-
- server = socket.socket()
- server.bind(('127.0.0.1', 8008))
- server.listen(5)
- print('等待客户端的链接')
-
- while True:
- sock, addr = server.accept()
- print(f'客户端{addr}已连接')
- # 等待接受客户端发的数据
- # 如果客户端没法,就会一直等待下去
- while True:
- try:
- # 同一个客户端,不停地发送
- data = sock.recv(1024)
- # 当客户端主动断开后,客户端会发送一个空的数据过来
- # 此时服务端必须对这种情况进行处理,否则会出现死循环
- if len(data) == 0:
- print(f'客户端{addr}已断开', '\n')
- break
-
- print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
- data_send = f'收到了你发送的 {data}'.encode('utf-8')
- sock.send(data_send)
- except Exception as e:
- print(e)
- break
-
- sock.close()
-
- # 关闭服务端
- server.close()
客户端
-
- import socket
-
- client = socket.socket()
- client.connect(('127.0.0.1', 8008))
- # 连上以后就可以发送消息了
-
- while True:
- data = input('输入发送给服务端的消息(q to quit):')
- if data == 'q': break
- client.send(data.encode("utf-8"))
-
- data = client.recv(1024)
- print(data.decode('utf-8'))
-
- client.close()
-
-
在这里是开启多进程完成的并发,可以换成多线程, 只要将Process类换成Thread类
- import socket
- from multiprocessing import Process
-
- def talk(sock,addr):
- print('客户端连接成功', addr)
- while True:
- try:
- data = sock.recv(1024)
- if len(data) == 0:
- print(f'客户端{addr}已断开', '\n')
- break
-
- print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
- data_send = f'收到了你发送的 {data}'.encode('utf-8')
- sock.send(data_send)
- except Exception as e:
- print(e)
- break
- sock.close()
-
-
- if __name__ == '__main__':
- server = socket.socket()
- server.bind(('127.0.0.1', 81))
- server.listen(5)
- while True:
- sock,addr = server.accept()
- print(f'客户端{addr}已连接')
- # 连接上一个客户端之后开启一个进程,对该客户端进行服务
- p = Process(target=talk, args=(sock, addr))
- p.start()
-
- server.close()
1.发送消息不能为空 >>>> 统计长度饼判断即可
2. 反复重启服务端可能会报错 >>> address in use
这个错在苹果本上会比较频繁出现,windows频率较少
from socket import SOL_SOCKET,SO_REUSEADDR
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加
3. 连接循环
如果是Windows电脑 客户端异常推出之后服务端会直接报错
处理方式 >>> 异常处理
如果是mac或者linux服务端会接收到一个空消息
处理方式 >>> len判断
客户端如果异常断开,服务端代码应该重新回到accept等待新的客人
目前的服务端只能实现一次服务一个人 不能做到同时服务多人, [并发状态下可以实现]
4. 加入通信循环之后,想要同一个py文件启动多个客户端, pycharm会出现以下提示,不允许启动多个
解决办法
- listen(5)
-
- # py文件默认同一时间只能运行一次
-
- # 半连接池
- # 设置最大等待人数 >>> 节省资源 提高CPU利用率
- data1 = conn.recv(1024)
- print(data1)
- data2 = conn.recv(1024)
- print(data2)
- data3 = conn.recv(1024)
- print(data3)
-
- client.send(b'hello')
- client.send(b'jason')
- client.send(b'kevin')
- """
- 三次打印的结果
- b'hellojasonkevin'
- b''
- b''
- """
-
会将数据量比较小 并且时间间隔比较短的数据整合到一起发送
并且还会受制于recv()内数字的大小,此现象我们称之为''六十协议''
>>> 问题产生的原因是因为recv括号内我们不知道即将要接收的数据到底有多大, 如果每次接受的收据我们都能够精准的知道他的大小, 那么肯定不会有粘包问题出现<<<<
如果我们能知道即将要接收的数据量大小那么粘包问题就解决了~
-
-
- # struct模块
- import struct
-
- data1 = 'hello world!'
- print(len(data1)) # 12
- res1 = struct.pack('i', len(data1)) # 第一个参数是格式 写i就可以了
- print(len(res1)) # 4
- ret1 = struct.unpack('i', res1)
- print(ret1) # (12,)
-
-
- data2 = 'hello baby baby baby baby baby baby baby baby'
- print(len(data2)) # 45
- res2 = struct.pack('i', len(data2))
- print(len(res2)) # 4
- ret2 = struct.unpack('i', res2)
- print(ret2) # (45,)
-
-
-
pack可以讲任意长度的数字打包成固定长度
unpack可以将固定长度的数字解包成打包之前的数据真实长度
>>> 将真实数据打包成固定长度的包
>>> 将固定长度的包发送给对方
>>> 对方接收到包之后再解包获取真实数据长度
>>> 接受真是长度的数据
<<<<<<< 1.先接收固定长度的报头
2.再根据报头解压出真实长度
3.根据真实长度接收即可<<<<<<<<<<<<<<
struct模块针对数据量特别大的数字没有办法打包
服务端
- # 服务端
- import socket
- server = socket.socket(type=socket.SOCK_DGRAM)
- server.bind(('127.0.0.1', 8080))
- msg, address = server.recvfrom(1024)
- print('msg>>>:%s' % msg.decode('utf8'))
- print('address>>>:',address)
- server.sendto('我是服务端 你好啊'.encode('utf8'), address)
客户端
- # 客户端
- import socket
- client = socket.socket(type=socket.SOCK_DGRAM)
- server_address = ('127.0.0.1', 8080)
- client.sendto('我是客户端 想我了没'.encode('utf8'), server_address)
- msg, address = client.recvfrom(1024)
- print('msg>>>:%s' % msg.decode('utf8'))
- print('address>>>:',address)
UDP不存在粘包问题, UDP多用于短消息交互,如QQ