python学习——socket网络编程

1.socket 概念                                                            

        socket 作用是让两台电脑实现通信。工作方式是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 网络通信都是基于 ip+port 定位到具体机器上的具体服务。每台电脑的操作系统有0-65535个port(端口),每个prot都可以独立对外提供服务。如果把一个公司比做一台电脑 ,那公司的前台就相当于ip地址, 每个员工的分机号就相当于端口,你想找公司某个人,必须先打电话到总机,然后再转分机 。


2.实现基本步骤                                                             

用流程图可以描述为:


客户端的基本步骤:

# Author: zjt

import socket

# 第一步:声明协议类型,并生产socket对象
client = socket.socket()    # 不传值的话,默认就是ipv4,和TCP

# 第二步:建立链接
# connect()只接收一个参数,但是需要传入对方ip地址以及端口号,才能准确连接,所以将两个参数放入一个元组进行传入
client.connect(("localhost",6969))

# 第三步:向服务器端发送数据
client.send(b"hello world!") # 传入发送内容Python2中可用发送字节和字符串,而Python3中只能发字节

#第四步: 接收服务器端返回结果
data = client.recv(1024)        #写入从服务器端接收数据的大小1024字节
print("收到反馈:",data)             #打印接收结果

#第五步:客户端关闭
client.close()

服务器端的基本步骤

# Author: zjt
'''
1.声明协议类型
'''

import  socket
#第一步:声明协议类型
server = socket.socket()

# 第二步:服务器绑定端口
server.bind(("localhost",6969)) #bind()也只接收一个参数,故传入参数要以元组的形式写入

#第三步: 监听端口
server.listen()
print("等电话....")

# 第四步:等待电话打进来
conn,addr = server.accept()
# print(conn,addr)
print("电话来了")

# 第五步:从客户端接收请求
data = conn.recv(1024)  # 固定一次只接收1024个字节,data存放接收的数据

print("server receive:",data)   #打印接收到的数据

# 第六步:对收到的数据处理操作,并发送回客户端
conn.send(data.upper())

# 第七步:服务器关闭
server.close()

       执行顺序:先启动服务端代码,然后启动客户端。

      上述方法实现的功能是:客户端将一串字符串传给服务器端,服务器端将收到的字符串小写变成大写,并返回给客户端。此代码只是为了演示,还有需要细节地方需要完善。


3.升级一下功能:客户端与服务器端能传递中文                                       

客户端代码:

import socket

client = socket.socket()

client.connect(("localhost",6969))

client.send("我是一段中文".encode("utf-8"))    #发送中文时先编码

data = client.recv(1024)
print("收到来自服务器端的数据:",data.decode())    #从服务器端返回的数据也是btype类型,所以此处需要解码成中文


client.close()

服务器端代码:

import socket

server = socket.socket()

server.bind(("localhost",6969))

server.listen()
print("等待ing....")

conn ,addr = server.accept()

data = conn.recv(1024)
print("收到数据",data)
print(data.decode())    #从客户端器端返回的数据是btype类型,所以此处需要decode解码成中文
conn.send(data)
print("返回给客户端")
server.close()

此时只需要主要发送和接收的时候,注意到底是解码和编码就行。

4.升级:服务器端与多个客户端能通信                                                    

    之前的连接程序,缺陷很多。每次客户端断开,服务器也跟着断开,这也导致此时不能接收其他客户端的链接,所以对服务器端的代码进行了修改,客户端代码不用变化。
import socket,os    #服务器端

server = socket.socket()

server.bind(('localhost',1008))

server.listen(5)

print("等待ing....")
while True: #此处循环保证客户端断开的情况下,服务器还处于工作状态
    conn, addr = server.accept()
    print(conn, addr)
    print("电话来了....")
    while True:
        data = conn.recv(1024)
        print("revc:",data)
        if not data:
                print("client is disconnection....")
                break
        conn.send(data.upper())

server.close()

客户端代码如下:

#-*-coding:utf-8-*-

import socket

client = socket.socket()

client.connect(('localhost',1008))

while True:
    msg = raw_input(">>>>:").strip()
    #if len(msg) == 0:continue       # 防止用户输入空让客户>端卡死
    client.send(msg.encode("utf-8"))
    data = client.recv(1024)
    print("recv:",data.decode())


client.close()

        为了实现效果,下面开启了两个客户端窗口,当其中一个客户端断开时,另一个客户端还能跟服务器保持链接。

        需要注意的是,这此代码的运行是在linux环境下运行,windows上无法出现一样的效果。

5.继续升级:实现linux目录下文件的传输(SSH)                                

    这次实现的目标是:客户端不管发送什么命令,服务器端读取指定文件,并将其发送给客户端。客户端收到文件后,读取并另存为新文件

对服务器端的代码进行修改:

# 模拟SSH客户端:执行命令并返回结果
import socket,os

server = socket.socket()

server.bind(('localhost',1008))

server.listen(5)

print("等待ing....")
while True:
    conn, addr = server.accept()
    print(conn, addr)
    print("电话来了....")

    while True:
        data = conn.recv(1024)
        print("revc:",data)
        if not data:
                print("client is disconnection....")
                break
        #res = os.popen(data).read()  #获取执行结果
        #conn.send(res.upper())
        f = open("oldboy.avi")    #此处暂时打开指定文件。
        data = f.read()
        print(len(data))    #打印读取文件的大小
        conn.sendall(data)    #sendall() 实现的是send()循环的功能,将目标文件完整的发送出去
        f.close()

客户端代码也需要修改:

#-*-coding:utf-8-*-

import socket

client = socket.socket()

client.connect(('localhost',1008))

f = open("videos",'wb')    #此处将打开文件的语句,放在循环外面,避免反复重新打开
while True:
        msg = raw_input(">>>>:").strip()
        if len(msg) == 0:continue   # 防止用户输入空字>符
        client.send(msg.encode("utf-8"))
        data = client.recv(10240000)
        #print(data)    
        f.write(data)
        f.flush()    #此处加入了刷新功能的语句

client.close()

        客户端和服务器端都开启成功后,客户端每次输入一条指令,客户端就接收一部分内容。这其中的原因是客户端接收是有上限的,每次只能接收一部分内容。实验效果如下:



6.继续升级:实现SSH传输指令,并返回结果                                            

为了实现目标,我们先完成基本目标:客户端能发送指令,服务器端收到指令后能返回结果

客户端代码:

# Author: zjt
import socket

client = socket.socket()

client.connect(("localhost",4545))

while True:
    cmd = input(">>>:").strip()

    if len(cmd)==0 :continue

    client.send(cmd.encode("utf-8"))

    cmd_res = client.recv(1024)
    print(cmd_res.decode())

client.close()
        此处需要注意的是,py3只能发送和接收btype类型,所以指令代码发出去前需要编码成btype类型,接收数据后需要解码成uncode.

客户端代码:

# Author: zjt
import socket,os

server =socket.socket()
server.bind(('localhost',4545))
server.listen()
print("等待....")

while True:

    conn,addr = server.accept()
    print("new conn:",conn)
    while True:
        data = conn.recv(1024)

        if not data:
            print("client is disconnection")
            break
        cmd_res = os.popen(data.decode()).read()
        print("before send: ",len(cmd_res))
        if len(cmd_res)==0:
            cmd_res = "cmd has no output...."
        conn.send(cmd_res.encode("utf-8"))
        print("发过去了")

server.close()

        运行后结果如下


    但是依然存在问题:当输入的指令需要返回比较大的信息时,客户端每次只能接收部分,输入下条指令时,客户端还在传输之前的信息,直到全部接收完毕,才会接收下一次的返回信息。

    解决办法:服务器端发送资料前,先计算内容长度,并结果发送给客户端。而客户端创建一个循环,接收文件大小后,每次比较当前接收了多少内容,直到实际接收内容等于预期接收的长度,则跳出本次循环执行下一条命令。

    按照解决办法,先对服务器代码修改,使其能发送文件大小:

# Author: zjt
import socket,os

server =socket.socket()
server.bind(('localhost',4545))
server.listen()
print("等待....")

while True:

    conn,addr = server.accept()
    print("new conn:",conn)
    while True:
        data = conn.recv(1024)
        if not data:
            print("client is disconnection")
            break
        cmd_res = os.popen(data.decode()).read()
        print("before send: ",len(cmd_res))
        if len(cmd_res)==0:
            cmd_res = "cmd has no output...."
        conn.send(str(len(cmd_res)).encode("utf-8"))
        # conn.send(cmd_res.encode("utf-8"))
        print("发过去了")

server.close()

        此处发送内容的长度类型为int,conn.send()发送的是btype类型。所以先将int转成字符串,然后encode发送出去,这一点技巧需要掌握。

代码效果如下:


进一步改进,客户端每次每次接收文件长度,都做记录。如果小于文件长度则继续传输,否则停止循环。

客户端代码:

# Author: zjt
import socket,os

server =socket.socket()
server.bind(('localhost',4545))
server.listen()
print("等待....")

while True:

    conn,addr = server.accept()
    print("new conn:",conn)
    while True:
        data = conn.recv(1024)
        if not data:
            print("client is disconnection")
            break
        cmd_res = os.popen(data.decode()).read()
        print("before send: ",len(cmd_res.encode("utf-8")))
        if len(cmd_res)==0:
            cmd_res = "cmd has no output...."
        conn.send(str(len(cmd_res.encode("utf-8"))).encode("utf-8"))    #发送过去的文件长度,必须是编码后的结果才行。
        conn.send(cmd_res.encode("utf-8"))
        print("发过去了")

server.close()

客户端代码:

# Author: zjt
import socket

client = socket.socket()

client.connect(("localhost",4545))

while True:
    cmd = input(">>>:").strip()

    if len(cmd)==0 :continue

    client.send(cmd.encode("utf-8"))

    file_size = int(client.recv(1024))

    print("文件大小:",file_size)

    recv_size = 0
    while file_size > recv_size:
        cmd_res = client.recv(1024)
        if len(cmd_res)==0:
            continue
        # print(cmd_res.decode())
        recv_size += len(cmd_res)
        print(recv_size)
    else:
        print("cmd_res has received",recv_size)

client.close()

实现效果:






猜你喜欢

转载自blog.csdn.net/zjt980452483/article/details/79743563