博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
网络编程
阅读量:5015 次
发布时间:2019-06-12

本文共 17061 字,大约阅读时间需要 56 分钟。

一、客户端/服务端架构:

1、硬件C/S架构(客户端/服务端)

二、osi七层模型

七层: 物理层 数据链路层 网络层 传输层 会话层 表示层 应用层

引子:计算机系统由硬件,操作系统,应用软件三部分组成

三、socket是什么

socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,在设计模式中socket其实是一种门面模式,对于用户来说它就是一组简单的接口。

有人将socket说成ip+port,ip是用来标识网络中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,

而程序的pid是同一台机器上不同的进程或者是线程的标识。

四、套接字的工作流程

服务端:初始化socket、绑定地址端口(bind)、监听(listen)、调用accept阻塞、等待客户端链接。

客户端:初始化socket、连接服务器(connect)

import socketsocket.socket(socket_famliy, socket_type, protocal=0)socket_famliy 可以是AF_UNIX或者是 AF_INET。socket_type可以是 SOCK_STREAM 或 SOCK_DGRAM。protocal 一般不填,默认值是0获取tcp/ip套接字tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)获取udp/ip套接字udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
服务端套接字函数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的套接字

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

tcp服务端:

ss = socket() #创建服务器套接字ss.bind()      #把地址绑定到套接字ss.listen()      #监听链接inf_loop:      #服务器无限循环    cs = ss.accept() #接受客户端链接    comm_loop:         #通讯循环        cs.recv()/cs.send() #对话(接收与发送)    cs.close()    #关闭客户端套接字ss.close()        #关闭服务器套接字(可选)

 tcp客户端

cs = socket()    # 创建客户套接字cs.connect()    # 尝试连接服务器comm_loop:        # 通讯循环    cs.send()/cs.recv()    # 对话(发送/接收)cs.close()            # 关闭客户套接字

加上链接循环和通信循环

#_*_coding:utf-8_*___author__ = 'mosson'import socketip_port=('127.0.0.1',8081)#电话卡BUFSIZE=1024s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机s.bind(ip_port) #手机插卡s.listen(5)     #手机待机while True:                         #新增接收链接循环,可以不停的接电话    conn,addr=s.accept()            #手机接电话    # print(conn)    # print(addr)    print('接到来自%s的电话' %addr[0])    while True:                         #新增通信循环,可以不断的通信,收发消息        msg=conn.recv(BUFSIZE)             #听消息,听话        # if len(msg) == 0:break        #如果不加,那么正在链接的客户端突然断开,recv便不再阻塞,死循环发生        print(msg,type(msg))        conn.send(msg.upper())          #发消息,说话    conn.close()                    #挂电话s.close()                       #手机关机
server
#_*_coding:utf-8_*___author__ = 'mosson'import socketip_port=('127.0.0.1',8081)BUFSIZE=1024s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect_ex(ip_port)           #拨电话while True:                             #新增通信循环,客户端可以不断发收消息    msg=input('>>: ').strip()    if len(msg) == 0:continue    s.send(msg.encode('utf-8'))         #发消息,说话(只能发送字节类型)    feedback=s.recv(BUFSIZE)                           #收消息,听话    print(feedback.decode('utf-8'))s.close()
client

问题:

有的同学在重启服务端时可能会遇到

这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)

解决办法:

#加入一条socket配置,重用ip和端口phone=socket(AF_INET,SOCK_STREAM)phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加phone.bind(('127.0.0.1',8080))

六、基于UDP的套接字

UDP是无链接的,先启动哪一端都不会报错

UDP服务端:

ss = socket()   #创建一个服务器的套接字ss.bind()       #绑定服务器套接字inf_loop:       #服务器无限循环     cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)ss.close()                         # 关闭服务器套接字

UDP客户端:

cs = socket()   # 创建客户套接字comm_loop:      # 通讯循环    cs.sendto()/cs.recvfrom()   # 对话(发送/接收)cs.close()

七:粘包现象

让我们给予tcp先制作一个远程执行命令的程序:(1:执行错误命令 2:执行ls 3:执行ifconfig)

action:::

res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE)

的结果的编码是以当前所在的系统为准

八:什么事粘包

Tip:只有tcp才有粘包现象,UDP永远不会粘包。

发送端可以是一k一k的发送数据,而对应的接收端可以是两k两k的接收数据,当然也有可能一次提走3k或者是6k的数据。应用程序所看到的数据是一个整体,或者说说是一个流stream。

tcp协议是面向流的数据,UDP协议是面向消息的。

tcp套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的

所谓的粘包,主要是因为接收方不知道消息之间的界限,不知道一次性需要提取多少数据,造成的。

1、TCP(transport control protocal)是面向连接的面向流,提供高可靠性服务,socket是成对的。发送端为了将多个发往服务端的包,更有效发到对方,使用了优化,将多次间隔较小且数据量小的数据,合并成一个大的数据块,这样接收端就难于分辨,必须提供科学的拆包机制

2、UDP(user datagram protocal)是无链接的面向消息的,提供高效率服务,不会使用合并算法,由于UDP支持一对多的模式,接收端的skbuff采用了链式结构

3、tcp是基于数据流的,于是收发消息不能为空(为空需要特殊处理),UDP的手法消息可以为空

两种情况下回发生粘包:

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

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

九:粘包的解决办法:

为字节流加上固定长度的报头,报头中包含字节流长度,然后send到对端,对端在接收时,先从缓存中取出定长的报头,然后在根据报头取相应的数据。

struct模块:

>>> struct("i", 111111111111111111111)

struct.error:"i" format requires -2147483648 <= number <= 2147483647 #这个是范围

struct模块用法讲解:

import json,struct#假设通过客户端上传1T:1073741824000的文件a.txt#为避免粘包,必须自定制报头header={
'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值#为了该报头能传送,需要序列化并且转为byteshead_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输#为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度#客户端开始发送conn.send(head_len_bytes) #先发报头的长度,4个bytesconn.send(head_bytes) #再发报头的字节格式conn.sendall(文件内容) #然后发真实内容的字节格式#服务端开始接收head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式header=json.loads(json.dumps(header)) #提取报头#最后根据报头的内容提取真实的数据,比如real_data_len=s.recv(header['file_size'])s.recv(real_data_len)
struct用法
import socket,struct,jsonimport subprocessphone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加phone.bind(('127.0.0.1',8080))phone.listen(5)while True:    conn,addr=phone.accept()    while True:        cmd=conn.recv(1024)        if not cmd:break        print('cmd: %s' %cmd)        res=subprocess.Popen(cmd.decode('utf-8'),                             shell=True,                             stdout=subprocess.PIPE,                             stderr=subprocess.PIPE)        err=res.stderr.read()        print(err)        if err:            back_msg=err        else:            back_msg=res.stdout.read()        conn.send(struct.pack('i',len(back_msg))) #先发back_msg的长度        conn.sendall(back_msg) #在发真实的内容    conn.close()服务端(自定制报头)
server自定制报头
#_*_coding:utf-8_*_import socket,time,structs=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(('127.0.0.1',8080))while True:    msg=input('>>: ').strip()    if len(msg) == 0:continue    if msg == 'quit':break    s.send(msg.encode('utf-8'))    l=s.recv(4)    x=struct.unpack('i',l)[0]    print(type(x),x)    # print(struct.unpack('I',l))    r_s=0    data=b''    while r_s < x:        r_d=s.recv(1024)        data+=r_d        r_s+=len(r_d)    # print(data.decode('utf-8'))    print(data.decode('gbk')) #windows默认gbk编码客户端(自定制报头)
client自定制报头

FTP的上传下载:

import socketimport structimport jsonimport subprocessimport osclass 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()#下列代码与本题无关class MYUDPServer:    """UDP server class."""    address_family = socket.AF_INET    socket_type = socket.SOCK_DGRAM    allow_reuse_address = False    max_packet_size = 8192    coding='utf-8'    def get_request(self):        data, client_addr = self.socket.recvfrom(self.max_packet_size)        return (data, self.socket), client_addr    def server_activate(self):        # No need to call listen() for UDP.        pass    def shutdown_request(self, request):        # No need to shutdown anything.        self.close_request(request)    def close_request(self, request):        # No need to close anything.        pass
server
import socketimport structimport jsonimport osclass 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()
client

十:认证客户端的链接的合法性

如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不想ssl那么复杂, 那么利用hmac+盐的方式来实现

#_*_coding:utf-8_*_from socket import *import hmac,ossecret_key=b'linhaifeng bang bang bang'def conn_auth(conn):    '''    认证客户端链接    :param conn:    :return:    '''    print('开始验证新链接的合法性')    msg=os.urandom(32)    conn.sendall(msg)    h=hmac.new(secret_key,msg)    digest=h.digest()    respone=conn.recv(len(digest))    return hmac.compare_digest(respone,digest)def data_handler(conn,bufsize=1024):    if not conn_auth(conn):        print('该链接不合法,关闭')        conn.close()        return    print('链接合法,开始通信')    while True:        data=conn.recv(bufsize)        if not data:break        conn.sendall(data.upper())def server_handler(ip_port,bufsize,backlog=5):    '''    只处理链接    :param ip_port:    :return:    '''    tcp_socket_server=socket(AF_INET,SOCK_STREAM)    tcp_socket_server.bind(ip_port)    tcp_socket_server.listen(backlog)    while True:        conn,addr=tcp_socket_server.accept()        print('新连接[%s:%s]' %(addr[0],addr[1]))        data_handler(conn,bufsize)if __name__ == '__main__':    ip_port=('127.0.0.1',9999)    bufsize=1024    server_handler(ip_port,bufsize)服务端
server
#_*_coding:utf-8_*_from socket import *import hmac,ossecret_key=b'linhaifeng bang bang bang'def conn_auth(conn):    '''    验证客户端到服务器的链接    :param conn:    :return:    '''    msg=conn.recv(32)    h=hmac.new(secret_key,msg)    digest=h.digest()    conn.sendall(digest)def client_handler(ip_port,bufsize=1024):    tcp_socket_client=socket(AF_INET,SOCK_STREAM)    tcp_socket_client.connect(ip_port)    conn_auth(tcp_socket_client)    while True:        data=input('>>: ').strip()        if not data:continue        if data == 'quit':break        tcp_socket_client.sendall(data.encode('utf-8'))        respone=tcp_socket_client.recv(bufsize)        print(respone.decode('utf-8'))    tcp_socket_client.close()if __name__ == '__main__':    ip_port=('127.0.0.1',9999)    bufsize=1024    client_handler(ip_port,bufsize)客户端(合法)
client合法
#_*_coding:utf-8_*_from socket import *def client_handler(ip_port,bufsize=1024):    tcp_socket_client=socket(AF_INET,SOCK_STREAM)    tcp_socket_client.connect(ip_port)    while True:        data=input('>>: ').strip()        if not data:continue        if data == 'quit':break        tcp_socket_client.sendall(data.encode('utf-8'))        respone=tcp_socket_client.recv(bufsize)        print(respone.decode('utf-8'))    tcp_socket_client.close()if __name__ == '__main__':    ip_port=('127.0.0.1',9999)    bufsize=1024    client_handler(ip_port,bufsize)客户端(非法:不知道加密方式)
client非法,不知道加密方式
#_*_coding:utf-8_*_from socket import *import hmac,ossecret_key=b'linhaifeng bang bang bang1111'def conn_auth(conn):    '''    验证客户端到服务器的链接    :param conn:    :return:    '''    msg=conn.recv(32)    h=hmac.new(secret_key,msg)    digest=h.digest()    conn.sendall(digest)def client_handler(ip_port,bufsize=1024):    tcp_socket_client=socket(AF_INET,SOCK_STREAM)    tcp_socket_client.connect(ip_port)    conn_auth(tcp_socket_client)    while True:        data=input('>>: ').strip()        if not data:continue        if data == 'quit':break        tcp_socket_client.sendall(data.encode('utf-8'))        respone=tcp_socket_client.recv(bufsize)        print(respone.decode('utf-8'))    tcp_socket_client.close()if __name__ == '__main__':    ip_port=('127.0.0.1',9999)    bufsize=1024    client_handler(ip_port,bufsize)客户端(非法:不知道secret_key)
client非法不知道secret_key

 

十一:socketserver实现并发。

基于tcp的套接字,关键就是两个循环,一个是链接循环(server),一个是通信循环(request)

server类:

request类:

继承关系:

 

 

 

以下述代码为例,分析socketserver源码:

ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer) ftpserver.serve_forever()

查找属性的顺序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer

  1. 实例化得到ftpserver,先找类ThreadingTCPServer的__init__,在TCPServer中找到,进而执行server_bind,server_active
  2. 找ftpserver下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer中
  3. 执行self._handle_request_noblock()进而执行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address)
  4. 在ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address)
  5. 上述四部分完成了链接循环,本部分开始进入处理通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找....

源码分析总结:

基于tcp的socketserver我们自己定义的类中的

  1.   self.server即套接字对象
  2.   self.request即一个链接
  3.   self.client_address即客户端地址

基于udp的socketserver我们自己定义的类中的

  1.   self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
  2.   self.client_address即客户端地址

 

转载于:https://www.cnblogs.com/mosson/p/8423492.html

你可能感兴趣的文章
android 实现2张图片层叠效果
查看>>
我个人所有的独立博客wordpress都被挂马
查看>>
html5——动画案例(时钟)
查看>>
调用Android系统“应用程序信息(Application Info)”界面
查看>>
ios中用drawRect方法绘图的时候设置颜色
查看>>
数据库中的外键和主键理解
查看>>
个人博客03
查看>>
Expression<Func<T,TResult>>和Func<T,TResult>
查看>>
文件缓存
查看>>
关于C语言中return的一些总结
查看>>
Codeforces Round #278 (Div. 2)
查看>>
51. N-Queens
查看>>
Linux 命令 - 文件搜索命令 locate
查看>>
[Grunt] grunt.template
查看>>
Ubuntu最小化桌面快捷键Super+D不生效解决
查看>>
Cookie&Session会话跟踪技术
查看>>
UNIX环境高级编程 第17章 高级进程间通信
查看>>
ES的Zen发现机制
查看>>
【hibernate】1、Hibernate的一个注解 @Transient
查看>>
HihoCoder 1877 - Approximate Matching
查看>>