码迷,mamicode.com
首页 > 其他好文 > 详细

10.网络编程之socket

时间:2020-07-19 23:23:40      阅读:63      评论:0      收藏:0      [点我收藏+]

标签:and   inux   多个   iges   err   digest   div   pytho   eve   

一、什么是socket?

1.1 套接字简介

套接字(socket):最初是应用于计算机两个进程之间的通信。

两种类型的套接字:基于文件的和面向网络的

  • 基于文件的套接字:UNIX套接字,套接字的一个家族,并且拥有一个“家族名字”,AF_UNIX(又名:AF_LOCAL),代表地址家族:UNIX。python用的是AF_UNIX。因为两个进程运行在同一台计算机上,所以,这是基于文件的套接字。
  • 基于网络的套接字:他的家族名字是:AF_INET,代表家族地址:因特网。另一个地址家族AF_INET6用ipv6寻址。

1.2 套接字地址:主机-端口对

? 主机名和端口号类似区号和电话号码的组合,有效的端口号范围为0~~65535(尽管小于1024的端口号预留给了系统)。使用POSIX兼容系统(如:Linux,Mac OS等),可以在/etc/services文件中找到预留端口号的列表。

二、面向连接的套接字和为无连接的套接字

2.1 面向连接的套接字

? 面向连接的套接字:在通信前必须先建立一个链接,也成为虚拟电路或者流套接字。它提供序列化的、可靠的、不可重复的数据交付,没有记录边界,这表示每条消息可以拆分成多个片段,在每条消息片段都能到达的前提下,按照一定顺序组合,最后将完整的消息传递给正在等待的应用程序。

? 这是基于传输控制协议(TCP协议),创建TCP套接字,必须使用SOCKET_STREAM作为套接字类型。

2.2 无连接的套接字

? 这是数据报类型的套接字。这是一种无连接的套接字,无法保证传输过程中的顺序性、可靠性、重复性,且消息是以整体发送的。

? 这是基于用户数据报协议(UDP),创建UDP套接字,必须使用SOCKET_DGRAM作为套接字。

三、python中socket

3.1 socket()模块函数

socket(socket_family,socket_type,protocol=0)
# socket_family是AF_UNIX或AF_INET
# socket_type是SOCKET_STRRAM或SOCKET_DGRAM
# protocol通常省略,默认为0,这是与特定的地址家族相关的协议,如果是0,则系统就会根据地址格式和套接类别,自动选择一个合适的协议。

# 使用from socket import *传入参数不会报错,直接imort socket传入参数会报错,显示没有这个AF_INET这个族,因为这个AF_INET这个值在socket的名称空间里,from socket import *是把所有名字都引入当前的名称空间下

3.2 套接字对象(内置)方法

名称 描述
服务器socket方法
s.bind() 将地址(主机名、端口号对)绑定到套接字上
s.listen() 设置并启动TCP监听器
s.accept() 被动接受TCP客户端的连接,一直等待直到连接到达(阻塞)
客户端socket方法
s.connect() 主动发起TCP连接(阻塞)
s.connect_ex() connext()的扩展版本,以错误码形式返回问题,不是抛出一个异常
普通socket方法
s.recv() 接受TCP消息,recv(1024)不代表一定要收到1024个字节,而是一次最多只能收这么多。(阻塞)
s.recv_into() 接受TCP消息到指定的缓冲区
s.send() 发送TCP消息
s.sendall() 完整的发送TCP消息(本质就是循环调用send,sendall在待发数据量),待发数据量大于缓冲区的剩余空间,数据不丢失,直到调用send发完
s.recvfrom() 接受UDP消息和地址(阻塞)
s.recvfrom_into() 接受UDP消息到指定的缓冲区
s.sendto() 发送UDP消息(需要写消息和地址)
s.close() 关闭套接字
面向阻塞(锁)的socket
s.setblocking() 设置套接字的阻塞或非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 获取阻塞套接字操作超时时间

会造成阻塞的方法,accept,recv,recvfrom,connect

3.3 Socket中的一些参数

listen(n) # n表示允许排队个数,socket允许的最大连接数=服务器正在处理的socket连接数+排队的个数。

send不需要写地址,sendto需要写地址。

四、基于TCP的socket

写在这里,不管是服务器还是客户端,在接收消息时先解码,在发送消息时编码。

4.1 创建TCP服务器

# 伪代码
ss  = scoket()						# 创建服务器套接字对象
ss.bind()									# 套接字与地址绑定
ss.listen()								# 监听连接
inf_loop:									# 服务器无限循环
  sc = ss.accept()				# 接受客户端连接
  comm_loop:							# 通信循环
    cs.recv()/sc.send()		# 对话(接受/发送)
  cs.close()							# 关闭客户端套接字
  ss.close()							# 关闭服务端套接字,一般不用

SocketServer模块是一个以socket为基础的高级套接字模块,支持客户端请求的线程和多进程处理。

4.2 创建TCP客户端

cs = socket()						# 创建客户端套接字
cs.connect()						# 尝试连接服务器
somm_loop:							# 通信循环
  cs.send()/cs.recv()		# 对话(发送/接受)
cs.close()							# 关闭客户端套接字

发送的数据相关的内容组成json,先发json的长度,再发json,json中存了接下来要发送的数据长度,再发数据。

五、基于UDP的socket

5.1 创建UDP服务器

UDP服务器除了等待连接外,不需要像TCP那样做一些额外的工作。

ss = socket()											# 创建服务器套接字
ss.bind()													# 绑定服务器套接字
inf_loop:													# 服务器无限循环
  cs = ss.recvfrom()/ss.sendto()	# 关闭(接受/发送)
ss.close()												# 关闭服务器套接字

OSError: [WinError 10057] 由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)没有提供地址,发送或接收数据的请求没有被接受。解决:socket.socket(type=socket.SOCK_DGRAM)

5.2 创建UDP客户端

cs = socket()								# 创建客户端
comm_loop:									# 通信循环
  cs.sento()/cs.recvfrom()	# 对话(发送/接受)
cs.close()									# 关闭客户端套接字

六、粘包问题

6.1 粘包问题

send()要发送的数据合并成一条发送了,产生原因:发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。还有一个机制是拆包,在发送端,因为受到网卡MTU限制,会将大的超过MTU
限制的数据,进行拆分,拆分成多个小的数据,进行传输,当传输到目标主机
的操作系统层时,会将多个小数据合并成原本的数据。

网络最大带宽限制MTU=1500字节,

导致粘包问题的本质:TCP传输的是流式传输,数据与数据之间没有边界

解决粘包:自定义协议,规定发送数据的字节大小,等于设置边界,即客户端发送一条消息,服务端接收一条消息,前提是服务端知道客户端发送的一条消息的大小,这需要客户端提前告知,需要在正式的发送消息前发送一条类似于验证消息,这条消息必须固定大小,否则服务端不知道这条消息多大,怎么接收,还是有可能发生粘包现象。

  • 发送端:计算要发送的数据的长度,通过struct模块转换为固定长度的4字节,发送4个字节的长度
  • 接收端:接收4个字节,在使用struct.unpack把4个字节转换成数字,这个数字就是要接收数据的长度,再更据长度接收真实的数据就不会发生粘包现象了。

6.2 自定义协议解决粘包问题

# 客户端
# 设置要发送消息的长度
num = str(len(msg))
# 前面补0填充到四个字节,由于是数字不会改变值的大小
ret = num.zfill(4)		# 把ret先发送过去,这个长度是固定的4个字节
sk.send(ret.encode(‘utf-8‘))
# 服务端
# 接收上面的ret
length = conn.recv(4).decode(‘utf-8‘)
# 按照客户端发送过来的长度进行接收
msg = conn.recv(length)

6.3 使用struct模块解决粘包问题

struct模块:

import struct
num = 125478568
# 转换成4个字节
ret = struct.pack(‘i‘, num)
print(ret, len(ret))		# b‘\xa8\xa6z\x07‘ 4
print(struct.unpack(‘i‘, ret))		# 返回的是一个元组(125478568,)

4个字节差不多可以表示1G的大小的数据。

七、SocketServer模块

SocketServer的封装度较高,但是效率比较固定,处理并发的客户端请求,只改变server端的代码,client端的代码不变。

SocketServer请求处理程序是默认行为是接收连接,获取请求,然后关闭连接。因此每次向服务端发送消息时都必须重新创建一个新的套接字。

描述
BaseServer 包含核心服务器功能和mix-in类的钩子:仅用于推导,这样不会创建这个类的实例;可以用TCPServer或UDPServer创建类的实例。
TCPServer/UDPServer 基础网络同步TCP/UDP服务器
BaseRequestHandler 包含处理服务请求的核心功能:仅用于推导,这样不会创建这个类的实例;可以用StreamRequestHandler或DatagramRequestHandler创建类的实例。
StreamRequestHandler/DatagramRequesthandler 实现TCP/UDP服务器的服务处理器

7.1 TCPServer+StreamRequestHandler

  1. StreamRequestHandler类支持像操作文件那样操作输入套接字
  2. 客户端和服务端发送的消息都必须加上回车和换行符 /r/n
  3. SockerServer请求处理程序的默认行为是接收连接、获取请求、关闭连接。
# SocketServer服务端
from socketserver import (TCPServer as TCP, StreamRequestHandler as SRH)
from time import ctime

HOST = ‘127.0.0.1‘
PORT = 9001
ADDR = (HOST, PORT)


class MyRequestHandler(SRH):
    def handle(self):
        # 打印连接的客户端的地址
        print(‘...connected from:‘, self.client_address)
        # SocketServer发送消息一定是\r\n结尾的,由于这里接收到客户端发来的消息就是以\r\n结尾的,所以不需要加
        data = ‘[%s] %s  ‘ %(ctime(), self.rfile.readline().decode((‘utf-8‘)))
        # 发送消息
        self.wfile.write(data.encode(‘utf-8‘))
        print(‘发送完毕‘)


tcpServ = TCP(ADDR, MyRequestHandler)
print(‘waiting for connection...‘)
tcpServ.serve_forever()
# SocketServer客户端
from socket import *
HOST = ‘127.0.0.1‘
PORT = 9001
ADDR = (HOST, PORT)
BUFSIZ = 1024

while True:
    tcpCliSock = socket(AF_INET, SOCK_STREAM)
    tcpCliSock.connect(ADDR)
    data = input(‘>>>‘)
    if not data:
        break
    # SocketServer发送消息一定是\r\n结尾的
    tcpCliSock.send((‘%s \r\n‘%data).encode(‘utf-8‘))
    print(‘发送完毕‘)
    data = tcpCliSock.recv(BUFSIZ)
    if not data:
        break
    print(data.strip().decode(‘utf-8‘))
    tcpCliSock.close()

7.2 TCPServer+BaseRequestHandler

这与TCPServer+StreamRequestHandler的区别是:通过self.request.recv()和self.request.send()两个函数来接受和发送消息而不是self.rfile.readline和self.rfile.write()。

八、补充:基础网络协议

九、例子

9.1 TCP文件上传

# 服务端
import socket
import json
import struct

sk = socket.socket()
sk.bind((‘127.0.0.1‘, 9001))
sk.listen()

conn , addr = sk.accept()

msg_len = conn.recv(4)
# 4个字节,去掉前面的0,拿到字典的长度
dic_len = struct.unpack(‘i‘, msg_len)[0]
# 接收字典并解码
jdic = conn.recv(dic_len).decode(‘utf-8‘)
# json转换成普通字典
dic = json.loads(jdic)
print(dic)

with open(dic[‘filename‘],mode=‘wb‘)as f:
    while dic[‘filesize‘]>0:
        data = conn.recv(1024)
        dic[‘filesize‘]-=len(data)
        f.write(data)

conn.close()
sk.close()
# 客户端
import socket
import json
import struct

sk = socket.socket()
sk.bind((‘127.0.0.1‘, 9001))
sk.listen()

conn , addr = sk.accept()

msg_len = conn.recv(4)
# 4个字节,去掉前面的0,拿到字典的长度
dic_len = struct.unpack(‘i‘, msg_len)[0]
# 接收字典并解码
jdic = conn.recv(dic_len).decode(‘utf-8‘)
# json转换成普通字典
dic = json.loads(jdic)
print(dic)

with open(dic[‘filename‘],mode=‘wb‘)as f:
    while dic[‘filesize‘]>0:
        data = conn.recv(1024)
        dic[‘filesize‘]-=len(data)
        f.write(data)

conn.close()
sk.close()

9.2 验证客户端的合法性

生成随机字符串

import os
# 生成32位的随机字符串
ret = os.urandom(32)
print(ret)
# 服务端
import os
import socket
import hashlib

secret_key = b‘alex_sb‘
sk = socket.socket()
sk.bind((‘127.0.0.1‘,9001))
sk.listen()

conn,addr = sk.accept()
# 创建一个随机的字符串,bytes类型
rand = os.urandom(32)
# 发送随机字符串
conn.send(rand)

# 根据发送的字符串 + secrete key 进行摘要
sha = hashlib.sha1(secret_key)
sha.update(rand)
res = sha.hexdigest()

# 等待接收客户端的摘要结果
res_client = conn.recv(1024).decode(‘utf-8‘)
# 做比对
if res_client == res:
    print(‘是合法的客户端‘)
    # 如果一致,就显示是合法的客户端
    # 并可以继续操作
    conn.send(b‘hello‘)
else:
    conn.close()
    # 如果不一致,应立即关闭连接
# 客户端
import socket
import hashlib

secret_key = b‘alex_sb979‘
sk = socket.socket()
sk.connect((‘127.0.0.1‘,9001))

# 接收客户端发送的随机字符串
rand = sk.recv(32)
# 根据发送的字符串 + secret key 进行摘要
sha = hashlib.sha1(secret_key)
sha.update(rand)
res = sha.hexdigest()
# 摘要结果发送回server端
sk.send(res.encode(‘utf-8‘))
# 继续和server端进行通信
msg = sk.recv(1024)
print(msg)

10.网络编程之socket

标签:and   inux   多个   iges   err   digest   div   pytho   eve   

原文地址:https://www.cnblogs.com/journeyer-xsh/p/13340945.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!