首页 > 技术文章 > python——socket网络编程

pyramid1001 2016-09-27 14:43 原文

一、OSI七层模型

 

网络通信要素:

  • 源、目的IP地址
  • 应用程序端口号
  • 通信协议

二、Socket

socket通常也称作”套接字“。网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

 

Socket通信流程:

 

1、服务器根据IP地址类型、socket类型、协议来创建socket对象。

2、为服务器socket对象绑定IP地址和端口号。

3、服务器socket监听端口号请求,随时准备接收来自客户端的请求,此时,socket并没有打开。

 

4、客户端创建socket对象。

5、客户端打开socket,根据IP地址和端口号试图连接服务器。

6、服务器监听到客户端的连接请求,被动打开socket。

7、服务器socket通过Accept进入阻塞状态,等待客户端返回连接信息。

8、客户端连接成功,返回连接信息给服务器。

9、服务器连接成功。

10、服务器与客户端之间,根据“一收一发”原则发送和接收数据。

11、客户端socket关闭。

12、服务器socket关闭。

 

三、方法

import socket

1. socket()

socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

(1)family = AF_INET

表示服务器之间的通信。

(2)fmily = AF_UNIX

表示Unix不同进程间的通信。

(3)type = SOCK_STREAM

表示TCP连接。

(4)type = SOCK_DGRAM

表示UDP连接。

2. bind()

sk.bind(address)

将地址与socket绑定。address(host IP,端口号),address必须是个元组。

1 sk = socket.socket()
2 print(sk)
3 
4 addr = ('127.0.0.1',8080)
5 sk.bind(addr)
6 print(sk)

输出结果:
<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>
<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)>

3. listen()

listen(backlog)

监听客户端的连接。backlog为可选参数,表示最大等待连接数量。

4.accept()

接受连接并返回(conn,address)。其中conn表示客户端的sk对象,

1 sk = socket.socket()
2 address = ('127.0.0.1',9080)
3 sk.bind(address)
4 sk.listen(5)
5 conn,addr = sk.accept() 6 print(sk) 7 print(conn) 8 print(addr)

输出结果:
<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9080)>
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9080), raddr=('127.0.0.1', 35066)>
('127.0.0.1', 35066)

5.recv()

sk.recv(bufsize)

接收数据。其中bufsize表示最大可接收的数据大小。

6.connect()

sk.connect(address)

连接指定地址的socket。address用元组表示。

7.send()

sk.send(data)

将data发送给连接的socket。

 

8.sendall()

 与send类似,内部通过递归调用send()方法,尝试将所有数据发送出去。

9.sendto()

sk.sendto(data,addr)

可指定远端地址。10.settimeout(timeout)

设置超时。

11.getpeername()

返回远端socket的地址(address,port)。

12.getsockname

返回自己的socket地址(address,port)。

13.fileno()

套接字的文件描述符。

四、实例

1.Client连接Server,并发送数据给Server。

server:

1 sk = socket.socket()
2 address = ('127.0.0.1',9080)
3 sk.bind(address)
4 sk.listen(5)
5 conn,addr = sk.accept()
6 data = conn.recv(1024)
7 print(str(data,'utf8'))

client:

1 sk = socket.socket()
2 address = ('127.0.0.1',9080)
3 sk.connect(address)
4 inp = input('>>')
5 sk.send(bytes(inp,'utf8'))

2.

server:

 1 sk = socket.socket()
 2 address = ('127.0.0.1',9090)
 3 sk.bind(address)
 4 sk.listen(3)
 5 
 6 while 1:
 7     conn,addr = sk.accept()
 8     print(addr)
 9     while 1:
10         try:
11             data = conn.recv(1024)  #当conn挂掉后,进行异常处理
12         except Exception:
13             break
14         print(str(data, 'utf8'))
15         if not data:
16             break
17         inp = input('>>')
18         conn.send(bytes(inp,'utf8'))
19 sk.close()

client:

 1 sk = socket.socket()
 2 address = ('127.0.0.1',9090)
 3 sk.connect(address)
 4 while 1:
 5     inp = input('>>')
 6     if inp == 'exit':
 7         break
 8     sk.send(bytes(inp,'utf8'))
 9     data = sk.recv(1024)
10     print(str(data,'utf8'))
11 sk.close()

3.在客户端输入一条命令,服务器端执行命令,并返回结果给客户端

注意:需要解决粘包现象

server:

 1 import subprocess
 2 
 3 sk = socket.socket()
 4 address = ('127.0.0.1',9090)
 5 sk.bind(address)
 6 sk.listen(3)
 7 
 8 while 1:
 9     conn,addr = sk.accept()
10     print(addr)
11     while 1:
12         try:
13             data = conn.recv(1024)
14         except Exception:
15             break
16         if not data: break
17         print(str(data, 'utf8'))
18 
19         obj = subprocess.Popen(str(data, 'utf8'),shell=True,stdout=subprocess.PIPE)
20         cmd_result = obj.stdout.read()
21         result_len = bytes(str(len(cmd_result)),'utf8')
22         print(result_len)
23         conn.sendall(result_len)
24         import time
25         time.sleep(1)    #解决粘包现象:在两次send之间,停留一段时间使之隔断。
26         # conn.recv(1024)  #或者接收一个数据,将两次send隔断。
27         conn.sendall(cmd_result)
28 
29 sk.close()

client:

 1 sk = socket.socket()
 2 address = ('127.0.0.1',9090)
 3 sk.connect(address)
 4 while 1:
 5     inp = input('>>')
 6     if inp == 'exit':
 7         break
 8     sk.send(bytes(inp,'utf8'))
 9 
10     result_len = int(str(sk.recv(1024),'utf8'))
11     # sk.send(bytes('ok','utf8'))
12     data = bytes()
13     while len(data) != result_len:
14         ret = sk.recv(1024)
15         data += ret
16 
17     print(str(data,'gbk'))
18 sk.close()

4.实现简单的文件传输

server:

 1 time.asctime()
 2 
 3 sk = socket.socket()
 4 address = ('127.0.0.1',8081)
 5 sk.bind(address)
 6 sk.listen(3)
 7 
 8 while 1:
 9     conn,addr = sk.accept()
10     while 1:
11         data = conn.recv(1024)      #bytes
12         # data = str(data,'utf8')     #将bytes转成string
13         # print(data)               # post|123.mp4|86566153
14         cmd,filename,filesize = str(data,'utf8').split(' ')
15         filesize = int(filesize)
16         BASE_DIR = os.path.dirname(os.path.abspath(__file__))
17         path = os.path.join(BASE_DIR,'FTP目录',filename)
18         has_sent = 0
19         with open(path,'ab') as f:
20             while has_sent != filesize:
21                 data = conn.recv(1024)
22                 f.write(data)
23                 has_sent += len(data)
24             print("文件%s上传成功,文件大小:%s"%(filename,filesize))

client:

 1 sk = socket.socket()
 2 address = ('127.0.0.1',8081)
 3 sk.connect(address)
 4 
 5 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 6 print(BASE_DIR)
 7 
 8 while 1:
 9     inp = input(">>").strip()   #post 123.mp4
10     cmd,filename = inp.split(' ')
11     path = os.path.join(BASE_DIR,filename)
12 
13     if os.path.exists(path):
14         filename = os.path.basename(path)   #获取文件名
15         print(filename)
16         filesize = os.stat(path).st_size    #获取文件大小
17         print(filesize)
18         file_info = 'post %s %s'%(filename,filesize)    #string
19 
20         sk.sendall(bytes(file_info,'utf8'))   #需要将string转成bytes才能进行传输
21 
22         with open(path,'rb') as f:
23             has_post = 0
24             while has_post != filesize:
25                 data = f.read(1024)
26                 sk.sendall(data)
27                 has_post += len(data)
28             print("上传成功!")

 五、socketserver

socketserver可以简化了网络服务器的编写,减少开发人员编写网络服务器程序的工作量。

  • TCPServer
  • UDPserver
  • UnixStreamServer
  • UnixDatagramServer

这4各类会同步处理每一个request,也就是说只有当前的request处理完才会处理下一个request,这种方式显然很不合理,如果当前的request处理过慢导致堵塞。正确的处理方式应该是创建新的进程或线程去处理不同的request。

另外通过ForkingMixIn和ThreadingMixIng类来支持异步。

 

Server类

包含五种server类:

  • BaseServer(不直接对外服务)
  • TCPServer
  • UnixStreamServer
  • UDPServer
  • UnixDatagramserver

五种类的继承关系如下:

 

官方参考文档:

https://docs.python.org/3/library/socketserver.html

 创建socketserver的步骤:

 (1)穿件处理request的类,创建方法为:继承BaseRequestHandler类,并重载handle()方法。该方法将被回调用作处理当前接收到的request。

注意:一般的做法是直接继承StreamRequestHandler或者DatagramRequestHandler。比如:

1 class MyTCPHandler(SocketServer.StreamRequestHandler):

(2)实例化一个server基类的对象,冰法服务器地址和处理request的类作为参数传入。

(3)使用server基类对象调用handle_request()或serve_forever()方法,即可处理一个或多个request。

(4)如果需要创建多进程或多线程的服务器程序,则可以通过混合继承ForkingMixIn或ThreadingMixIn类来实现,比如:

class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass //创建一个多线程的TCP服务器。

注意:ThreadingMixIn必须要放在TCPServer前面。

 

简单并发实例:

server:

 1 import socketserver
 2 
 3 class MyServer(socketserver.BaseRequestHandler):  #定义request handler类,从
 4 BaseRequestHandler类继承
 5     def handle(self):  #重写handle()方法,处理当前的request
 6         print("服务端启动..")
 7         while True:
 8             conn = self.request
 9             print(self.client_address)
10             while True:
11                 client_data = conn.recv(1024)  #self.request是和客户端连接的套接字
12                 print(str(client_data,'utf8'))
13                 server_response = input('>>').strip()
14                 conn.send(bytes(server_response,'utf8'))
15             conn.close()
16 
17 if __name__ == '__main__':
18     server = socketserver.ThreadingTCPServer(('127.0.0.1',8081),MyServer)
    #传入监听地址 端口号和request handler类
19 server.serve_forever()  #启动监听处理request

 

client:

 1 import socket
 2 
 3 sk = socket.socket()
 4 address = ('127.0.0.1',8081)
 5 sk.connect(address)
 6 print("客户端启动..")
 7 
 8 while True:
 9     inp = input(">>")
10     sk.sendall(bytes(inp,'utf8'))
11     if inp == 'exit':
12         break
13     server_response = sk.recv(1024)
14     print(str(server_response,'utf8'))
15 
16 sk.close()

 

推荐阅读