网络编程 ------ 基础

一 . 软件

  客户端: CS架构 , client --> server

  浏览器: BS架构 , browser --> server

二 . socket模块

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

  socket起源于Unix , 而Unix/Linux基本哲学之一就是"一切皆文件" , 对于文件用[打开] [读写] [关闭] 模式来操作 . socket就是该模式的一个实现 , socket即是一种特殊的文件 , 一些socket函数就是对其进行的操作

  socket和file的区别:

    -- file模块是针对某个指定文件进行[打开] [读写] [关闭]

    -- socket模块是针对 服务器端 和 客户端 Socket 进行[打开] [读写] [关闭]

import socket

# 创建服务端socket对象
server = socket.socket()

# 绑定IP和端口
server.bind(('192.168.13.155',8000))

# 后边可以等5个人
server.listen(5)

print('服务端准备开始接收客户端的连接')
# 等待客户端来连接,如果没人来就傻傻的等待。
# conn是客户端和服务端连接的对象(伞),服务端以后要通过该对象进行收发数据。
# addr是客户端的地址信息。
# #### 阻塞,只有有客户端进行连接,则获取客户端连接然后开始进行通信。
conn,addr = server.accept()

print('已经有人连接上了,客户端信息:',conn,addr)


# 通过对象去获取(王晓东通过伞给我发送的消息)
# 1024表示:服务端通过(伞)获取数据时,一次性最多拿1024字节。
data = conn.recv(1024)
print('已经有人发来消息了',data)


# 服务端通过连接对象(伞)给客户端回复了一个消息。
conn.send(b'stop')

# 与客户端断开连接(放开那把伞)
conn.close()

# 关闭服务端的服务
server.close()
View Code

  服务端和客户端的操作:

import socket

server = socket.socket()

server.bind(("IP地址",端口))

server.listen(服务次数)

while 1:
    conn,addr = server.accept()   # 等待与客户端连接
    
    while 1:
        data = conn.recv(最大字节数 = 1024)
        if data == b"exit":
            break

        result = data + b"任意"
        conn.send(result)     # 返回给客户端的内容
    
    conn.close()
客户端
import socket

sk = socket.socket()

sk.connect("服务端IP",服务端端口)

while 1:
    name = input(">>>")
    sk.send(name.encode("utf-8"))   # 传给服务端必须是字节
    
    if name == "exit":
        break

    result = sk.recv(1024)          # 服务端回复给客户端的也是字节
    print(result.decode("utf-8"))

sk.close()
客户端

  总结:

    python3 : send / recv 都是字节

    python2 : send / recv 都是字符串

    服务端 :

      accept : 阻塞 , 等待客户端来连接

      recv : 阻塞 , 等待客户端发来数据

    客户端 :

      connect : 阻塞 ,一直在连接 , 直到连接成功才往下运行其他代码

      recv : 阻塞 , 等待服务端发来数据    

 三 . Tcp协议和Udp协议

  Tcp : 可靠的 , 面向连接的协议 , 传输效率低全双工通信 , 面向字节流 . 使用Tcp的应用 : Web浏览器 , 电子邮件 , 文件传输程序.

  Udp : 不可靠的 , 无连接的服务 , 传输效率高 , 一对一 , 一对多 , 多对一 , 多对多 , 面向报文 , 尽最大努力服务 , 无阻塞控制 . 使用UDP的应用 : 域名系统(DNS) , 视频流 , IP语音(Volp) . 

四 . 套接字初使用

  1. 基于TCP协议的socket

    tcp是基于链接的 , 必须先启动服务端 , 然后再启动客户端去链接服务端 . 

  server端 :

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
sk.listen()          #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024)  #接收客户端信息
print(ret)       #打印客户端信息
conn.send(b'hi')        #向客户端发送信息
conn.close()       #关闭客户端套接字
sk.close()        #关闭服务器套接字(可选)
View Code

  client端 :

import socket
sk = socket.socket()           # 创建客户套接字
sk.connect(('127.0.0.1',8898))    # 尝试连接服务器
sk.send(b'hello!')
ret = sk.recv(1024)         # 对话(发送/接收)
print(ret)
sk.close()            # 关闭客户套接字
View Code

  问题 : 在重启服务端时可能会遇到

 

  解决方法:

#加入一条socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
sk.listen()          #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024)   #接收客户端信息
print(ret)              #打印客户端信息
conn.send(b'hi')        #向客户端发送信息
conn.close()       #关闭客户端套接字
sk.close()        #关闭服务器套接字(可选)
View Code

  2 . 基于UDP协议的socket

    udp是无链接的 , 启动服务之后可以直接接受消息 , 不需要提前建立链接.

  server端:

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字
udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
udp_sk.close()                         # 关闭服务器套接字
View Code

  client端:

import socket

ip_port=('127.0.0.1',9000)

udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)

back_msg,addr=udp_sk.recvfrom(1024)

print(back_msg.decode('utf-8'),addr)
View Code

  QQ聊天:

#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server_sock.bind(ip_port)

while True:
    qq_msg,addr=udp_server_sock.recvfrom(1024)
    print('来自[%s:%s]的一条消息:33[1;44m%s33[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
    back_msg=input('回复消息: ').strip()

    udp_server_sock.sendto(back_msg.encode('utf-8'),addr)

server
server
#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server_sock.bind(ip_port)

while True:
    qq_msg,addr=udp_server_sock.recvfrom(1024)
    print('来自[%s:%s]的一条消息:33[1;44m%s33[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
    back_msg=input('回复消息: ').strip()

    udp_server_sock.sendto(back_msg.encode('utf-8'),addr)

server
client

  时间服务器:

# _*_coding:utf-8_*_
from socket import *
from time import strftime

ip_port = ('127.0.0.1', 9000)
bufsize = 1024

tcp_server = socket(AF_INET, SOCK_DGRAM)
tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_server.bind(ip_port)

while True:
    msg, addr = tcp_server.recvfrom(bufsize)
    print('===>', msg)

    if not msg:
        time_fmt = '%Y-%m-%d %X'
    else:
        time_fmt = msg.decode('utf-8')
    back_msg = strftime(time_fmt)

    tcp_server.sendto(back_msg.encode('utf-8'), addr)

tcp_server.close()

server
server
#_*_coding:utf-8_*_
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024

tcp_client=socket(AF_INET,SOCK_DGRAM)

while True:
    msg=input('请输入时间格式(例%Y %m %d)>>: ').strip()
    tcp_client.sendto(msg.encode('utf-8'),ip_port)

    data=tcp_client.recv(bufsize)

client
client

五 . 黏包

  同时执行多条命令之后 , 得到的结果很可能只有一部分 , 在执行其他命令的时候又接收到之前执行的另外一部分结果 , 这种现象就是黏包 . 

  注意 : 只有TCP有黏包现象 , UDP永远不会黏包

  TCP是面向连接的 , 面向流的 , 提供高可靠性服务 . 

  收发两端都要有一一承兑的socket . 因此 , 发送端为了将多个发往接收端的包 , 更有效的发到对方 , 使用了优化方法 , 将多次间隔较小且数据量小的数据 , 合并成一个大的数据块 , 然后进行封包 . 

  1. 发生黏包的两种情况

    情况一 : 发送方的缓存机制

      发送端需要等缓冲区满才发送出去 , 造成黏包(发送数据时间间隔很短 , 数据量很小 , 会合到一起 , 产生黏包)

#_*_coding:utf-8_*_
from socket import *
ip_port=('127.0.0.1',8080)

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)


conn,addr=tcp_socket_server.accept()


data1=conn.recv(10)
data2=conn.recv(10)

print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))

conn.close()

服务端
server
#_*_coding:utf-8_*_
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)


s.send('hello'.encode('utf-8'))
s.send('egg'.encode('utf-8'))

客户端
client

    情况二 : 接收方的缓存机制

      接受方不及时接受缓冲区的包 , 造成多个包接受(客户端发送了一段数据 , 服务端只收了一小部分 , 服务端下次再收的时候还是从缓冲区拿上次遗留的数据 , 产生黏包)

#_*_coding:utf-8_*_
from socket import *
ip_port=('127.0.0.1',8080)

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)


conn,addr=tcp_socket_server.accept()


data1=conn.recv(2) #一次没有收完整
data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的

print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))

conn.close()
server
#_*_coding:utf-8_*_
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)


s.send('hello egg'.encode('utf-8'))
client

    总结 :

      黏包现象只发生在tcp协议中 :

        1. 从表面上看 , 黏包问题主要是发送方和接受方的缓存机制 , tcp协议面向流通信的特点

        2. 实际上 , 主要还是因为接收方不知道消息之间的界限 , 不知道一次性提取多少字节的数据所造成的

六 . 黏包的解决的方案

1. struct模块

import struct


res=struct.pack("i","")

print(res)
print(len(res))


obj=struct.unpack("i",res)
print(obj[0])

"""
结果:
b'xb3xb5Vx07'
4
123123123
"""
View Code

  该模型可以把一个类型转换成固定长度的bytes(固定长度4)

2. subprocess模块

import subprocess

res=subprocess.Popen("dir",
                     shell=True,
                     stderr=subprocess.PIPE,
                     stdout=subprocess.PIPE)


print(res.stdout.read().decode("gbk"))

"""
结果:
 驱动器 C 中的卷是 Windows
 卷的序列号是 A786-D135

 C:UsersAdministratorPycharmProjectsuntitled1基础28day 的目录

2018/09/04  15:50    <DIR>          .
2018/09/04  15:50    <DIR>          ..
2018/09/04  15:50               218 1.py
2018/09/04  14:53                 0 __init__.py
               2 个文件            218 字节
               2 个目录 215,588,208,640 可用字节
"""
View Code

3. 使用struct模块解决黏包

  借助struct模块 , 我们可以知道长度数字可以转换成一个标准大小的4字节数字 . 因袭可以利用这个特点来预先发送长度 . 

发送时 接收时
先发送struct转换好的数据长度4字节 先接受4字节使用struct转换成数字来获取要接收的数据长度
再发送数据 再按照长度接受数据
import socket
import subprocess

server = socket.socket()

server.bind(('127.0.0.1',8008))

server.listen(5)

while True:
    print("server is working.....")
    conn,addr = server.accept()
    # 字节类型
    while True:
        # 针对window系统
        try:
            cmd = conn.recv(1024).decode("utf8") # 阻塞

            if cmd == b'exit':
                break

            res=subprocess.Popen(cmd,
                             shell=True,
                             stderr=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             )
            # print("stdout",res.stdout.read())
            # print("stderr",res.stderr.read().decode("gbk"))
            out=res.stdout.read()
            err=res.stderr.read()

            print("out响应长度",len(out))
            print("err响应长度",len(err))
            if err:
                 import struct
                 header_pack = struct.pack("i", len(err))
                 conn.send(header_pack)
                 conn.send(err)
            else:
                 #构建报头
                 import struct
                 header_pack=struct.pack("i",len(out))
                 print("header_pack",header_pack)
                 # # 发送报头
                 conn.send(str(len(out)).encode("utf8"))
                 # 发送数据
                 conn.send(out)

        except Exception as e:
            break

    conn.close()
server
import socket
import struct
sk = socket.socket()

sk.connect(('127.0.0.1',8008))

while 1:
    cmd = input("请输入命令:")
    sk.send(cmd.encode('utf-8')) # 字节
    if cmd=="":
        continue
    if cmd == 'exit':
        break

    header_pack=sk.recv(4)
    data_length=struct.unpack("i",header_pack)[0]
    print("data_length",data_length)

    data_length=int(sk.recv(1024).decode("utf8"))
    print("data_length",data_length)

    recv_data_length=0
    recv_data=b""

    while recv_data_length<data_length:
        data=sk.recv(1024)
        recv_data_length+=len(data)
        recv_data+=data

    print(recv_data.decode("gbk"))

sk.close()
client

 4. 解决黏包的两种方法

方法一:

import socket
import json

sock = socket.socket()
sock.bind(("121.12.11.11",5555))
sock.listen(5)

while 1:
    print("server is working....")
    conn,addr = sock.accept()
    
    while 1:
        data = conn.recv(1024).decode("utf-8")
        file_info = josn.loads(data)

        # 文件信息
        action = file_info.get("action")
        filename = file_info.get("filename")
        filesize = file_info.get("filesize")

        connsend(b"succes")

        # 接收文件数据
        with open("put/" +filename , "wb") as f:
            recv_data_length = 0
            while recv_data_lengh < filesize:
                data = conn.recv(1024)
                recv_data_length += len(data)
                f.write(data)
                print("文件总大小: %s , 已接收 %s" %(filesize,recv_data_length))
        print("接收成功!")
        break
    
    sock.close()
FTP -- server
import socket
import os
import json


sock=socket.socket()
sock.connect(("121.12.11.11",5555))

while 1:
    cmd = ("请输入命令:")
    action,filename = cmd.strip().split(" ")

    file_info = {"action":action,"filename":filename,"filesize":filesize}
    file_info_josn = josn.dumps(file_info).encode("utf-8")
    sock.send(file_info_josn)

    # 确认服务端接收到了文件信息
    code = sock.recv(1024).decode("utf-8")
    if code == "succes":
        # 发送文件数据
        with open("filename","rb") as f:
            for line in f:
                sock.send(line)
    else:
        print("服务器异常!")
    break

sock.close()
FTP -- client

方法二:

import struct
import socket
import josn
import hashlib


sock = socket.socket()
sock.bind(("121.21.12.11",5555))
sock.listen(5)

while 1:
    print("server is working...")
    conn,addr = sock.accept()
    
    while 1:
        
        # 接收josn的打包长度
        file_info_length_pack = conn.recv(4)
        file_info_length = struct.unpack("i",file_info_length_pack)[0]

        # 接收josn字符串
        file_info_josn = conn.recv(file_info_length)
        file_info = josn.loads(file_info_josn)

        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()
                
                # md5摘要
                md5.update(data)
                print("文件总大小:%s,已成功接收%s" %(filesize,recv_data_length))
        print("接收成功!")
        conn.send(b"ok")
        
        md5_val = md5.hexdigest()
        client_md5 = conn.recv(1024).decode("utf-8")
        
        if md5_val == client_md5:
            conn.send(b"203")

        else:
            conn.send(b"204")
    
        break
    
    sock.close()
FTP -- server
import socket
import os
import json
import struct
import hashlib


sock=socket.socket()
sock.connect(("121.21.12.11",5555))

while 1:
    cmd = input("请输入命令:")
    action , filename = cmd.strip().split(" ")
    filesize = os.patn.getsize(filename)

    file_info = {"action":action,"filename":filename,"filesize":filesize}
    file_info_josn = josn.dumps(file_info).encode("utf-8")

    res = struct.pack("i",len(file_info_josn))
    
    # 发送file_info_josn 的打包长度
    sock.send(res)

    # 发送 file_info_josn字节串
    sock.send(file_info_josn)

    md5 = hashlib.md5()
    # 发送文件数据
    with open(filename,"rb") as f:
        for line in f:
            sock.send(line)
            md5.update(line)
    data = sock.recv(1024)
    md5_val = md5.hexdigest()
    sock.send(md5_val.encode("utf-8"))
    is_val = sock.recv(1024).decode("utf-8")
    
    if is_val == "203":
        print("文件上传成功!")

    else:
        print("文件上传失败!")
    break

sock.close()
FTP -- client

七 . 并发编程(socketserver模块)

import socketserver


class Myserver(socketserver.BaseRequestHandler):
    def handle(self):

        # 字节类型
        while 1:

            # 针对window系统
            try:
                print("等待信息")
                data = self.request.recv(1024)  # 阻塞

                # 针对linux
                if len(data) == 0:
                    break

                if data == b'exit':
                    break

                response = data + b'SB'
                self.request.send(response)

            except Exception as e:
                break

        self.request.close()

# 1 创建socket对象 2 self.socket.bind()  3 self.socket.listen(5)
server=socketserver.ForkingUDPServer(("127.0.0.1",8899),Myserver)

server.serve_forever()
FTP -- server
import socket

sk = socket.socket()

sk.connect(('127.0.0.1',8899))

while 1:
    name = input(">>>>:")
    sk.send(name.encode('utf-8')) # 字节

    response = sk.recv(1024) # 字节
    print(response.decode('utf-8'))
FTP -- client
原文地址:https://www.cnblogs.com/xiangweilai/p/9579066.html