本节内容:

  一、网络基础知识

  二、socket概念及相关语法

    2.1socket概念
    2.2socket解释
    2.3socket模块功能介绍
    2.4socket粘包问题
    2.5Socket多并发

一、网络基础知识

1、OSI七层模型(具体自己百度)

2、TCP/IP四层参考模型(具体自己百度)

他们的对应网络协议如下:协议

今天我们说的socket就在传输层:TCP/IP三次握手建立连接

 

客户/服务器架构

服务器是一个软件或硬件,用于提供客户需要的“服务”。服务器存在的唯一目的就是等待客户的请求,给这些客户服务,然后再等待其它的请求。另一方面,客户连上一个(预先已知的)服务器,提出自己的请求, 发送必要的数据,然后就等待服务器的完成请求或说明失败原因的反馈。服务器不停地处理外来的请求,而客户一次只能提出一个服务的请求,等待结果。然后结束这个事务。客户之后也可以再提出其它的请求,只是,这个请求会被视为另一个不同的事务了。

二、socket概念及相关语法

socket是TCP/IP中传输层中TCP、UDP的实现方式,用socket编程,可以实现TCP UDP的通信。有一个比较好的比喻方式:socket就是一条管子,连接两段,而TCP、UDP就是管子中的东西

2.1socket概念

socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。

建立一个socket必须至少有2端, 一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求, 连接建立之后,双方可以互发数据

来看个socket的传递模式:

2.2socket解释

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

socket和file的区别:

  • file模块是针对某个指定文件进行【打开】【读写】【关闭】
  • socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】

我们来看一个简单的socket通信程序:

 #服务器端

 import socket
server = socket.socket()
server.bind(('localhost',6969)) #绑定要监听端口
server.listen(5) #监听 print("我要开始等电话了")
while True:
conn, addr = server.accept() # 等电话打进来
# conn就是客户端连过来而在服务器端为其生成的一个连接实例
print(conn, addr)
print("电话来了")
count = 0
while True:
data = conn.recv(1024)
print("recv:",data)
if not data:
print("client has lost...")
break
conn.send(data.upper())
count+=1
if count >10:break server.close()

服务器端

 #客户端
import socket client = socket.socket() #声明socket类型,同时生成socket连接对象
client.connect(('localhost',6969)) while True:
msg = input(">>:").strip()
if len(msg) == 0:continue
client.send(msg.encode("utf-8"))
data = client.recv(1024)
print("recv:",data.decode()) client.close()

客户端

 import socket

 ip_port = ('127.0.0.1',9999)

 sk = socket.socket()
sk.bind(ip_port)
sk.listen(5) while True:
print 'server waiting...'
conn,addr = sk.accept() client_data = conn.recv(1024)
print client_data
conn.sendall('不要回答,不要回答,不要回答') conn.close()

socket server

 #!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
ip_port = ('127.0.0.1',9999) sk = socket.socket()
sk.connect(ip_port) sk.sendall('请求占领地球') server_reply = sk.recv(1024)
print server_reply sk.close()

socket client

server端:

 import socket

 ip_port=('127.0.0.1',9008)  

 s=socket.socket()   #创建对象

 s.bind(ip_port)     #bind一个IP地址和短裤哦

 s.listen(2)         #开始监听

 while True:
print('server waiting...')
conn,addr=s.accept() #接受连接并返回(conn,addr),其中conn是新的套接字对象,用来接受也发送数据,addr是连接客户端的地址,此时是阻塞状态 client_data=conn.recv(1024) #接受套接字的数据,1024是最多可以接受的数量
print(client_data.decode().upper()) #bytes格式可以使用decode()解码 conn.sendall(bytes('fuck XXXXX',encoding='utf8')) #发送数据,注意python3中发送必须是bytes格式的 conn.close() #关闭套接字

client端:

 import socket
ip_port=('127.0.0.1',9008) sk=socket.socket() #创建套接字对象
sk.connect(ip_port) #客户端直接连接IP和端口
sk.sendall(bytes('hello,world!!!',encoding='utf8')) #发送数据,必须转换为字节再发送
rep=sk.recv(1024) #阻塞状态,接受数据,大小为1024
print(rep.decode().upper()) #打印,并解码,将结果大写
sk.close() #关闭套接字

可以看出来,客户端只需创建类,连接,直接发送接收即可;而服务端需要创建对象,绑定IP端口,监听,开始接受并返回,并且接受到的内容为(新的套接字对象,客户端地址),进行数据交换的是新的套接字对象,并不是刚开始创建的套接字对象,切记,切记!!!!

2.3socket模块功能介绍

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

参数一解释:socket.AF_INET:地址簇

socket.AF_INET IPv4(默认)

socket.AF_INET6 IPv6

socket.AF_UNIX 只能够用于单一的Unix系统进程间通信

参数二解释:socket.SOCK_STREAM:类型

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_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。

socket.SOCK_SEQPACKET 可靠的连续数据包服务

参数三解释:0:协议

0  (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议

 import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port) while True:
data = sk.recv(1024)
print data import socket
ip_port = ('127.0.0.1',9999) sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
inp = raw_input('数据:').strip()
if inp == 'exit':
break
sk.sendto(inp,ip_port) sk.close()

UDP Demo

更多

sk.bind(address)

  s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)

  开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。

backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
      这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)

  是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()

  接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。

  接收TCP 客户的连接(阻塞式)等待连接的到来

sk.connect(address)

  连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

sk.connect_ex(address)

  同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

sk.close()

  关闭套接字

sk.recv(bufsize[,flag])

  接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])

  与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

sk.send(string[,flag])

  将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

sk.sendall(string[,flag])

  将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

内部通过递归调用send,将所有内容发送出去。

sk.sendto(string[,flag],address)

  将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

sk.settimeout(timeout)

  设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )

sk.getpeername()

  返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

sk.getsockname()

  返回套接字自己的地址。通常是一个元组(ipaddr,port)

sk.fileno()

  套接字的文件描述符

socket.sendfile(fileoffset=0count=None)

     发送文件 ,但目前多数情况下并用不到。

实例:socket中的 udp demo

 # 服务端
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port) while True:
data,(host,port) = sk.recvfrom(1024)
print(data,host,port)
sk.sendto(bytes('ok', encoding='utf-8'), (host,port)) #客户端
import socket
ip_port = ('127.0.0.1',9999) sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
inp = input('数据:').strip()
if inp == 'exit':
break
sk.sendto(bytes(inp, encoding='utf-8'),ip_port)
data = sk.recvfrom(1024)
print(data) sk.close() UDP

UDP demo

实例:智能机器人

 #!/usr/bin/env python
# -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',8888)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5) while True:
conn,address = sk.accept()
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
Flag = True
while Flag:
data = conn.recv(1024)
if data == 'exit':
Flag = False
elif data == '':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')
conn.close()

服务端

 #!/usr/bin/env python
# -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',8005)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5) while True:
data = sk.recv(1024)
print 'receive:',data
inp = raw_input('please input:')
sk.sendall(inp)
if inp == 'exit':
break sk.close()

客户端

2.4socket粘包问题

先看一个列子

 import socket,os

 server = socket.socket()  #创建套接字
server.bind(("localhost",9999)) #绑定
server.listen(5)
print("等电话来")
while True:
print("电话来了")
conn,addr = server.accept()
print("new conn:",addr)
while True:
print("等待新指令")
data = conn.recv(1024)
if not data:
print("客户端已断开")
break
print("执行指令:",data)
cmd_res = os.popen(data.decode()).read() #接受字符串,执行结果也是字符串
print("before send",len(cmd_res))
if len(cmd_res)==0:
cmd_res = 'cmd has no output......'
conn.send(str(len(cmd_res)).encode("utf-8")) #先发大小给客户端
#time.sleep(0.5)
client_ack = conn.recv(1024) # wait client to confirm
print("ack from client:",client_ack)
conn.send(cmd_res.encode("utf-8"))
print("send done") server.close()

server端

 #!/usr/bin/env python
# -*-coding=utf-8-*-
# Auther:pfjiand Mail:18565800581@163.com Blog:https://blog.csdn.net/weixin_38384902 import socket client = socket.socket() client.connect(("localhost",9999)) while True:
cmd = input(">>:").strip()
if len(cmd)==0:
continue
client.send(cmd.encode("utf=8"))
cmd_res_size = client.recv(1024) #接受命令结束的长度
print("命令结果大小:",cmd_res_size)
client.send("准备好了接收了,loser可以发了".encode("utf-8")) #发一个消息 received_size = 0
received_data =b''
while received_size < int(cmd_res_size.decode()):
data = client.recv(1024)
received_size += len(data) #每次收到的有可能小于1024,所有必须用len判断
print(data.decode())
received_data += data else:
print("cmd res receive done....",received_size)
print(received_data.decode()) client.close()

client

sk.recv(1024)中,bufsize值为1024,最多只能接受1024个字节,那么如果client端发送的数据包特别大时,超过了指定的bufsize的值,超过的不分会留在内核缓冲区中,下次调用recv的时候会继续读剩余的字节。这就是所谓的粘包问题,那么怎么解决呢?

类似于http协议,我们可以:

  1. 在发送之前先告诉接受数据端我要发送数据的字节大小
  2. 接收数据端收到数据后回复给数据发送端一个确认消息
  3. 数据发送端收到确认信息后,发送数据
  4. 数据接收端循环接受数据,直到数据接受完成,收到完整数据包

我们可以来看个例子,模拟一个shell连接的方式:

client端:

 import socket
ip_port=('127.0.0.1',9999) s=socket.socket() s.connect(ip_port) while True:
send_data=input('>>:').strip() if send_data=='exit':break
if len(send_data)==0:continue
s.send(bytes(send_data,encoding='utf8')) ready_tag=s.recv(1024)
ready_tag=str(ready_tag,encoding='utf8')
if ready_tag.startswith('Ready'):
msg_size=int(ready_tag.split('|')[-1])
print(msg_size)
start_tag='Start'
s.send(bytes(start_tag,encoding='utf8')) recv_size=0
recv_msg=b'' while recv_size < msg_size:
recv_data=s.recv(1024)
recv_msg+=recv_data
recv_size+=len(recv_data) print(str(recv_msg,encoding='utf8')) s.close()

client端

 import socket,subprocess
ip_port=('127.0.0.1',9999) s=socket.socket() s.bind(ip_port) s.listen(5) while True:
conn,addr=s.accept()
while True:
try:
recv_data=conn.recv(1024)
if recv_data==0:break
p=subprocess.Popen(str(recv_data,encoding='utf8'),shell=True,stdout=subprocess.PIPE)
res=p.stdout.read()
if len(res)==0:
send_data='cmd ERROR'
send_data=bytes(send_data,encoding='utf8')
else:
send_data=res
ready_tag='Ready|%s'%len(send_data)
conn.send(bytes(ready_tag,encoding='utf8')) feedback=conn.recv(1024)
feedback=str(feedback,encoding='utf8') if feedback.startswith('Start'):
conn.send(send_data) except Exception as ex:
break conn.close()

server端


2.5Socket多并发

多线程的socket--socketserver模块

server端:

 import socketserver

 class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
while True:
try:
self.data = self.request.recv(1024).strip()
print("{}wrote:".format(self.client_address[0]))
print(self.data)
self.request.send(self.data.upper())
except ConnectionResetError as e:
print("error",e)
break if __name__=="__main__":
HOST,PORT = "localhost",9999
server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler) #线程,多并发
#server = socketserver.ForkingTCPServer((HOST,PORT),MyTCPHandler) #windows上不能使用,linux可以 server.serve_forever()

client端:

 import socket

 client = socket.socket()
client.connect(("localhost",9999))
while True:
msg = input('>>:').strip()
if len(msg)==0:
continue
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print("recv:",data.decode())
print(type(data)) client.close()

仔细来看下server端的code:

  1. 创建一个派生类,其基类为socketserver.BaseRequestHandler
  2. 类中必须定义一个名称为 handle 的方法
  3. 启动ThreadingTCPServer时,先需要创建一个ThreadingTCPServer的实例,需要两个参数,①元组数据(IP,port),②创建的派生类
  4. 启动为调用ThreadingTCPServer类中的serve_forever方法,永久启动

为什么必须定义一个handle的方法呢?

先看看他的父类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. 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 arbitrary other instance variariables. """ def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish() def setup(self):
pass def handle(self):
pass def finish(self):
pass

源码

ThreadingTCPServer源码剖析

类的基类关系图如下:

python学习笔记08-- socket编程的更多相关文章

  1. Python Web学习笔记之socket编程

    Python 提供了两个基本的 socket 模块. 第一个是 Socket,它提供了标准的 BSD Sockets API. 第二个是 SocketServer, 它提供了服务器中心类,可以简化网络 ...

  2. python学习笔记之socket(第七天)

         参考文档:              1.金角大王博客:http://www.cnblogs.com/alex3714/articles/5227251.html               ...

  3. 网络编程学习笔记:Socket编程

    文的主要内容如下: 1.网络中进程之间如何通信? 2.Socket是什么? 3.socket的基本操作 3.1.socket()函数 3.2.bind()函数 3.3.listen().connect ...

  4. Python学习笔记6 函数式编程_20170619

    廖雪峰python3学习笔记: # 高阶函数 将函数作为参数传入,这样的函数就是高阶函数(有点像C++的函数指针) def add(x, y): return x+y def mins(x, y): ...

  5. python学习笔记11 ----网络编程

    网络编程 网络编程需要知道的概念 网络体系结构就是使用这些用不同媒介连接起来的不同设备和网络系统在不同的应用环境下实现互操作性,并满足各种业务需求的一种粘合剂.网络体系结构解决互质性问题彩是分层方法. ...

  6. IOS学习笔记之 Socket 编程

    最近开始静心学习IOS编程,虽然起步有点晚,但有句话说的好:“如果想去做,任何时候都不晚”.所以在今天,开始好好学习IOS.(本人之前4年都是搞.Net的,java也培训过一年) 打算学IOS,从哪入 ...

  7. python学习笔记10 ----网络编程

    网络编程 网络编程需要知道的概念 网络体系结构就是使用这些用不同媒介连接起来的不同设备和网络系统在不同的应用环境下实现互操作性,并满足各种业务需求的一种粘合剂.网络体系结构解决互质性问题彩是分层方法. ...

  8. python 学习笔记7 面向对象编程

    一.概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发"更快更好更强..." ...

  9. python学习笔记 - 初识socket

    socket socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. sock ...

  10. python学习笔记(Tkinter编程利用Treeview实现表格自动更新)

    博主今天总结这段时间抽空写的一个GUI编程项目 功能是查看本地打印机队列,可选择指定队列重新打印 直接上图 UI设计包括3个区域左上方,右上方和下方列表区域 使用网格grid方法来分配位置 下面是界面 ...

随机推荐

  1. DOS常用命令详解

    DOS常用命令详解 dir 列文件名 deltree 删除目录树 cls 清屏 cd 改变当前目录 copy 拷贝文件 diskcopy 复制磁盘 del 删除文件 format 格式化磁盘 edit ...

  2. vue-admin-template模板添加tagsview

    参考: https://github.com/PanJiaChen/vue-admin-template/issues/349 一.从vue-element-admin复制文件: vue-admin- ...

  3. 从GoogLeNet至Inception v3

    从GoogLeNet至Inception v3 一.CNN发展纵览 我们先来看一张图片: 1985年,Rumelhart和Hinton等人提出了后向传播(Back Propagation,BP)算法( ...

  4. 政务私有云盘系统建设的工具 – Mobox私有云盘

    序言 这几年,智慧政务已经成为了政府行业IT建设发展的重要进程.传统办公方式信息传递速度慢.共享程度低.查询利用难,早已成为政府机关获取和利用信息的严重制约因素.建立文档分享共用机制,加强数据整合,避 ...

  5. lc13 Roman to Integer

    lc13 Roman to Integer 遇到那六种特殊情况分别-2,-20,-200, 按照罗马数字的规则,每种只可能出现一次.所以只需要考虑一次,用indexOf()即可判断是否出现这几种特殊情 ...

  6. session中load()跟get()的区别

    1.相同点:Session.load/get方法均可以根据指定的实体类和id从数据库读取记录,并返回与之对应的实体对象. 2.区别在于: (1)如果未能发现符合条件的记录,get方法返回null,而l ...

  7. vue.js_06_vue.js的自定义指令和自定义键盘修饰符

    1.全局的自定义指令 实现:当页面刷新时,光标聚焦到搜索框中 <label> 搜索: <input type="text" class="form-co ...

  8. python 日记 day4

    1.为何数据要分类 数据是用来表示状态的,不同的状态应该用不同类型的数据来表示. 2.数据类型 数字 字符串 列表 元组 字典 集合 列表:列表相比于字符串,不仅可以储存不同的数据类型,而且可以储存大 ...

  9. IO流 输入和输出文档内容

    package io; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io. ...

  10. springboot 集成eureka 超详细配置

    撸了今年阿里.头条和美团的面试,我有一个重要发现.......>>> 原文链接: https://blog.csdn.net/nanbiebao6522/article/detail ...