python的socket的学习

一、Socket相关知识

1、socket是什么:

socket是应用层与TCP/IP协议族通信的中间软件抽象层,他是一组接口。在设计模式中,Socket其实就是一个门面模式。

它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

所以,我们无需深入了解tcp/udp协议,Socket已经为我们封装好了,我们只需要遵循Socket的规定去编程,写出程序自然就是遵循tcp/udp标准的。

2、看一张图片帮助理解

3、tcp套接字工作流程

(1)、工作流程图

(2)、python的Socket()模块函数用法

 1 from socket import *
 2
 3 socket(socket_family,socket_type,protocal=0)
 4 socket_family 可以是 AF_UNIX或者AF_INET。socket_type可以是SOCK_STREAM或者SOCK_DGRAM。protocal 一般不填,默认值为0
 5
 6 获取tcp/ip套接字
 7 tcpSock = socket(socket.AF_INET, socket.SOCK_STREAM)
 8
 9 获取udp/ip套接字
10 udpSock = socket(socket.AF_INET, socket.SOCK_DGRAM)
11
12 #############其他套接字函数
13 s=socket(socket_family,socket_type,protocal=0)
14 服务端套接字函数
15
16 s.bind() 绑定(主机,端口)到套接字
17 s.listen() 开始监听
18 s.accept()  被动接受TCP客户端的连接,等待连接的到来
19
20 客户端套接字函数
21 s.connect()  主动初始化tcp服务器连接
22 s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
23
24 公用的套接字函数
25 s.recv()            接收TCP数据
26 s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
27 s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
28 s.recvfrom()        接收UDP数据
29 s.sendto()          发送UDP数据
30 s.getpeername()     连接到当前套接字的远端的地址
31 s.getsockname()     当前套接字的地址
32 s.getsockopt()      返回指定套接字的参数
33 s.setsockopt()      设置指定套接字的参数
34 s.close()           关闭套接字
35
36 面向锁的套接字方法
37 s.setblocking()     设置套接字的阻塞与非阻塞模式
38 s.settimeout()    设置阻塞套接字操作的超时时间
39 s.gettimeout()    得到阻塞套接字操作的超时时间
40
41 面向文件的套接字函数
42 s.fileno()  套接字的文件描述符
43 s.makefile()  创建一个与该套接字相关的文件。

(3)、基于tcp的Socket模块例子

 1 from socket import *
 2
 3 srv_msg = ("127.0.0.1",8000)
 4 bufsize = 1024
 5
 6 sock_server = socket(AF_INET,SOCK_STREAM)
 7 sock_server.bind((srv_msg))
 8 sock_server.listen(5)
 9
10 print("服务端启动")
11 while True:
12     conn, addr = sock_server.accept()
13
14     while True:
15         try:
16             data=conn.recv(bufsize)
17             print("服务端收到了一条消息: %s " % data.decode("utf-8"))
18             data = "服务端说:%s" % data.decode("utf-8")
19             conn.send(data.encode("utf-8"))
20         except Exception as e:
21             break
22     conn.close()
23 sock_server.close()

服务端

 1 from socket import *
 2
 3 srv_msg = ("127.0.0.1",8000)
 4 bufsize = 1024
 5
 6 socket_client = socket(AF_INET,SOCK_STREAM)
 7 socket_client.connect(srv_msg)
 8
 9 while True:
10     msg_input = input(">>")
11     if not msg_input:continue  # 防止输入为空而卡住死机,为什么会卡住?请看后面详解
12     socket_client.send(msg_input.encode("utf-8"))
13     data=socket_client.recv(bufsize)
14     print(data.decode("utf-8"))
15
16 socket_client.close()

客户端

socket收发消息的原理图

当client端直接回车发送了一个空,在自己的缓冲区是一个空。经过网络传输,到server端的时候,服务端写入缓冲区的也是空,。

那么服务端就一直检测自己的缓冲区是否有数据,所以就在等待接受数据。数据没有收到,自然也就无法正常在自己的缓冲区写入回复消息。

这样也就无法返回给客户端消息。所以客户端就会一直等待服务端的回应。

(4)、解决socket服务端断开后出现端口占用的情况

(4.1)、链路复用

1 #加入一条socket配置,重用ip和端口
2
3 phone=socket(AF_INET,SOCK_STREAM)
4 phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
5 phone.bind((‘127.0.0.1‘,8080))

(4.2)、linux内核参数优化

 1 发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
 2 vi /etc/sysctl.conf
 3
 4 编辑文件,加入以下内容:
 5 net.ipv4.tcp_syncookies = 1
 6 net.ipv4.tcp_tw_reuse = 1
 7 net.ipv4.tcp_tw_recycle = 1
 8 net.ipv4.tcp_fin_timeout = 30
 9
10 然后执行 /sbin/sysctl -p 让参数生效。
11
12 net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
13
14 net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
15
16 net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
17
18 net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间
19 

(5)、基于udp的套接字例子

from socket import *
import time

ip_port = ("127.0.0.1",8080)
buf_size = 1024
udp_srv = socket(AF_INET,SOCK_DGRAM)
udp_srv.bind(ip_port)

print("udp服务端启动")
while True:
    data,addr=udp_srv.recvfrom(buf_size)
    if not data:
        fmt = "%Y-%m-%d %X"
    else:
        fmt = data.decode(‘utf-8‘)
    back_msg = time.strftime(fmt)
    udp_srv.sendto(back_msg.encode("utf-8"),addr)  #这里和tcp区别比较大

udp_srv.close()

服务端

from socket import *

buf_size = 1024
ip_port = ("127.0.0.1",8080)
udp_client = socket(AF_INET,SOCK_DGRAM)

while True:
    msg = input(">>")
    udp_client.sendto(msg.encode("utf-8"),ip_port)
    data = udp_client.recv(buf_size)
    print(data.decode("utf-8"))

udp_client.close()

客户端

4、利用socket编写远程执行命令的程序

(1)、第一版本程序

 1 from socket import *
 2 import subprocess
 3
 4 ip_port = ("127.0.0.1",8000)
 5 back_log = 5
 6 buf_size = 1024
 7
 8 socket_server = socket(AF_INET,SOCK_STREAM)
 9 socket_server.bind(ip_port)
10 socket_server.listen(back_log)
11
12 print("服务端启动")
13 while True:
14     conn, addr = socket_server.accept()
15     print("客户端 %s 连上服务器!" % addr[0])
16
17     while True:
18         try:
19             cmd = conn.recv(buf_size)
20             if not cmd:break
21             print("收到客户端 %s 命令" % cmd.decode("utf-8"))
22             res = subprocess.Popen(cmd.decode("utf-8"),shell=True,
23                                    stdin=subprocess.PIPE,
24                                    stdout=subprocess.PIPE,
25                                    stderr=subprocess.PIPE)
26             err = res.stderr.read()
27             if err:
28                 res_msg = err
29             else:
30                 res_msg = res.stdout.read()
31             if not res_msg:
32                 res_msg = "执行成功".encode("gbk")
33             conn.send(res_msg)
34         except Exception as e:
35             print(e)
36             break
37         # conn.send()
38     conn.close()
39 socket_server.close()

服务端

 1 from socket import *
 2
 3 ip_port = ("127.0.0.1",8000)
 4 back_log = 5
 5 buf_size = 1024
 6
 7 socket_client = socket(AF_INET,SOCK_STREAM)
 8 socket_client.connect(ip_port)
 9
10 while True:
11     cmd = input(">>:")
12     if not cmd:continue
13     if cmd == "quit" or cmd == "exit":break
14     socket_client.send(cmd.encode("utf-8"))
15     res = socket_client.recv(buf_size)
16     print("命令的结果是:",res.decode(‘gbk‘))
17 socket_client.close()

客户端

第一版本会出现粘包现象。

为什么会粘包,简单来说就是接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

粘包显现只有tcp会出现,udp不会出现。

粘包现象有两种体现方式:

(1.1)、发送端数据很小,时间间隔很短这个时候发送端的优化算法就会将数据合并到一起发送。

(1.2)、发送方数据量很大,接收方没有及时接收缓冲区的包,造成多个包接收。

许海峰老师的说法(摘自他的博客)

http://www.cnblogs.com/linhaifeng/articles/6129246.html#_label5

发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

  1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
  2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
  3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略

udp的recvfrom是阻塞的,一个recvfrom(x)必须对一个一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

(2)、第二版本程序

 1 from socket import *
 2 import subprocess
 3
 4 ip_port = ("127.0.0.1",8000)
 5 back_log = 5
 6 buf_size = 1024
 7
 8 sck_server = socket(AF_INET,SOCK_STREAM)
 9 sck_server.bind(ip_port)
10 sck_server.listen(back_log)
11
12 print("服务端启动")
13 while True:
14     conn,addr = sck_server.accept()
15
16     while True:
17         try:
18             data = conn.recv(buf_size)
19             print("服务端收到:%s" % data.decode("utf-8"))
20             if not data:break
21             sub = subprocess.Popen(data.decode(‘utf-8‘),shell=True,
22                                    stdout=subprocess.PIPE,
23                                    stderr=subprocess.PIPE)
24             res_msg = sub.stderr.read()
25             if res_msg:  #当命令错误管道有信息,则表明命令出错
26                 res = res_msg
27             else: #其他情况就正常返回命令的结果
28                 res = sub.stdout.read()
29
30             if not res: # 这里是为了兼容异常命令,例如exit,windows和linux都有这个命令是退出系统的,所以他的返回值为空,如果不封装信息返回给客户端,客户端会卡死。
31                 res = "执行成功".encode("gbk")   # windows默认编码是gbk
32
33             res_length = len(res) # 将数据的长度保存
34             conn.send(str(res_length).encode("utf-8")) # 将数据的长度传给客户端
35             data = conn.recv(buf_size).decode("utf-8") # 接收客户端收到数据长度后返回来的口令。
36             if data == "go": #收到可以传正式数据的口令后开始传数据
37                 conn.sendall(res)
38         except Exception as e:
39             print(e)
40             break
41     conn.close()
42 sck_server.close()

服务端

 1 from socket import *
 2
 3 ip_port = ("127.0.0.1",8000)
 4 back_log = 5
 5 buf_size = 1024
 6
 7 sck_client = socket(AF_INET,SOCK_STREAM)
 8 sck_client.connect(ip_port)
 9
10 while True:
11     msg = input(">>: ")
12     if not msg:continue
13     if msg == "exit":break
14
15     sck_client.send(msg.encode("utf-8"))
16     data = sck_client.recv(buf_size)
17     data_length = int(data.decode("utf-8"))
18     sck_client.send("go".encode("utf-8"))
19     recv_size = 0 #初始化收到的数据的字节数
20     data_new = b"" #初始化数据
21     while recv_size < data_length:
22         print(recv_size)
23         data_new += sck_client.recv(buf_size) #拼接收到的数据
24         recv_size+=len(data_new) #将收到的数据数量合并
25
26     print("客户端收到服务端的返回信息:%s" % data_new.decode("gbk"))
27 sck_client.close()

客户端

第二版本的思路就是先将服务端所要传给客户端的数据大小先发给客户端,这样客户端就根据服务端传过来的大小来接收数据。

(3)、第三版程序

 1 from socket import *
 2 import subprocess,struct,json
 3
 4 ip_port = ("127.0.0.1",8000)
 5 back_log = 5
 6 buf_size = 1024
 7
 8 sck_server = socket(AF_INET,SOCK_STREAM)
 9 sck_server.bind(ip_port)
10 sck_server.listen(back_log)
11
12 print("服务端启动")
13 while True:
14     conn,addr = sck_server.accept()
15
16     while True:
17         try:
18             data = conn.recv(buf_size)
19             print("服务端收到:%s" % data.decode("utf-8"))
20             if not data:break
21             sub = subprocess.Popen(data.decode(‘utf-8‘),shell=True,
22                                    stdout=subprocess.PIPE,
23                                    stderr=subprocess.PIPE)
24             res_msg = sub.stderr.read()
25             if res_msg:  #当命令错误管道有信息,则表明命令出错
26                 res = res_msg
27             else: #其他情况就正常返回命令的结果
28                 res = sub.stdout.read()
29
30             if not res: # 这里是为了兼容异常命令,例如exit,windows和linux都有这个命令是退出系统的,所以他的返回值为空,如果不封装信息返回给客户端,客户端会卡死。
31                 res = "执行成功".encode("gbk")   # windows默认编码是gbk
32
33             headers = {"data_size":len(res)}
34             head_json = json.dumps(headers)
35             head_json_bytes = bytes(head_json,encoding="utf-8")
36
37             conn.send(struct.pack("i",len(head_json_bytes)))
38             conn.send(head_json_bytes)
39             conn.sendall(res)
40
41         except Exception as e:
42             print(e)
43             break
44     conn.close()
45 sck_server.close()

服务端

 1 from socket import *
 2 import struct,json
 3
 4 ip_port = ("127.0.0.1",8000)
 5 back_log = 5
 6 buf_size = 1024
 7
 8 sck_client = socket(AF_INET,SOCK_STREAM)
 9 sck_client.connect(ip_port)
10
11 while True:
12     msg = input(">>: ")
13     if not msg:continue
14     if msg == "exit":break
15     sck_client.send(msg.encode("utf-8"))
16
17     head = sck_client.recv(4)
18     head_json_len = struct.unpack("i",head)[0]
19     head_json = json.loads(sck_client.recv(head_json_len).decode("utf-8"))
20
21     data_length = head_json[‘data_size‘]
22     recv_size = 0 #初始化收到的数据的字节数
23     data_new = b"" #初始化数据
24     while recv_size < data_length:
25         print(recv_size)
26         data_new += sck_client.recv(buf_size) #拼接收到的数据
27         recv_size+=len(data_new) #将收到的数据数量合并
28
29     print("客户端收到服务端的返回信息:%s" % data_new.decode("gbk"))
30 sck_client.close()

客户端

解决思路:

服务端:

将包头做成字典,字典里面包含要发送的真实数据的详细信息,然后通过json进行序列化,然后用struck将序列化后的数据长度打包成4个字节

发送时,先讲包头长度发送,再讲编码包头内容发送,最后进行真实数据的发送

客户端:

先收包头长度,用struct取出来,根据取出的长度收取包头内容,然后解码,反序列化,从反序列化的结果中取出要收取数据的详细信息。最后取真实数据,进行拼接和长度判断。

时间: 2024-10-03 23:18:17

python的socket的学习的相关文章

Python 3 socket编程

Python 3 socket编程 一 客户端/服务器架构 互联网中处处是C/S架构 1.C/S结构,即Client/Server(客户端/服务器)结构 2.在互联网中处处可见c/s架构 比如说浏览器,在线视频,各种社交软件. C/S架构与socket的关系: 我们学习socket就是为了c/s架构的开发 学习socket一定要先学习互联网协议: 1.如何基于socket编程,来开发一款自己的C/S架构软件 2..C/S架构的软件(软件属于应用层)是基于网络进行通信的 3.网络的核心即一堆协议,

Python 的 Socket 编程教程

这是用来快速学习 Python Socket 套接字编程的指南和教程.Python 的 Socket 编程跟 C 语言很像. Python 官方关于 Socket 的函数请看 http://docs.python.org/library/socket.html 基本上,Socket 是任何一种计算机网络通讯中最基础的内容.例如当你在浏览器地址栏中输入 www.oschina.net 时,你会打开一个套接字,然后连接到 www.oschina.net 并读取响应的页面然后然后显示出来.而其他一些聊

Python异步Socket编程

异步网络据说能极大的提高网络server的连接速度,所以打算写一个专题,来学习和了解异步网络.因为Python有个非常出名的异步Lib:Twisted,所以就用Python来完成. OK,首先写一个pythone socket的server段,对开放三个端口:10000,10001,10002.krondo的例子中是每个server绑定一个端口,测试的时候需要分别开3个shell,分别运行.这太麻烦了,就分别用三个Thread来运行这些services. Java代码   import optp

Python的socket编程

目前处在学习python的阶段,昨天看到了python的socket模块,分别实现TCP.UDP时间戳回显. 1.tcp通信server和client代码 # tcpServer.py #!/usr/bin/python # -*- coding: utf-8 -*- from socket import * from time import ctime HOST = '' PORT = 21156 BUFSIZE = 1024 ADDR = (HOST,PORT) tcpServerSock 

python之raw_input()(学习笔记六)

python之raw_input()(学习笔记六) 我们经常使用raw_input()读取用户的输入,如下例子所示: >>> name = raw_input('please input your name:'),截图如下: 下面简单说下,raw_input()与if搭配使用,脚本如下: #!/usr/bin/env python # -*- coding:utf-8 -*- birth = raw_input('birth:') if birth < 2000: print '0

python tcp socket 多线程

不多说,直接上代码 client.py #!/usr/bin/python import socket,sys,string host="localhost" port=8000 def main(): sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.connect((host,port)) message = raw_input("please input whath you want to search

python基础教程_学习笔记23:图形用户界面

图形用户界面 丰富的平台 在编写Python GUI程序前,需要决定使用哪个GUI平台. 简单来说,平台是图形组件的一个特定集合,可以通过叫做GUI工具包的给定Python模块进行访问. 工具包 描述 Tkinter 使用Tk平台.很容易得到.半标准. wxpython 基于wxWindows.跨平台越来越流行. PythonWin 只能在Windows上使用.使用了本机的Windows GUI功能. JavaSwing 只能用于Jython.使用本机的Java GUI. PyGTK 使用GTK

python基础教程_学习笔记3:元组

元组 元组不能修改:(可能你已经注意到了:字符串也不能修改.) 创建元组的语法很简单:如果用逗号分隔了一些值,那么你就自动创建了元组. >>> 1,3,'ab' (1, 3, 'ab') 元组也是(大部分时候是)通过圆括号括起来的. >>> (1,3,'13') (1, 3, '13') 空元组可以用没有内容的两个圆括号来表示. 如何实现包括一个值的元组呢? >>> (5) 5 >>> ('ab') 'ab' >>>

Python 使用Socket实现FTP功能

FtpServer #!/usr/bin/env python import SocketServer class MyServer(SocketServer.BaseRequestHandler):   def setup(self):      pass   def handle(self):      path='/tmp'      while True:          print self.request,self.client_address,self.server