socket套接字简介

由于操作OSI七层是所有C/S架构的程序都需要经历的过程,而操作OSI七层相当的复杂,所以这时候就出现了一门技术——socket套接字。

socket套接字可以向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯,而python语言提供了socket模块来使用这门技术。

socket模块

C/S架构的软件无论是在编写还是运行,都应该先考虑服务端,所以我们先编写服务端的代码。

服务端(Server)

import socket
# 创建套接字对象,相当于买手机
server = socket.socket()
# 将ip地址和端口号绑定到套接字,相当于插电话卡
server.bind(('127.0.0.1', 8080))
# 监听,后面详细讲解,相当于开机
server.listen(5) # 等待客户端的消息,获取客户端的对象和地址,相当于等待并接听电话
sock, addr = server.accept() # 没有消息来就原地等待(程序阻塞)
# 获取客户端的消息
data = sock.recv(1024)
# 获取的消息是bytes类型,需要解码
print(data.decode('utf8'))
# 给客户端发消息,需要转成bytes类型
sock.send('来自服务端的消息'.encode('utf8'))
# 断开与客户端的连接,相当于挂电话
sock.close()
# 关闭服务端,相当于电话关机
server.close()

客户端(Client)

import socket
# 产生一个socket对象
client = socket.socket()
# 根据服务端的地址和端口连接
client.connect(('127.0.0.1', 8080))
# 给服务端发消息
client.send('来自客户端的消息'.encode('utf8'))
# 接收来自服务端的消息
data = client.recv(1024)
# 解码并输出
print(data.decode('utf8'))
# 关闭客户端
client.close()

服务端与客户端首次交互,一边是recv那么另一边必须是send,两边不能相同,否则两边都在等待对方发来的消息,程序就卡住了。

通信循环

上面的代码已启动就结束了,无法让服务端一直运行,为了能让服务端和客户端一直可以互相发送消息,我们可以用循环的方式实现服务端和客户端一直可以交互,可以互相发消息。

服务端(Server)

import socket
# 服务端启动
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
# 建立与客户端连接
sock, addr = server.accept()
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
msg = input('需要发送给客户端的消息:').strip()
sock.send(msg.encode('utf8'))
# 断开连接
sock.close()
server.close()

客户端(Client)

import socket
# 客户端建立连接
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
# 与服务端交互
msg = input('需要发送给服务端的消息:').strip()
client.send(msg.encode('utf8'))
data = client.recv(1024)
print(data.decode('utf8'))
# 断开连接
client.close()

代码优化

在实现了通信循环后,还是有很多小问题,比如当服务端或者客户端发送的消息为空时,程序会卡住,无法获取空的数据。

解决方法:加一个判断条件判断输入的数据是否为空。

# 客户端
msg = input('需要发送给服务端的消息:').strip()
if len(msg) == 0:
print('不能发送空消息')
continue
# 服务端
msg = input('需要发送给客户端的消息:').strip()
if len(msg) == 0:
msg = '服务端给你发送了空消息'

有些时候重启服务端可能会报错:Address already in use

解决方法:在服务端的bind方法前加一串代码

from socket import SOL_SOCKET,SO_REUSEADDR
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加

连接循环

在windows系统中,如果客户端异常退出,那么服务端会引起报错,所以我们要让代码可以在客户端异常退出后可以重新回到accept等待新的客户端,这里可以使用异常处理的方法。

服务端(Server)

import socket
# 服务端启动
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
# 建立与客户端连接
sock, addr = server.accept()
while True:
try:
data = sock.recv(1024)
print(data.decode('utf8'))
msg = input('需要发送给客户端的消息:').strip()
if len(msg) == 0:
msg = '服务端给你发送了空消息'
sock.send(msg.encode('utf8'))
except ConnectionResetError:
# 重新建立与客户端连接
sock, addr = server.accept()
# 断开连接
sock.close()
server.close()

客户端(Client)

import socket
# 客户端建立连接
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
# 与服务端交互
msg = input('需要发送给服务端的消息:').strip()
if len(msg) == 0:
print('不能发送空消息')
continue
client.send(msg.encode('utf8'))
data = client.recv(1024)
print(data.decode('utf8'))
# 断开连接
client.close()

PS:目前我们的服务端只能实现一个服务端对应一个客户端,不能做到一个服务端对应多个客户端,这个功能需要学了并发编程才可以实现。

半连接池

在创建服务端的时候,我们需要建立半连接池,server.listen()这个方法就是建立半连接池的。

半连接池的作用就是设置的最大等待的客户端的数量,可以有效节省资源,提高效率。listen(5)就是可以让最多有5个客户端进行等待。



与当前客户端断开连接后,就会去等待区与下一个客户端连接。

黏包问题

我们先来看一段代码:

服务端(Server)

import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5) sock, addr = server.accept() data1 = sock.recv(1024)
print(data1.decode('utf8'))
data2 = sock.recv(1024)
print(data2.decode('utf8'))
data3 = sock.recv(1024)
print(data3.decode('utf8')) sock.close()
server.close()

客户端(Client)

import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080)) client.send(b'one')
client.send(b'two')
client.send(b'three') client.close()

首先启动服务端,然后启动客户端,按照之前的理解,服务端应该是输出三段数据,但是并不是,而是把三段数据合在第一个send一起发送了,后面两个send发送的是空字符。

服务端输出内容:

b'onetwothree'
b''
b''

这个就是黏包问题!因为TCP协议的特点:会将数据量比较小并且时间间隔比较短的数据整合到一起发送,并且还会受制于recv括号内的数字大小。

我们可以更改服务端的recv括号内的大小来防止黏包问题:

data1 = sock.recv(3)
data2 = sock.recv(3)
data3 = sock.recv(5)

但这只能在我们知道发送的数据大小才能这样使用,如果我们不知道即将要接收的数据到底多大呢?

解决黏包问题

解决黏包问题,我们可以使用python中的struct模块,这个模块可以把长度任意的数据打包成固定长度的数据。

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 world world world '
print(len(data2)) # 24
# 数据打包
res2 = struct.pack('i', len(data2))
print(len(res2)) # 4
# 数据解包
ret2 = struct.unpack('i', res2)
# 返回的是元组
print(ret2) # (24,)

结合C/S架构:

服务端(Server)

import socket
import struct
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5) sock, addr = server.accept()
# 先获取打包的数据
msg1 = sock.recv(4)
# 解包获取真实数据长度
data1_len = struct.unpack('i', msg1)[0]
# 在获取真实数据
data1 = sock.recv(data1_len)
print(data1) # 获取第二段数据
msg2 = sock.recv(4)
data2_len = struct.unpack('i', msg2)[0]
data2 = sock.recv(data2_len)
print(data2) # 获取第三段数据
msg3 = sock.recv(4)
data3_len = struct.unpack('i', msg3)[0]
data3 = sock.recv(data3_len)
print(data3) sock.close()
server.close()

客户端(Client)

import socket
import struct client = socket.socket()
client.connect(('127.0.0.1', 8080)) data1 = b'one'
data2 = b'two'
data3 = b'three' # 数据打包
msg1 = struct.pack('i', len(data1))
# 先发送打包好的数据,服务端解包获取长度
client.send(msg1)
# 在发送真实的数据
client.send(data1) msg2 = struct.pack('i', len(data2))
client.send(msg2)
client.send(data2) msg3 = struct.pack('i', len(data3))
client.send(msg3)
client.send(data3) client.close()

黏包问题特殊情况(文件过大)

recv括号内的数字尽量不要写太大,1024、2048、4096足够了,如果要发送的数据大小过大,我们可以使用字典的方式。

1.先接收固定长度的字典包
2.解析出字典的真实长度
3.接收字典数据
4.从字典数据中解析出各种信息
5.接收真实的数据

比如客户端给服务端传输文件的信息:

服务端(Server)

import socket
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5) sock, addr = server.accept()
# 获取打包的数据
data_json_pack = sock.recv(4)
# 解包
data_json_len = struct.unpack('i', data_json_pack)[0]
# 获取json数据
data_json = sock.recv(data_json_len)
# json转字典
data_dict = json.loads(data_json)
print(data_dict) # 接收文件
size = 0
while size < data_dict['file_size']:
data = sock.recv(1024)
print(data.decode('utf8'))
size += len(data) sock.close()
server.close()

客户端(Client)

import socket
import os
import struct
import json client = socket.socket()
client.connect(('127.0.0.1', 8080)) data_dict = {
'file_name': r'main.py', # 文件名
'file_size': os.path.getsize(r'main.py') # 文件大小
}
# 字典转json
data_json = json.dumps(data_dict)
# 打包json,并发送
data_json_pack = struct.pack('i', len(data_json))
client.send(data_json_pack)
# 发送json数据
client.send(data_json.encode('utf8')) # 发送文件
with open(data_dict['file_name'], 'rb') as f:
for line in f:
client.send(line) client.close()

网络编程之socket套接字的更多相关文章

  1. [网络编程之Socket套接字介绍,套接字工作流程,基于TCP协议的套接字程序]

    [网络编程之Socket套接字介绍,套接字工作流程,基于TCP协议的套接字程序] 为何学习socket套接字一定要先学习互联网协议: 1.首先:要想开发一款自己的C/S架构软件,就必须掌握socket ...

  2. java 25 - 3 网络编程之 Socket套接字

    Socket Socket套接字: 网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字. Socket原理机制: 通信的两端都有Socket. 网络通信其实就是Socket ...

  3. Python网络编程之TCP套接字简单用法示例

    Python网络编程之TCP套接字简单用法示例 本文实例讲述了Python网络编程之TCP套接字简单用法.分享给大家供大家参考,具体如下: 上学期学的计算机网络,因为之前还未学习python,而jav ...

  4. 网络编程之socket

    网络编程之socket socket:在网络编程中的一个基本组件,也称套接字. 一个套接字就是socket模块中的socket类的一个实例. 套接字包括两个: 服务器套接字和客户机套接字 套接字的实例 ...

  5. 网络编程之Socket & ServerSocket

    网络编程之Socket & ServerSocket Socket:网络套接字,网络插座,建立网络通信连接至少要一对端口号(socket).socket本质是编程接口(API),对TCP/IP ...

  6. GO语言的进阶之路-网络编程之socket

    GO语言的进阶之路-网络编程之socket 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是socket; 在说socket之前,我们要对两个概念要有所了解,就是IP和端口 ...

  7. [深入浅出Cocoa]iOS网络编程之Socket

    http://blog.csdn.net/kesalin/article/details/8798039 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[+]   [深入浅出Co ...

  8. 网络编程之Socket代码实例

    网络编程之Socket代码实例 一.基本Socket例子 Server端: # Echo server program import socket HOST = '' # Symbolic name ...

  9. 网络编程与socket套接字

    网络编程与socket套接字 传输层 PORT协议 port是一种接口,数据通过它在计算机和其他设备(比如打印机,鼠标,键盘或监视器)之间,网络之间和其他直接连接的计算机之间传递 TCP协议 ​ 传输 ...

随机推荐

  1. 几种常见布局的flex写法

    flex布局目前基本上兼容主流的浏览器,且实现方式简单.我整理了flex的一些知识点,并且总结归纳了几种常见布局的flex写法 ​flex基础知识点 flex-grow和flex-shrink相关计算 ...

  2. 小程序wx.getRecorderManager()录音管理

    小程序中提供了两种录音的API,wx.startRecord和wx.getRecorderManager(),前一个现在微信团队已经不再维护,所以在这里写一下新的录音管理,比之前要强大 1.小程序录音 ...

  3. react 移动端 兼容性问题和一些小细节

    react 移动端 兼容性问题和一些小细节 使用 ES6 的浏览器兼容性问题 react 对低版本的安卓webview 兼容性 iOS下 fixed与软键盘的问题 onClick 阻止冒泡 meta对 ...

  4. java中接口interface有什么用呢?举例!

    接口只有方法的定义,没有方法的任何实现.那这有什么意义呢?马克-to-win: 接口就像一个服务合同.接口只关心必须得干什么而不关心如何去实现它.有 意义吗?有意义.马克-to-win:比如我们的软件 ...

  5. 【Android开发】【数据库】Realm For Android

    目录 1.Realm简介 2.环境配置 3.初始化Realm 4.创建实体 5.增 6.删 7.改 8.查 9.异步操作 10.数据库数据更新监听 11.json转对象,插入数据库 12.Demo地址 ...

  6. 不用关闭重启cad及不用更改快捷方式或者版本号c#调试cad插件

    c#开发的cad插件需要重启cad才能进行调试,然而高版本的cad启动比较慢特别是一些古董电脑,而且cad有重启次数限制.针对不用重启cad调试已经有成熟的方案了,但是需要调试一次修改一次快捷方式或者 ...

  7. MySQL 中继日志

    什么是中继日志从服务器I/O线程将主服务器的二进制日志读取过来记录到从服务器本地文件即relay-log日志中,然后从服务器SQL线程会读取relay-log日志的内容并应用到从服务器,从而使从服务器 ...

  8. Hyperledger Fabric定制联盟链网络工程实践

    总体来看,网络上成体系的可用的 Fabric 教程极少--不是直接在 Fabric 官网复制内容大谈基础理论就是在描述一个几乎无法复现的项目实践,以至于学习 Fabric 的效率极低,印象最深刻的就是 ...

  9. 【直播回顾】OpenHarmony知识赋能第四期第四课——音频驱动开发

    3月31日晚上19点,知识赋能第四期直播的第四节,也是本期最后一节直播课​<OpenHarmony标准系统HDF框架之音频驱动开发>​,在OpenHarmony开发者成长计划社群内成功举办 ...

  10. 背包问题dp的初步总结

    背包问题 01背包 给定的物体只有0个和1个,只有选与不选的划分,其状态转移方程时由i-1行推出,所以第二层循环是由j=m,递减到v[i]的. for(int i=1;i<=n;i++){ fo ...