Socket网络编程

 本节内容:

计算机网络

   

                                        

一、网络通信要素

    A:IP地址   (1) 用来标识网络上一台独立的主机

                   (2) IP地址 = 网络地址 + 主机地址(网络号:用于识别主机所在的网络/网段。主机号:用于识别该网络中的主机)

                   (3) 特殊的IP地址:127.0.0.1(本地回环地址、保留地址,点分十进制)可用于简单的测试网卡是否故障。表示本机。

    B:端口号:  (1) 用于标识进程的逻辑地址。不同的进程都有不同的端口标识。

                   (2) 端口:要将数据发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。为了方便称呼这些数字,则将这些数字称为端口。(此端口是一个逻辑端口)

   C: 传输协议通讯的规则。例如:TCP、UDP协议(好比两个人得用同一种语言进行交流)

 

①、UDP:User Datagram Protocol用户数据报协议

特点:

  • 面向无连接:传输数据之前源端和目的端不需要建立连接。
  • 每个数据报的大小都限制在64K(8个字节)以内。
  • 面向报文的不可靠协议。(即:发送出去的数据不一定会接收得到)
  • 传输速率快,效率高。
  • 现实生活实例:邮局寄件、实时在线聊天、视频会议…等。

 

 ②、TCP:Transmission Control Protocol传输控制协议

 特点:

  • 面向连接:传输数据之前需要建立连接。
  • 在连接过程中进行大量数据传输。
  • 通过“三次握手”的方式完成连接,是安全可靠协议。
  • 传输速度慢,效率低。

注意:在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接

 # “我能给你讲个关于tcp的笑话吗?”
 #   “行,给我讲个tcp笑话.” 
 #   “好吧那我就给你讲个tcp笑话.”

 网络通讯步骤:

     确定对端IP地址→ 确定应用程序端口 → 确定通讯协议

     总结:网络通讯的过程其实就是一个(源端)不断封装数据包和(目的端)不断拆数据包的过程。

     简单来说就是:发送方利用应用软件将上层应用程序产生的数据前后加上相应的层标识不断的往下层传输(封包过程),最终到达物理层通过看得见摸得着的物理层设备,例如:网线、光纤…等将数据包传输到数据接收方,然后接收方则通过完全相反的操作不断的读取和去除每一层的标识信息(拆包过程),最终将数据传递到最高层的指定的应用程序端口,并进行处理。

二、SOCKET 编程

     要想理解socket,就要先来理解TCP,UDP协议

     TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何再它们之间传输的标准,

从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中

应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等

传输层:TCP,UDP

网络层:IP,ICMP,OSPF,EIGRP,IGMP

数据链路层:SLIP,CSLIP,PPP,MTU

每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的

    我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,我们经常把socket翻译为套接字,socket是在应用层和传输层(TCP/IP协议族通信)之间的一个抽象层,是一组接口,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

    应用程序两端通过“套接字”向网络发出请求或者应答网络请求。可以把socket理解为通信的把手(hand)

    socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。socket的英文原义是“插槽”或“插座”,就像我们家里座机一样,如果没有网线的那个插口,电话是无法通信的。Socket是实现TCP,UDP协议的接口,便于使用TCP,UDP。

    

socket通信流程

                                

 1 基本格式:(注:send发的bytes类型  listen阻塞  一收一发)
 2     server端:
 3         import socket
 4         sk = socket.socket()
 5         ip_port = ('127.0.0.1',8000)
 6         sk.bind(ip_port)
 7         sk.listen(3)
 8         conn,addr = sk.accept()
 9         inp = input()
10         conn.send(bytes(inp,'utf8'))
11         conn.recv(1024)
12         conn.close()
13     client端:
14         import socket
15         sk = socket.socket()
16         ip_port = ('127.0.0.1',8000)
17         sk.connect(ip_port)
18         sk.recv(1024)
19         inp = input()
20         sk.send(bytes(inp,'utf8'))
21         sk.close()
socket基本格式
# 流程描述:
# 
# 1 服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
# 
# 2 服务器为socket绑定ip地址和端口号
# 
# 3 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
# 
# 4 客户端创建socket
# 
# 5 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
# 
# 6 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直等到客户端返回连接信息后才返回,开始接收下一个客户端连接请求
# 
# 7 客户端连接成功,向服务器发送连接状态信息
# 
# 8 服务器accept方法返回,连接成功
# 
# 9 客户端向socket写入信息(或服务端向socket写入信息)
# 
# 10 服务器读取信息(客户端读取信息)
# 
# 11 客户端关闭
# 
# 12 服务器端关闭

三、相关方法及参数介绍

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提供有关消息的其他信息,通常可以忽略。

       异常退出
          data = conn.recv(1024) # 阻塞
          监听的套接字对象conn断开连接:
              针对window:异常报错,用异常处理
              针对linux:接收一个空的data,用if data==’’:break。

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()

  #套接字的文件描述符

 

四 、实例(屌丝追女神的故事)

 故事1

 背景:从前,有个屌丝阿武,想追女神阿思,闷骚型,心想:老子就给她一次机会,不把握就算了...

#################server
import socket
ip_port = ('127.0.0.1',9997)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)

print ('server waiting...')

conn,addr = sk.accept()
client_data = conn.recv(1024)
print (str(client_data,"utf8"))
conn.sendall(bytes('滚蛋!',encoding="utf-8"))

sk.close()

################client
import socket
ip_port = ('127.0.0.1',9997)

sk = socket.socket()
sk.connect(ip_port)


sk.sendall(bytes('俺喜欢你',encoding="utf8"))

server_reply = sk.recv(1024)
print (str(server_reply,"utf8"))

 故事2

背景:又有个屌丝,名叫武二,想追女神阿思,内心强大,臭不要脸,于是...

#-------------------------------------------------server.py
#-------------------------------------------------
import socket
ip_port = ('127.0.0.1',8888)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(2)
print ("服务端启动...")
conn,address = sk.accept()
while True:
    client_data=conn.recv(1024)
    if str(client_data,"utf8")=='exit':
        break
    print (str(client_data,"utf8"))
    server_response=input(">>>")
    conn.sendall(bytes(server_response,"utf8"))

conn.close()

#-------------------------------------------------client.py
#-------------------------------------------------

import socket
ip_port = ('127.0.0.1',8888)
sk = socket.socket()
sk.connect(ip_port)
print ("客户端启动:")

while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    if inp == 'exit':
        break
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
sk.close()

注意:固然武二脸皮够厚,但是落花有意流水无情,女神不给机会和你聊也白搭(server端也需要有while)。

 故事3

背景:某天,女神孤独难耐,决定调整状态想和多人聊一聊找个合适的(虽然不是并发,但是可以在不重新开启server的前提下与多人聊天),于是52抓住了机会,俩人一顿深聊后..............52放弃了。

代码同上

 win:

import socket

ip_port = ('127.0.0.1',8870)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(2)
print ("服务端启动...")

while True:
    conn,address = sk.accept()
    print(address)
    while True:
        try:
            client_data=conn.recv(1024)
        except:
            print("意外中断")
            break
        print (str(client_data,"utf8"))

        server_response=input(">>>")
        conn.sendall(bytes(server_response,"utf8"))

    conn.close()
    
##################
import socket
ip_port = ('127.0.0.1',8870)
sk = socket.socket()
sk.connect(ip_port)
print ("客户端启动:")

while True:
    inp = input('>>>')
    if inp == 'exit':
        break
    sk.sendall(bytes(inp,"utf8"))
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
sk.close()

linux:

1
2
3
4
5
6
7
8
client_data=conn.recv(1024)
print (str(client_data,"utf8"))
if len(client_data)==0:
      print("意外中断")
      break
 
server_response=input(">>>")
conn.sendall(bytes(server_response,"utf8"))

关于linux这里,有同学可能会问

1
2
3
4
5
client_data=conn.recv(1024)
print('3333',str(client_data,"utf8"))
if len(client_data)==0:
     print("意外中断")
     break

这样如果客户端发的数据就是空数据的话岂不是也意外退出,那不就bug啦?

其实不用担心,如果客户端send了一个空数据后客户端继续向下执行,而server端的recv方法会继续阻塞,直到接收到一个非空数据才会继续向下执行。

而stop_button的退出会使client_data成为一个空数据继续向下执行,所以可以依据代码意外退出。

 

ok,关键的问题来了:如何让女神可以同时和多个屌丝聊天呢?(如何实现并发)

server端:

import socket
sk = socket.socket()
ip_port = ('127.0.0.1', 8000)
sk.bind(ip_port)
sk.listen(3)
while 1:
     conn, addr = sk.accept()
     while 1:
           try:
                data = conn.recv(1024)
           except Exception as e:
                print('意外退出')
                break
           print(str(data, 'utf8'))
           inp = input()
           conn.send(bytes(inp, 'utf8'))
     conn.close()
sk.close()





client端:

import socket
sk = socket.socket()
ip_port = ('127.0.0.1', 8000)
sk.connect(ip_port)
while 1:
      inp = input()
      if inp == 'exit': 
           break
      sk.send(bytes(inp, 'utf8'))
      data = sk.recv(1024)
      print(str(data, 'utf8'))
sk.close()
注:外层while实现与多人聊天,内层while实现不间断聊天,try实现退出处理
不间断聊天加退出处理

简单并发实例

#-----------------------------------------------------server.py
#-----------------------------------------------------
import socketserver

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
        print ("服务端启动...")
        while True:
            conn = self.request
            print (self.client_address)
            while True:
                client_data=conn.recv(1024)
                print (str(client_data,"utf8"))
                print ("waiting...")
                conn.sendall(client_data)
            conn.close()

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8091),MyServer)
    server.serve_forever()

#-----------------------------------------------------client.py
#-----------------------------------------------------
import socket

ip_port = ('127.0.0.1',8091)
sk = socket.socket()
sk.connect(ip_port)
print ("客户端启动:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    if inp == 'exit':
        break
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
sk.close()

聊天并发实例

import socketserver

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
        print ("服务端启动...")
        while True:
            conn = self.request
            print (self.client_address)
            while True:

                client_data=conn.recv(1024)

                print (str(client_data,"utf8"))
                print ("waiting...")
                server_response=input(">>>")
                conn.sendall(bytes(server_response,"utf8"))
                # conn.sendall(client_data)

            conn.close()
            # print self.request,self.client_address,self.server


if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8098),MyServer)
    server.serve_forever()


##########################################
import socket


ip_port = ('127.0.0.1',8098)
sk = socket.socket()
sk.connect(ip_port)
print ("客户端启动:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
    if inp == 'exit':
        break
sk.close()

五、 其它应用

命令传送1:

 1 注:利用粘包使用struct将shell命令执行结果的长度返回给client端,client端再根据长度接收结果数据
 2 
 3 ssh-server端
 4 
 5 import socket
 6 import subprocess
 7 import struct
 8 
 9 server = socket.socket()
10 server.bind(('127.0.0.1',8008))
11 server.listen(5)
12 while 1:
13     print("server is working.....")
14     conn,addr = server.accept()
15     while 1:
16         try:
17             cmd = conn.recv(1024).decode("utf8") # 阻塞
18             if cmd == b'exit':
19                 break
20             res=subprocess.Popen(cmd,
21                              shell=True,
22                              stderr=subprocess.PIPE,
23                              stdout=subprocess.PIPE,
24                              )
25             out=res.stdout.read()
26             err=res.stderr.read()
27             #print("out响应长度",len(out))
28             #print("err响应长度",len(err))
29             if err:
30                  header_pack = struct.pack("i", len(err))
31                  conn.send(header_pack)
32                  conn.send(err)
33             else:
34                  header_pack=struct.pack("i",len(out))  #构建报头
35                  #print("header_pack",header_pack)
36                  conn.send(str(len(out)).encode("utf8"))  # 发送报头
37                  conn.send(out)  # 发送数据
38         except Exception as e:
39             break
40     conn.close()
41 
42 
43 
44 
45 
46 ssh-client端
47 
48 import socket
49 import struct
50 
51 sk = socket.socket()
52 sk.connect(('127.0.0.1',8008))
53 
54 while 1:
55     cmd = input("请输入命令:")
56     sk.send(cmd.encode('utf-8')) # 字节
57     if cmd == "":
58         continue
59     if cmd == 'exit':
60         break
61     header_pack = sk.recv(4)
62     data_length = struct.unpack("i", header_pack)[0]
63     #print("data_length", data_length)
64     recv_data_length = 0
65     recv_data = b""
66     while recv_data_length < data_length:
67         data = sk.recv(1024)
68         recv_data_length += len(data)
69         recv_data += data
70     print(recv_data.decode("gbk"))
71 sk.close()
shell命令传输并执行然后返回执行结果(struct解决粘包)
 1 shell命令传输并执行然后返回执行结果:
 2     server端:
 3         import socket,subprocess
 4         sk = socket.socket()
 5         ip_port = ('127.0.0.1',8000)
 6         sk.bind(ip_port)
 7         sk.listen(3)
 8         while 1:
 9             conn,addr = sk.accept()
10             while 1:
11                 try:
12                     data = conn.recv(1024)
13                 except Exception as e:
14                     print('意外退出')
15                     break
16                 print(str(data,'utf8'))
17                 cmd_result = subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)
18                 cmd_results = cmd_result.stdout.read()
19                 cmd_len = len(cmd_results)
20                 conn.sendall(bytes(str(cmd_len,'utf8'),'utf8'))
21                 conn.recv(1024)
22                 conn.sendall(cmd_results)
23             conn.close()
24         sk.close()
25     client端:
26         import socket
27         sk = socket.socket()
28         ip_port = ('127.0.0.1',8000)
29         sk.connect(ip_port)
30         while 1:
31             inp = input().strip()
32             if inp=='exit': 
33                 break
34             sk.send(bytes(inp,'utf8'))
35             cmd_size = int(sk.recv(1024))
36             sk.send('ok') #处理粘包现象
37             data = bytes()
38             while len(data)!=cmd_size:
39                 cmd_result = sk.recv(1024)
40                 data += cmd_result
41             print(str(data,'gbk'))
42         sk.close()
shell命令传输并执行然后返回执行结果
#------------------------------------------------server
#------------------------------------------------
import socket
import subprocess
ip_port = ('127.0.0.1',8879)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)
print ("服务端启动...")
while True:
    conn,address = sk.accept()
    while True:
        try:

            client_data=conn.recv(1024)
        except Exception:
            break
        print (str(client_data,"utf8"))
        print ("waiting...")
        # server_response=input(">>>")
        # conn.sendall(bytes(server_response,"utf8"))
        cmd=str(client_data,"utf8").strip()
        cmd_call=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
        cmd_result=cmd_call.stdout.read()
        if len(cmd_result)==0:
            cmd_result=b"no output!"
        conn.sendall(cmd_result)
        print('send data size',len(cmd_result))
        print('******************')
        print('******************')
        print('******************')

    conn.close()
    
#------------------------------------------------client 
#------------------------------------------------
import socket
ip_port = ('127.0.0.1',8879)
sk = socket.socket()
sk.connect(ip_port)
print ("客户端启动:")
while True:
    inp = input('cdm:>>>').strip( )
    if len(inp)==0:
        continue
    if inp=="q":
        break
    sk.sendall(bytes(inp,"utf8"))
    server_response=sk.recv(1024)
    print (str(server_response,"gbk"))
    print('receive data size',len(server_response))
    if inp == 'exit':
        break
sk.close()
1
2
#试一试
netstat -an

conclusion:

              sendall会把数据直接全部发送到客户端,客户端将所有的数据都放到缓冲区,每次recv多少字节取决于recv内的参数,理论不应该超过8k。

所以,并不能一次recv()无限大数据,所以这里我们应该通过循环去接收。

1
sk.recv(4096)  

              

命令传送2:解决大数据传送和粘包问题

 1 import socketserver
 2 import subprocess
 3 
 4 class Myserver(socketserver.BaseRequestHandler):
 5     def handle(self):
 6         while True:
 7             conn=self.request
 8             conn.sendall(bytes("欢迎登录","utf8"))
 9             while True:
10                 client_bytes=conn.recv(1024)
11                 if not client_bytes:break
12                 client_str=str(client_bytes,"utf8")
13                 print(client_str)
14                 command=client_str
15 
16                 result_str=subprocess.getoutput(command)
17                 result_bytes = bytes(result_str,encoding='utf8')
18                 info_str="info|%d"%len(result_bytes)
19                 conn.sendall(bytes(info_str,"utf8"))
20                 # conn.recv(1024)
21                 conn.sendall(result_bytes)
22             conn.close()
23 
24 if __name__=="__main__":
25     server=socketserver.ThreadingTCPServer(("127.0.0.1",9998),Myserver)
26     server.serve_forever()
27 
28 #####################################client
29 
30 
31 import socket
32 ip_port=("127.0.0.1",9998)
33 
34 sk=socket.socket()
35 sk.connect(ip_port)
36 print("客户端启动...")
37 
38 print(str(sk.recv(1024),"utf8"))
39 
40 while True:
41     inp=input("please input:").strip()
42 
43 
44     sk.sendall(bytes(inp,"utf8"))
45     basic_info_bytes=sk.recv(1024)
46     print(str(basic_info_bytes,"utf8"))
47     # sk.send(bytes('ok','utf8'))
48     result_length=int(str(basic_info_bytes,"utf8").split("|")[1])
49 
50     print(result_length)
51     has_received=0
52     content_bytes=bytes()
53     while has_received<result_length:
54         fetch_bytes=sk.recv(1024)
55         has_received+=len(fetch_bytes)
56         content_bytes+=fetch_bytes
57     cmd_result=str(content_bytes,"utf8")
58     print(cmd_result)
59 
60 sk.close()
View Code

文件上传

server端:
import socket,os
sk = socket.socket()
ip_port = ('127.0.0.1',8000)
sk.bind(ip_port)
sk.listen(3)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
while 1:
    conn,addr = sk.accept()
    while 1:
        try:
            data = conn.recv(1024)
        except Exception as e:
            print('意外退出')
            break
        cmd,filename,filesize = str(data,'utf8').split('|')
        path = os.path.join(BASE_DIR,'yuan',filename)
        filesize = int(filesize)
        f = open(path,'ab')
        has_received = 0
        while has_received != filesize:
            data = conn.recv(1024)
            f.write(data)
            has_received += len(data)
        conn.close()
    sk.close()




client端:
import socket,os
sk = socket.socket()
ip_port = ('127.0.0.1',8000)
sk.connect(ip_port)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
while 1:
    inp = input('>>>').strip()
    if inp=='exit': 
        break
    cmd,path = inp.split('|')
    path = os.path.join(BASE_DIR,path)
    filename = os.path.basename(path)
    filesize = os.stat(path).st_size
    file_info = 'post|%s|%s' % (filename,filesize)
    sk.sendall(bytes(file_info,'utf8'))
    f = open(filename,'rb')
    has_sent = 0
    while has_sent != filesize:
        data = f.read(1024)
        sk.sendall(data)
        has_sent += len(data)
    sk.close()
文件上传
 1 import socket,os
 2 ip_port=("127.0.0.1",8898)
 3 sk=socket.socket()
 4 sk.bind(ip_port)
 5 sk.listen(5)
 6 BASE_DIR=os.path.dirname(os.path.abspath(__file__))
 7 
 8 while True:
 9     print("waiting connect")
10     conn,addr=sk.accept()
11     flag = True
12     while flag:
13 
14             client_bytes=conn.recv(1024)
15             client_str=str(client_bytes,"utf8")
16             func,file_byte_size,filename=client_str.split("|",2)
17 
18             path=os.path.join(BASE_DIR,'yuan',filename)
19             has_received=0
20             file_byte_size=int(file_byte_size)
21 
22             f=open(path,"wb")
23             while has_received<file_byte_size:
24                 data=conn.recv(1024)
25                 f.write(data)
26                 has_received+=len(data)
27             print("ending")
28             f.close()
29 
30 #----------------------------------------------client
31 #----------------------------------------------
32 import socket
33 import re,os,sys
34 ip_port=("127.0.0.1",8898)
35 sk=socket.socket()
36 sk.connect(ip_port)
37 BASE_DIR=os.path.dirname(os.path.abspath(__file__))
38 print("客户端启动....")
39 
40 while True:
41     inp=input("please input:")
42 
43     if inp.startswith("post"):
44         method,local_path=inp.split("|",1)
45         local_path=os.path.join(BASE_DIR,local_path)
46         file_byte_size=os.stat(local_path).st_size
47         file_name=os.path.basename(local_path)
48         post_info="post|%s|%s"%(file_byte_size,file_name)
49         sk.sendall(bytes(post_info,"utf8"))
50         has_sent=0
51         file_obj=open(local_path,"rb")
52         while has_sent<file_byte_size:
53             data=file_obj.read(1024)
54             sk.sendall(data)
55             has_sent+=len(data)
56         file_obj.close()
57         print("上传成功")
View Code
ftp-server端

import struct
import socket
import json
import hashlib

sock = socket.socket()
sock.bind(('127.0.0.1', 8800))
sock.listen(5)

while 1:
    print("server is working....")
    conn, addr = sock.accept()
    while 1:
 
        # 接收json的打包长度
        file_info_length_pack = conn.recv(4)
        file_info_length = struct.unpack("i", file_info_length_pack)[0]
 
        # 接收json字符串
        file_info_json = conn.recv(file_info_length).decode("utf8")
        file_info = json.loads(file_info_json)
 
        action = file_info.get("action")
        filename = file_info.get("filename")
        filesize = file_info.get("filesize")
 
        # 循环接收文件
        md5 = hashlib.md5()
        with open("put/" + filename, "wb") as f:
            recv_data_length = 0
            while recv_data_length < filesize:
                data = conn.recv(1024)
                recv_data_length += len(data)
                f.write(data)
                # MD5摘要
                md5.update(data)
                print("文件总大小:%s,已成功接收%s"%(filesize, recv_data_length))
        print("接收成功!")
        conn.send(b"OK")
        #print(md5.hexdigest())
        md5_val = md5.hexdigest()
        client_md5 = conn.recv(1024).decode("utf8")
        if md5_val == client_md5:
            conn.send(b"203")
        else:
            conn.send(b"204")
 
 


 
ftp-client端

import socket
import os
import json
import struct
import hashlib

sock = socket.socket()
sock.connect(("127.0.0.1", 8800))

while 1:
    cmd = input("请输入命令:") # put 111.jpg
 
    action, filename = cmd.strip().split(" ")
    filesize = os.path.getsize(filename)
 
    file_info = {
            "action": action,
            "filename": filename,
            "filesize": filesize,
    }
    file_info_json = json.dumps(file_info).encode("utf8")
 
    ret = struct.pack("i", len(file_info_json))
    # 发送 file_info_json的打包长度
    sock.send(ret)
    # 发送 file_info_json字节串
    sock.send(file_info_json)
    # 发送 文件数据
    md5 = hashlib.md5()
    with open(filename, "rb") as f:
        for line in f:
            sock.send(line)
            md5.update(line)
 
    data = sock.recv(1024)
    #print(md5.hexdigest())
    md5_val = md5.hexdigest()
    sock.send(md5_val.encode("utf8"))
    is_valid = sock.recv(1024).decode('utf8')
    if is_valid == "203":
        print("文件完整!")
    else:
        print("文件上传失败!")
简单FTP文件上传示例

注意:

       1  纸条就是conn

       2  一收一发

       3   client_data=conn.recv(1024)        

           if  那边send一个空数据  这边recv为空,则recv继续阻塞,等待其他的数据。所以聊天的时候好好聊,别发空数据。 

socketserver

虽说用Python编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较好。这样就可以专心事务逻辑,而不是套接字的各种细节。SocketServer模块简化了编写网络服务程序的任务。同时SocketServer模块也是Python标准库中很多服务器框架的基础。

socketserver模块可以简化网络服务器的编写,Python把网络服务抽象成两个主要的类,一个是Server类,用于处理连接相关的网络操作,另外一个则是RequestHandler类,用于处理数据相关的操作。并且提供两个MixIn 类,用于扩展 Server,实现多进程或多线程。

Server类

它包含了种五种server类,BaseServer(不直接对外服务)。TCPServer使用TCP协议,UDPServer使用UDP协议,还有两个不常使用的,即UnixStreamServer和UnixDatagramServer,这两个类仅仅在unix环境下有用(AF_unix)。

Base class for server classes.

1
class BaseServer

This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server. 

1
class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)

This uses datagrams, which are discrete packets of information that may arrive out of order or be lost while in transit. The parameters are the same as for TCPServer

1
class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)

These more infrequently used classes are similar to the TCP and UDP classes, but use Unix domain sockets; they’re not available on non-Unix platforms. The parameters are the same as for TCPServer. 

1
2
class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)<br><br>#---------
class UnixStreamServer(TCPServer):
address_family = socket.AF_UNIX

class UnixDatagramServer(UDPServer):
address_family = socket.AF_UNIX

BaseServer的源码:

  1 class BaseServer:
  2 
  3     """Base class for server classes.
  4 
  5     Methods for the caller:
  6 
  7     - __init__(server_address, RequestHandlerClass)
  8     - serve_forever(poll_interval=0.5)
  9     - shutdown()
 10     - handle_request()  # if you do not use serve_forever()
 11     - fileno() -> int   # for select()
 12 
 13     Methods that may be overridden:
 14 
 15     - server_bind()
 16     - server_activate()
 17     - get_request() -> request, client_address
 18     - handle_timeout()
 19     - verify_request(request, client_address)
 20     - server_close()
 21     - process_request(request, client_address)
 22     - shutdown_request(request)
 23     - close_request(request)
 24     - service_actions()
 25     - handle_error()
 26 
 27     Methods for derived classes:
 28 
 29     - finish_request(request, client_address)
 30 
 31     Class variables that may be overridden by derived classes or
 32     instances:
 33 
 34     - timeout
 35     - address_family
 36     - socket_type
 37     - allow_reuse_address
 38 
 39     Instance variables:
 40 
 41     - RequestHandlerClass
 42     - socket
 43 
 44     """
 45 
 46     timeout = None
 47 
 48     def __init__(self, server_address, RequestHandlerClass):
 49         """Constructor.  May be extended, do not override."""
 50         self.server_address = server_address
 51         self.RequestHandlerClass = RequestHandlerClass
 52         self.__is_shut_down = threading.Event()
 53         self.__shutdown_request = False
 54 
 55     def server_activate(self):
 56         """Called by constructor to activate the server.
 57 
 58         May be overridden.
 59 
 60         """
 61         pass
 62 
 63     def serve_forever(self, poll_interval=0.5):
 64         """Handle one request at a time until shutdown.
 65 
 66         Polls for shutdown every poll_interval seconds. Ignores
 67         self.timeout. If you need to do periodic tasks, do them in
 68         another thread.
 69         """
 70         self.__is_shut_down.clear()
 71         try:
 72             while not self.__shutdown_request:
 73                 # XXX: Consider using another file descriptor or
 74                 # connecting to the socket to wake this up instead of
 75                 # polling. Polling reduces our responsiveness to a
 76                 # shutdown request and wastes cpu at all other times.
 77                 r, w, e = _eintr_retry(select.select, [self], [], [],
 78                                        poll_interval)
 79                 if self in r:
 80                     self._handle_request_noblock()
 81 
 82                 self.service_actions()
 83         finally:
 84             self.__shutdown_request = False
 85             self.__is_shut_down.set()
 86 
 87     def shutdown(self):
 88         """Stops the serve_forever loop.
 89 
 90         Blocks until the loop has finished. This must be called while
 91         serve_forever() is running in another thread, or it will
 92         deadlock.
 93         """
 94         self.__shutdown_request = True
 95         self.__is_shut_down.wait()
 96 
 97     def service_actions(self):
 98         """Called by the serve_forever() loop.
 99 
100         May be overridden by a subclass / Mixin to implement any code that
101         needs to be run during the loop.
102         """
103         pass
104 
105     # The distinction between handling, getting, processing and
106     # finishing a request is fairly arbitrary.  Remember:
107     #
108     # - handle_request() is the top-level call.  It calls
109     #   select, get_request(), verify_request() and process_request()
110     # - get_request() is different for stream or datagram sockets
111     # - process_request() is the place that may fork a new process
112     #   or create a new thread to finish the request
113     # - finish_request() instantiates the request handler class;
114     #   this constructor will handle the request all by itself
115 
116     def handle_request(self):
117         """Handle one request, possibly blocking.
118 
119         Respects self.timeout.
120         """
121         # Support people who used socket.settimeout() to escape
122         # handle_request before self.timeout was available.
123         timeout = self.socket.gettimeout()
124         if timeout is None:
125             timeout = self.timeout
126         elif self.timeout is not None:
127             timeout = min(timeout, self.timeout)
128         fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
129         if not fd_sets[0]:
130             self.handle_timeout()
131             return
132         self._handle_request_noblock()
133 
134     def _handle_request_noblock(self):
135         """Handle one request, without blocking.
136 
137         I assume that select.select has returned that the socket is
138         readable before this function was called, so there should be
139         no risk of blocking in get_request().
140         """
141         try:
142             request, client_address = self.get_request()
143         except OSError:
144             return
145         if self.verify_request(request, client_address):
146             try:
147                 self.process_request(request, client_address)
148             except:
149                 self.handle_error(request, client_address)
150                 self.shutdown_request(request)
151 
152     def handle_timeout(self):
153         """Called if no new request arrives within self.timeout.
154 
155         Overridden by ForkingMixIn.
156         """
157         pass
158 
159     def verify_request(self, request, client_address):
160         """Verify the request.  May be overridden.
161 
162         Return True if we should proceed with this request.
163 
164         """
165         return True
166 
167     def process_request(self, request, client_address):
168         """Call finish_request.
169 
170         Overridden by ForkingMixIn and ThreadingMixIn.
171 
172         """
173         self.finish_request(request, client_address)
174         self.shutdown_request(request)
175 
176     def server_close(self):
177         """Called to clean-up the server.
178 
179         May be overridden.
180 
181         """
182         pass
183 
184     def finish_request(self, request, client_address):
185         """Finish one request by instantiating RequestHandlerClass."""
186         self.RequestHandlerClass(request, client_address, self)
187 
188     def shutdown_request(self, request):
189         """Called to shutdown and close an individual request."""
190         self.close_request(request)
191 
192     def close_request(self, request):
193         """Called to clean up an individual request."""
194         pass
195 
196     def handle_error(self, request, client_address):
197         """Handle an error gracefully.  May be overridden.
198 
199         The default is to print a traceback and continue.
200 
201         """
202         print('-'*40)
203         print('Exception happened during processing of request from', end=' ')
204         print(client_address)
205         import traceback
206         traceback.print_exc() # XXX But this goes to stderr!
207         print('-'*40)
View Code

There are five classes in an inheritance diagram, four of which represent synchronous servers of four types:

RequestHandler类

所有requestHandler都继承BaseRequestHandler基类。

 1 class BaseRequestHandler:
 2 
 3     """Base class for request handler classes.
 4 
 5     This class is instantiated for each request to be handled.  The
 6     constructor sets the instance variables request, client_address
 7     and server, and then calls the handle() method.  To implement a
 8     specific service, all you need to do is to derive a class which
 9     defines a handle() method.
10 
11     The handle() method can find the request as self.request, the
12     client address as self.client_address, and the server (in case it
13     needs access to per-server information) as self.server.  Since a
14     separate instance is created for each request, the handle() method
15     can define arbitrary other instance variariables.
16 
17     """
18 
19     def __init__(self, request, client_address, server):
20         self.request = request
21         self.client_address = client_address
22         self.server = server
23         self.setup()
24         try:
25             self.handle()
26         finally:
27             self.finish()
28 
29     def setup(self):
30         pass
31 
32     def handle(self):
33         pass
34 
35     def finish(self):
36         pass
源码

创建一个socketserver 至少分以下几步

  1. First, you must create a request handler class by subclassing the BaseRequestHandlerclass and overriding its handle() method; this method will process incoming requests.   
  2. Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.
  3. Then call the handle_request() orserve_forever() method of the server object to process one or many requests.
  4. Finally, call server_close() to close the socket.
 1 import socketserver
 2 
 3 class MyTCPHandler(socketserver.BaseRequestHandler):
 4     """
 5     The request handler class for our server.
 6 
 7     It is instantiated once per connection to the server, and must
 8     override the handle() method to implement communication to the
 9     client.
10     """
11 
12     def handle(self):
13         # self.request is the TCP socket connected to the client
14         self.data = self.request.recv(1024).strip()
15         print("{} wrote:".format(self.client_address[0]))
16         print(self.data)
17         # just send back the same data, but upper-cased
18         self.request.sendall(self.data.upper())
19 
20 if __name__ == "__main__":
21     HOST, PORT = "localhost", 9999
22 
23     # Create the server, binding to localhost on port 9999
24     server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
25 
26     # Activate the server; this will keep running until you
27     # interrupt the program with Ctrl-C
28     server.serve_forever()
View Code

流程图:

 

 

让你的socketserver并发起来, 必须选择使用以下一个多并发的类

1
2
3
4
5
6
7
class socketserver.ForkingTCPServer
 
class socketserver.ForkingUDPServer
 
class socketserver.ThreadingTCPServer
 
class socketserver.ThreadingUDPServer

所以:

server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
#替换为
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        self.request.sendall(self.data.upper())


if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
    server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
    server.serve_forever()
View Code

 

思考:

     1  tcp与udp的区别(三次握手后,建立连接,双向通道,一个收,一个发,tcp每次接到数据后都会有一个应答,有了应答,新的数据就会被覆盖掉)

     2  粘包

返回顶部
posted on 2019-04-14 19:10  始终不够啊  阅读(196)  评论(0编辑  收藏  举报