Socket模块使用及其socketserver模块分析。

Socket

Socket层

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。

Socket把复杂的TCP/IP协议族隐藏在Socket接口后面。

Socket编程

1.创建套接字对象

  • Sk=socket.socket(sock_family,socket_type,protoco=0) # 默认是AF_INET,SOCK_STREAM

    • sock_family

      • socket.AF_INET : 代表地址簇为IPV4。
      • socket.AF_INET6: 代表地址簇为IPV6。
      • socket.AF_UNIX : 用于UNIX进程间通信的。
    • socket_type:

      • socket.SOCK_STREAM : TCP类型的套接字。
      • socket.SOCK_DGRAM : UDP类型的套接字。
      • socket.SOCK_RAW : 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以。此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头

2.套接字对象方法

  • 服务器端

    • Sk.bind() #设置监听的IP、PORT。

      • IP与PORT作为元组的元素,传入元组(IP,PORT)。
      • 0.0.0.0表示本机的任意网卡地址都可以接受链接。
    • Sk.listen() #开启套接字对象的监听。

      • 可以设置连接的最大数量,默认为5(不是并发数,而是队列长度)。
    • con,addr = Sk.accept() #开始接受套接字连接。

      • 有连接时,返回一个连接对象和客户端套接字地址(元组)。
      • 阻塞直到有连接。
      • 有连接就会继续往下运行,因此要接受多个客户端的连接时,需要一直循环运行该语句。
    • con.send() #向指定的连接对象发送数据。(只发一次发送缓存区大小就往下走)

      • 数据要求字节类型,而不能是字符串。返回发送的字节大小。
    • con.sendall() #内部通过递归调用send,将所有内容发送出去。(保证数据全部发完才走)

      • 成功返回None,失败抛出异常。
      • con表示连接对象(完整的套接字),通常是由accept()返回的。
      • 注:不能发送空,否则会卡死
  • 客户端

    • Sk.connect() #连接服务器。传入(IP,PORT)元组。
    • Sk.connect_ex() #同上,但是会有返回值,连接成功时返回 0 ,连接失败时候返回错误码,而不是异常。
    • Sk.send() #同上面服务器send。
    • Sk.sendall() #同上。
  • 通用方法

    • Sk.recv(Bufsize) #接收套接字的数据,通常是接收accept()返回的某套接字的消息。

      • 阻塞直到收到数据。
      • 接收到数据就会往下运行,因此要多次交互数据则需要不断循环运行该语句。
    • Sk.recvfrom(Bufsize) #同上,但是返回数据外加地址,(data,(IP,PORT)),通常用于UDP。

    • Sk.close() #关闭套接字。

    • 其他

      1
      2
      3
      4
      5
      6
      7
      8
      9
      Sk.getsockname()   #返回本地套接字的地址。(IP,PORT)
      Sk.getpeername() #返回远端连接过来的套接字的地址。(IP,PORT)

      Sk.settimeout() #设置套接字阻塞的超时时长,传入浮点型。如recv()的阻塞超时时长。
      Sk.setblocking() #是否阻塞(默认True),如果传入False,那么accept和recv时一旦无数据,则报 错。
      Sk.gethostname() #获取本地主机名(不是IP)
      Sk.gethostbyname('www.baidu.com') #域名解析

      Sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #可地址重用,在调用bind()方法前使用。

Socketserver源码分析

  • socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
  • socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)
  • 多线程:
    • socketserver.ThreadingTCPServer(server_address, RequestHandlerClass,bind_and_activate=True)
  • 多进程:
    • socketserver.ForkingTCPServer(server_address, RequestHandlerClass, bind_and_activate=True)

样例源码:时间服务器

Server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import socketserver
import time

class Myhander(socketserver.BaseRequestHandler):
def handle(self):
client_addr = self.client_address[0] #获得连接的客户端IP
print('[+]From %s Connect!!!'%client_addr)
while True:
self.data = self.request.recv(4096) #获取客户端消息
print('[+]From %s message :%s'%(client_addr,self.data.decode()))
nowtime = time.ctime()
self.request.sendall(b'Hello Friend ! Now Time:%s'%nowtime.encode())

FSk= socketserver.TCPServer(('127.0.0.1',7777),Myhander)
print("[-]%s : 时间服务器正在监听!!!"%time.ctime())

FSk.serve_forever()

Client.py

1
2
3
4
5
6
7
8
9
10
11
import socket
Sk = socket.socket()

Sk.connect(('127.0.0.1',7777))

print('[+]Connect success!!')
while 1:
userinput = input('[-]Please input:')
Sk.sendall(userinput.encode())
data = Sk.recv(4096)
print('[+]From server reply : %s '%data.decode())

socketserver.TCPServer()运行调用分析

socketserver.ThreadingTCPServer()

该类与socketserver.TCPServer()类的区别在于,该类为到来的每一个连接请求各分配一个线程进行处理。除此之外,调用情况大致与上面相同。

接下来,我们来看二者区别的细节。

可以看到,ThreadingTCPServer()先继承于ThreadingMixIn,再继承TCPServer,而自己本身是不包含任何代码的。

1
2
class TCPServer(BaseServer):...
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

接下来,查看ThreadingMixIn类的源码,就可以发现,ThreadingMixIn的作用就是覆盖重写TCPServer()类的process_request()方法以实现多线程处理请求。

简单来说就是,使用ThreadingTCPServer()类时,上图中调用走到第9步时,执行的时ThreadingMixIn类的process_request()方法,而不是TCPServer(BaseServer)类的process_request()方法(因为先继承ThreadingMixIn类,所以优先查找ThreadingMixIn类的命名空间)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class ThreadingMixIn:
daemon_threads = False
block_on_close = True
_threads = None

def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
except Exception:
self.handle_error(request, client_address)
finally:
self.shutdown_request(request)

def process_request(self, request, client_address):
"""Start a new thread to process the request."""
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
if not t.daemon and self.block_on_close:
if self._threads is None:
self._threads = []
self._threads.append(t)
t.start()

def server_close(self):
super().server_close()
if self.block_on_close:
threads = self._threads
self._threads = None
if threads:
for thread in threads:
thread.join()

参考链接:

https://www.cnblogs.com/Eva-J/p/5081851.html

https://www.cnblogs.com/wupeiqi/articles/5040823.html

最后更新: 2019年06月18日 20:14

原始链接: https://sakuxa.com/2019/06/18/Python网络编程/