基于TCP协议的socket

一、基本用法

1.1 服务端

import socket

# 1.买手机
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# server = socket.socket() 默认为网络类型的 TCP协议
# 参数1指定socket类型为AF_INET,表示为网络类型
# 参数2指定的传输协议为SOCK_STREAM表示TCP协议,  SOCK_DGRAM为UDP协议

# 2.插入手机卡
server.bind(("127.0.0.1",8080))
# 作为服务器必须要有自己的IP和端口号,并且不应该发生变化
# 127.0.0.1 为回送地址,表示当前电脑本身
# IP一定是本机IP,本机可能会有多个IP(无线|有线)
# 注意:需要参数是一个元组,端口就是普通证书

# 3.手机开始待机
server.listen()
# 开机监听8080端口,看着这个端口有没有数据传输过来

while True:
    # 4.接电话
    client_socket, close_addr = server.accept()  # 完成了三次握手
    print("握手成功")
    # 接受连接请求
    # client表示客户端的socket
    # addr 表示客户端的地址信息
    # accept 是一个阻塞函数,会一直等到有客户链接过来,再继续执行

    while True:
        try:
            # 5.收发消息
            # 发送数据与接收数据,对方可能会异常下线,都放入try中
            data = client_socket.recv(1024)
            # 在linux,windows中,对方如果强行下线了,服务器不会抛出异常,只会收到空消息
            # 故需要嘉盛if判断,如果为空,则意味着对方下线了,应该关闭链接,跳出循环
            if not data:
                client_socket .close()
                break
            print("客户端发来的数据:", data)
            msg = input("输入发送的信息:").strip()
            client_socket.send(msg.encode("utf-8"))
        except ConnectionResetError as e:
            print("%s %s" % (close_addr[0],close_addr[1]),e)
            # 断开链接
            client_socket.close()  # 完成四次挥手
            break

# 6.关机,但是服务器一般不会关机
# server.close()

1.2 客户端

import socket

# 1.买电话
client = socket.socket()

# 2.打电话
client.connect(("127.0.0.1",8080))
# 作为客户端,IP和端口可以变化,所有系统会自动随机端口给客户端

# 添加循环,用来重复收发数据
while True:
    msg = input("输入发送的信息:").strip()
    if not msg: continue
    client.send(msg.encode("utf-8")) # 3.开始通话,发送与接收数据
    data = client.recv(1024)
    print("服务端发送的信息",data.decode("utf-8"))

client.close()   # 4.关机

二、常见函数

# 服务端套接字函数
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()      得到阻塞套接字操作的超时时间

三、简易的循环连接和循环通信

"""
客户端, 可随时开启,随时关闭
"""
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    msg = input('>>>:').encode('utf-8')
    if len(msg) == 0:continue
    client.send(msg)
    data = client.recv(1024)
    print(data)


"""
服务端
    固定的ip和port
    24小时不间断提供服务
"""
import socket

server = socket.socket()  # 生成一个对象
server.bind(('127.0.0.1',8080))  # 绑定ip和port
server.listen(5)  # 半连接池

while True:
    conn, addr = server.accept()  # 等到别人来  conn就类似于是双向通道
    print(addr)  # ('127.0.0.1', 51323) 客户端的地址
    while True:
        try:
            data = conn.recv(1024)
            print(data)  # b''  针对mac与linux 客户端异常退出之后 服务端不会报错 只会一直收b''
            if len(data) == 0:break
            conn.send(data.upper())
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()

四、socket使用中常见问题及解决办法

4.1 端口占用

4.1.1 问题发生原因

1.可能是由于你已经启动了服务器程序,却又再次启动了服务器程序,同一个端口不能被多个进程使用导致!
2.三次握手或四次挥手时,发生了异常导致对方程序已经结束而服务器任然处于time_wait状态导致!
3.在高并发的场景下,由于链接的客户端太多,也会产生大量处于time_wait状态的链接

4.1.2 解决方案

# 第1种原因,很简单关闭之前运行的服务器即可
"""
如果已经开启了服务器  再次运行将会抛出 端口占用异常  把之前开启的服务器关掉即可

有些情况   明明已经关闭了进程 但是 还是端口占用  
可能是进程正在后台运行  可以通过端口查出进程  进行关闭
windows下
netstat -ano  | findstr 9898
tasklist | findstr 进程id    拿到进程名称
taskkill /f /t /im 进程名称
"""

# 第2,3中原因导致的问题,有两种解决方案:

#加入一条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()        #关闭服务器套接字(可选)

4.2 粘包问题

4.2.1 什么是粘包

粘包指的是数据与数据之间没有明确的分界线,导致不能正确读取数据!

4.2.2 发生情形

粘包 仅发生在TCP协议中
  1. 发送端 发送的数据量小 并且间隔短 会粘
  2. 接收端 一次性读取了两次数据的内容 会粘
  3. 接收端 没有接收完整 剩余的内容 和下次发送的粘在一起

无论是那种情况,其根本原因在于 接收端不知道数据到底有多少

4.2.3 解决方案

# 先发长度给对方 再发真实数据

"""
发送端
    1. 使用struct 将真实数据的长度转为固定的字节数据
    2. 发送长度数据
    3. 发送真实数据

接收端
    1.先收长度数据 字节数固定
    2.再收真实数据 真实可能很长 需要循环接收

发送端和接收端必须都处理粘包 才算真正的解决了
"""

# 客户端
import socket
import struct
c = socket.socket()
c.connect(("127.0.0.1",8888))
while True:
    cmd = input(">>>:").strip()
    c.send(cmd.encode("utf-8"))

    data = c.recv(4)
    length = struct.unpack("i",data)[0]
    
    print(length)
    size = 0
    res = b""
    while size < length:
        temp = c.recv(1024)
        size += len(temp)
        res += temp
    print(res.decode("gbk"))

# 服务端
import socket
import subprocess
import struct
server = socket.socket()
server.bind(("127.0.0.1",8888))
server.listen()

while True:
    client, addr = server.accept()
    while True:
        cmd = client.recv(1024).decode("utf-8")
        p = subprocess.Popen(cmd,shell=True,stdout=-1,stderr=-1)
        data = p.stdout.read()+p.stderr.read()
        length = len(data)
        len_data = struct.pack("i",length)
        client.send(len_data)

        print(length)
        client.send(data)

4.2.4 自定义报头解决粘包

什么是报头:当需要在传输数据时 传呼一些额外参数时就需要自定义报头例如我们要实现文件上传下载,不光要传输文件数据,还需要传输文件名字,md5值等等,报头本质是一个json 数据。

# 客户端
import socket
import struct
import json

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    msg = input('>>>:').encode('utf-8')
    if len(msg) == 0:continue
    client.send(msg)
    # 1.先接受字典报头
    header_dict = client.recv(4)
    # 2.解析报头 获取字典的长度
    dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0
    # 3.接收字典数据
    dict_bytes = client.recv(dict_size)
    dict_json = json.loads(dict_bytes.decode('utf-8'))
    # 4.从字典中获取信息
    print(dict_json)
    recv_size = 0
    real_data = b''
    while recv_size < dict_json.get('file_size'):  # real_size = 102400
        data = client.recv(1024)
        real_data += data
        recv_size += len(data)
    print(real_data.decode('gbk'))

# 服务端
import socket
import subprocess
import struct
import json


server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)


while True:
    conn, addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            if len(cmd) == 0:break
            cmd = cmd.decode('utf-8')
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            res = obj.stdout.read() + obj.stderr.read()
            d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'}
            json_d = json.dumps(d)
            # 1.先制作一个字典的报头
            header = struct.pack('i',len(json_d))
            # 2.发送字典报头
            conn.send(header)
            # 3.发送字典
            conn.send(json_d.encode('utf-8'))
            # 4.再发真实数据
            conn.send(res)
            # conn.send(obj.stdout.read())
            # conn.send(obj.stderr.read())
        except ConnectionResetError:
            break
    conn.close()# 客户端
import socket
import struct
import json

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    msg = input('>>>:').encode('utf-8')
    if len(msg) == 0:continue
    client.send(msg)
    # 1.先接受字典报头
    header_dict = client.recv(4)
    # 2.解析报头 获取字典的长度
    dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0
    # 3.接收字典数据
    dict_bytes = client.recv(dict_size)
    dict_json = json.loads(dict_bytes.decode('utf-8'))
    # 4.从字典中获取信息
    print(dict_json)
    recv_size = 0
    real_data = b''
    while recv_size < dict_json.get('file_size'):  # real_size = 102400
        data = client.recv(1024)
        real_data += data
        recv_size += len(data)
    print(real_data.decode('gbk'))

# 服务端
import socket
import subprocess
import struct
import json


server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)


while True:
    conn, addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            if len(cmd) == 0:break
            cmd = cmd.decode('utf-8')
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            res = obj.stdout.read() + obj.stderr.read()
            d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'}
            json_d = json.dumps(d)
            # 1.先制作一个字典的报头
            header = struct.pack('i',len(json_d))
            # 2.发送字典报头
            conn.send(header)
            # 3.发送字典
            conn.send(json_d.encode('utf-8'))
            # 4.再发真实数据
            conn.send(res)
            # conn.send(obj.stdout.read())
            # conn.send(obj.stderr.read())
        except ConnectionResetError:
            break
    conn.close()

4.3 半连接数

五、基于TCP实现文件上传下载

5.1 客户端

import socket
import struct
import json
import os

class MYTCPClient:
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    allow_reuse_address = False
    max_packet_size = 8192
    coding='utf-8'
    request_queue_size = 5

    def __init__(self, server_address, connect=True):
        self.server_address=server_address
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if connect:
            try:
                self.client_connect()
            except:
                self.client_close()
                raise

    def client_connect(self):
        self.socket.connect(self.server_address)

    def client_close(self):
        self.socket.close()

    def run(self):
        while True:
            inp=input(">>: ").strip()
            if not inp:continue
            l=inp.split()
            cmd=l[0]
            if hasattr(self,cmd):
                func=getattr(self,cmd)
                func(l)


    def put(self,args):
        cmd=args[0]
        filename=args[1]
        if not os.path.isfile(filename):
            print('file:%s is not exists' %filename)
            return
        else:
            filesize=os.path.getsize(filename)

        head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}
        print(head_dic)
        head_json=json.dumps(head_dic)
        head_json_bytes=bytes(head_json,encoding=self.coding)

        head_struct=struct.pack('i',len(head_json_bytes))
        self.socket.send(head_struct)
        self.socket.send(head_json_bytes)
        send_size=0
        with open(filename,'rb') as f:
            for line in f:
                self.socket.send(line)
                send_size+=len(line)
                print(send_size)
            else:
                print('upload successful')

client=MYTCPClient(('127.0.0.1',8080))

client.run()

5.2 服务端

import socket
import struct
import json
import subprocess
import os

class MYTCPServer:
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    allow_reuse_address = False
    max_packet_size = 8192
    coding='utf-8'
    request_queue_size = 5
    server_dir='file_upload'

    def __init__(self, server_address, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        self.server_address=server_address
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if bind_and_activate:
            try:
                self.server_bind()
                self.server_activate()
            except:
                self.server_close()
                raise

    def server_bind(self):
        """Called by constructor to bind the socket.
        """
        if self.allow_reuse_address:
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)
        self.server_address = self.socket.getsockname()

    def server_activate(self):
        """Called by constructor to activate the server.
        """
        self.socket.listen(self.request_queue_size)

    def server_close(self):
        """Called to clean-up the server.
        """
        self.socket.close()

    def get_request(self):
        """Get the request and client address from the socket.
        """
        return self.socket.accept()

    def close_request(self, request):
        """Called to clean up an individual request."""
        request.close()

    def run(self):
        while True:
            self.conn,self.client_addr=self.get_request()
            print('from client ',self.client_addr)
            while True:
                try:
                    head_struct = self.conn.recv(4)
                    if not head_struct:break

                    head_len = struct.unpack('i', head_struct)[0]
                    head_json = self.conn.recv(head_len).decode(self.coding)
                    head_dic = json.loads(head_json)

                    print(head_dic)
                    #head_dic={'cmd':'put','filename':'a.txt','filesize':123123}
                    cmd=head_dic['cmd']
                    if hasattr(self,cmd):
                        func=getattr(self,cmd)
                        func(head_dic)
                except Exception:
                    break

    def put(self,args):
        file_path=os.path.normpath(os.path.join(
            self.server_dir,
            args['filename']
        ))

        filesize=args['filesize']
        recv_size=0
        print('----->',file_path)
        with open(file_path,'wb') as f:
            while recv_size < filesize:
                recv_data=self.conn.recv(self.max_packet_size)
                f.write(recv_data)
                recv_size+=len(recv_data)
                print('recvsize:%s filesize:%s' %(recv_size,filesize))


tcpserver1=MYTCPServer(('127.0.0.1',8080))
tcpserver1.run()

 

posted on 2020-05-21 23:25  软饭攻城狮  阅读(138)  评论(0编辑  收藏  举报

导航