网络编程-TCP/UDP

网络基础之网络协议

http://www.cnblogs.com/linhaifeng/articles/5937962.html

基于tcp协议实现简单的套接字通信

原理图:
这里写图片描述

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

socket模块:
 import socket
 socket.socket(socket_family,socket_type,protocal=0)
 socket_family     #可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。

 #获取tcp/ip套接字
 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

 #获取udp/ip套接字
 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

 #由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我的命名空间里了,这样能 大幅减短我们的代码。
#例如tcpSock = socket(AF_INET, SOCK_STREAM)
服务端套接字函数
s.bind()    绑定(主机,端口号)到套接字
s.listen()  开始TCP监听
s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数
s.connect()     主动初始化TCP服务器连接
s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数
s.recv()            接收TCP数据
s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom()        接收UDP数据
s.sendto()          发送UDP数据
s.getpeername()     连接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()      返回指定套接字的参数
s.setsockopt()      设置指定套接字的参数
s.close()           关闭套接字

面向锁的套接字方法
s.setblocking()     设置套接字的阻塞与非阻塞模式
s.settimeout()      设置阻塞套接字操作的超时时间
s.gettimeout()      得到阻塞套接字操作的超时时间

面向文件的套接字的函数
s.fileno()          套接字的文件描述符
s.makefile()        创建一个与该套接字相关的文件
TCP服务端:
import socket

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #TCP协议
phone=socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  #开启tcp连接重用功能
phone.bind(('127.0.0.1',8080)) #把地址绑定到套接字
phone.listen(5) #监听链接数

while True:
    print('starting...')
    conn,client_addr=phone.accept()   #连接后得到一个连接通道,和连接的地址
    print(client_addr)

    while True:
        try:
            data=conn.recv(1024)  #在在通道中收信息的时候一次只收1024字节
            if not data:break  #针对的是linux系统
            print('>>>>%s' %data)
            conn.send(data)
        except ConnectionResetError:
            print('断开连接')
            break
    conn.close()  #断开连接

phone.close()  #系统回收连接
TCP客户端:
import socket

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #TCP协议
phone.connect(('127.0.0.1',8080))  #元组

while True:
    vl_info=input('>>>')
    if vl_info == 'exit':break
    if not vl_info:continue
    phone.send(vl_info.encode('utf-8'))  #发消息的时候必须要转换为bytes
    data=phone.recv(1024)  #收的时候一次收1024字节
    print(data.decode('utf-8'))

phone.close()
实现ssh远程执行命令

服务端:

from socket import *
import subprocess

server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8090))
server.listen(5)

while True:
    conn,client_addr=server.accept()
    print(client_addr)

    while True:
        try:
            cmd=conn.recv(1024)
            if not cmd:break

            #ls -l;sadfasdf;pwd;echo 123
            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE
                             )
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()

            cmd_res=stdout+stderr
            print(len(cmd_res))
            conn.send(cmd_res)
        except ConnectionResetError:
            break
    conn.close()

server.close()

客户端:

from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8090))

while True:
    cmd=input('>>: ').strip()
    if not cmd:continue
    client.send(cmd.encode('utf-8'))
    data=client.recv(1024)
    print(data.decode('gbk'))

client.close()
粘包现象

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
这里写图片描述

解决粘包问题1
struct打包解包模块
import struct

# 打包工具
headers=struct.pack('i',123123)
print(headers,len(headers))

#注意解包后得到的是一个元组
res=struct.unpack('i',headers)
print(res[0])

print('王王王'.encode('utf-8'))    #把字符串转换为bytes
print(b'\xe5\x88\x98\xe7\xa5\xba\xe7\xa5\xa5'.decode('utf-8'))   #把bytes转换为utf-8格式

#如果报头过大:可以使用json模块序列化,然后得到字符串再使用struct模块打包
import json

headers={
    'filepath':'a.txt',
    'md5':'123sfsaddvx23sd23',
    'total_size':123123123123123123412419847184678146154612541827461872641846127841264127
}

headers_json=json.dumps(headers)
print(headers_json)

print(b' \xc7\xfd\xb6\xaf\xc6\xf7 D \xd6\xd0\xb5\xc4\xbe\xed\xca\xc7 \xcf\xb5\xcd\xb3\r\n \xbe\xed\xb5\xc4\xd0\xf2\xc1\xd0\xba\xc5\xca\xc7 C001-01AB\r\n\r\n D:\\python2'.decode('utf-8'))

这里写图片描述

#server端
import socket
import subprocess
import struct

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #TCP协议
phone.bind(('127.0.0.1',8080))
phone.listen(5)

while True:
    print('starting...')
    conn,client_addr=phone.accept()
    print(client_addr)

    while True:
        try:
            # 1、收client端发送的命令
            cmd=conn.recv(1024)  #在收信息的时候一次只收1024字节
            if not cmd:break  #针对的是linux系统
            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()

            #2、制作包头,先发送包头,避免粘包
            cmd_size=int(len(stdout))+int(len(stderr))
            total_size=struct.pack('i',cmd_size)
            conn.send(total_size)

            # 3、发送命令执行的结果
            conn.send(stdout+stderr)
        except ConnectionResetError:
            print('断开连接')
            break
    conn.close()  #断开连接
phone.close()  #系统回收连接

#client端
import socket
import struct

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #TCP协议
phone.connect(('127.0.0.1',8080))  #元组

while True:
    cmd=input('>>>')
    if cmd == 'exit':break
    if not cmd:continue
    # 1、把命令传给server端
    phone.send(cmd.encode('utf-8'))  #发消息的时候必须要转换为bytes

    #2、先收包头使用打包模块固定4个字节(struct)
    hearder=phone.recv(4)
    total_size=struct.unpack('i',hearder)[0]

    #3、 收server端执行完命令的结果
    recv_size=0
    data=b''
    while recv_size < total_size:
        res=phone.recv(1024)  #收的时候一次收1024字节
        data+=res
        recv_size+=len(res)
    print(data.decode('gbk'))

phone.close()

#如果client端发送一个空,client端和server端都会卡住
# 原因:client端会send一个空给自己操作系统的一个缓存(用户态到内核态的转换),然后操作系统的缓存收到了一个空(空也就是没有),操作系统的缓存收到一个空,也就不会发送给server端,也就是server端什么也没收到,会在等client端发送信息,而client端recv也在等server端发送信息。

#accept和recv会阻塞:
#在建立连接的时候,需要等待client去连接,1、等待数据来的时间 加上2、数据来了以后,等待程序把数据拷贝给自己的时间

# send不会阻塞的原因:send会把数据拷贝给操作系统,send拷贝的过程非常快,只涉及到本地硬盘拷贝到内存的时间

#解决粘包的问题:制作包头,给client发送信息的收发送一个固定的包头长度,让client端收的时候先收包头的信息
解决粘包问题2
#server端
import socket
import subprocess
import struct
import json

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #TCP协议
phone.bind(('127.0.0.1',8080))
phone.listen(5)

while True:
    print('starting...')
    conn,client_addr=phone.accept()
    print(client_addr)

    while True:
        try:
            # 1、收client端发送的命令
            cmd=conn.recv(1024)  #在收信息的时候一次只收1024字节
            if not cmd:break  #针对的是linux系统
            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            total_size = len(stdout) + len(stderr)
            #2、使用json序列化制作包头,然后先发包头的大小
            hearder_dic={
                'filename':'a.txt',
                'md5':'123saf12asqe4',
                'file_size':total_size
            }
            hearder_json=json.dumps(hearder_dic)
            hearder_bytes=hearder_json.encode('utf-8')
            hearder_size = struct.pack('i',len(hearder_bytes))
            # 3、发送包头长度
            conn.send(hearder_size)  #包头大小

            #3.1发送包头
            conn.send(hearder_bytes) #包头内容

            # 4、发送命令执行的结果
            conn.send(stdout+stderr)
        except ConnectionResetError:
            print('断开连接')
            break
    conn.close()  #断开连接
phone.close()  #系统回收连接

#client端
import socket
import struct
import json
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #TCP协议
phone.connect(('127.0.0.1',8080))  #元组

while True:
    cmd=input('>>>')
    if cmd == 'exit':break
    if not cmd:continue
    # 1、把命令传给server端
    phone.send(cmd.encode('utf-8'))  #发消息的时候必须要转换为bytes

    #2、先收包头使用打包模块固定4个字节(struct)
    hearder=phone.recv(4)
    hearder_size=struct.unpack('i',hearder)[0]  #解包,得到hearder的大小

    #3、再收包头信息
    hearder_bytes=phone.recv(hearder_size)    #使用hearder大小,收取hearder_info(bytes)
    hearder_json=hearder_bytes.decode('utf-8')
    hearder_info=json.loads(hearder_json) #反序列化,得到hearder的dict
    # print(hearder_info)  #{'filename': 'a.txt', 'md5': '123saf12asqe4', 'file_size': 446}

    #4、 收server端执行完命令的结果
    recv_size=0
    data=b''
    while recv_size < hearder_info['file_size']:
        res=phone.recv(1024)  #收的时候一次收1024字节
        data+=res
        recv_size+=len(res)
    print(data.decode('gbk'))

phone.close()
实现上传下载文件
#server端:
import socket
import json
import os
import struct
SHARE_DIR=r'D:\python20期课程\day8\8 上传下载文件\SERVICE'

class FtpServer:

    def __init__(self,host,port):
        self.host=host
        self.port=port
        self.server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.server.bind((self.host,self.port))
        self.server.listen(5)

    def server_forever(self):
        print('server starting....')
        while True:
            self.conn,self.client_addr=self.server.accept()
            print(self.client_addr)
            while True:
                try:
                    data=self.conn.recv(1024)
                    if not data:break
                    params=json.loads(data.decode('utf-8'))
                    # params=data.decode('utf-8')
                    cmd=params[0]
                    print(params)
                    if hasattr(self,cmd):
                        func=getattr(self,cmd)
                        func(params)
                    else:
                        print('\033[45mnot %s valuse exists\033[0m' %params)
                    print('==>end')
                except ConnectionResetError:
                    break
            self.conn.close()

    def get(self,params):
        filename=params[1]
        filepath=os.path.join(SHARE_DIR,filename)
        if os.path.exists(filepath):
            #1、制作报头
            hearder={
                'filename':filename,
                'md5':'12312312',
                'filesize': os.path.getsize(filepath)
            }
            hearder_json=json.dumps(hearder)
            hearder_bytes=hearder_json.encode('utf-8')
            #2、先发报头的长度
           self.conn.send(struct.pack('i',len(hearder_bytes)))
            #3、发送报头
            self.conn.send(hearder_bytes)

            #4、发送真实的数据
            with open(filepath,'rb') as f:
                for line in f:
                    self.conn.send(line)

if __name__=='__main__':
    server=FtpServer('127.0.0.1',8081)
    server.server_forever()

#client端
import socket
import struct
import json
import os
import time
DOWNLOAD_DIR=r'D:\python20期课程\day8\8 上传下载文件\CLIENT'
class FtpClient:
    def __init__(self,host,port):
        self.host=host
        self.port=port
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.client.connect((self.host,self.port))
    def interactive(self):
        while True:
            data=input('>>>:').strip()
            if not data:continue
            params=data.split()
            cmd=params[0]
            if hasattr(self,cmd):
                func=getattr(self,cmd)
                func(params)
    def get(self,params):
        params_json=json.dumps(params)
        self.client.send(params_json.encode('utf-8'))

        #1、先接收报头的长度
        hearder_by=self.client.recv(4)
        headers_size=struct.unpack('i',hearder_by)[0]

        #2、再收报头
        headers_bytes=self.client.recv(headers_size)
        headers_json=headers_bytes.decode('utf-8')
        headers_dic=json.loads(headers_json)
        filename=headers_dic['filename']
        filesize=headers_dic['filesize']
        filepath=os.path.join(DOWNLOAD_DIR,filename)

        #3、再收取真实的数据
        with open(filepath,'wb') as f:
           recv_size=0
           while recv_size < filesize:
               line=self.client.recv(1024)
               recv_size+=len(line)
               f.write(line)
               self.progress(recv_size/filesize)

    def progress(self,percent,width=50):
        if percent <=1:
            percent=1
        show_str=('[%%-%ds]' %width) % ('#' * int(width*percent))
        print('\033[45m\r%s %d%%\033[0m' %(show_str,int(100*percent)),end='')

if __name__=='__main__':
    client=FtpClient('127.0.0.1',8081)
    client.interactive()

socketserver并发编程

socketserver.ThreadingTCPServer   #启动多线程,实现并发编程
import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        while True:
            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',9000

    # server=socketserver.TCPServer((HOST,PORT),MyTCPHandler)  #把定义的类当作参数传入socketserver这个类
    server=socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler)  #使用ThreadingTCPServer就可以实现并发方法

    server.serve_forever()  #启动一个监听

基于UDP协议的套接字

UDP关键字
SOCK_DGREM #数据报协议
server.recvfrom()   #udp使用recvfrom来接收消息
sendto(‘vlues’,sendto ipaddress)   #udp使用sendto来发消息

ps:注意,udp接受消息不会产生粘包现象,原因是客户端发多少,服务端收多少,如果客户端发的1024字节,服务器端最大收512字节,那么剩下的数据会丢弃,udp收到消息也是一条一条的去处理的,可以开多个客户端给服务端发送,(给我们感觉到是并发的)其实不会实现并发的现象,只是数据量小,收发快。

#服务端
from socket import *
server=socket(AF_INET,SOCK_DGRAM)  #数据报协议

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

while True:
    data,client_addr=server.recvfrom(1024)   #udp使用recvfrom 来收消息
    print(data,client_addr)
    server.sendto(data.upper(),client_addr)


#客户端
from socket import *

client=socket(AF_INET,SOCK_DGRAM)
while True:
    msg=input('>>>').strip()
    client.sendto(msg.encode('utf-8'),('127.0.0.1',9090))
    data=client.recvfrom(1024)
    print(data)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值