zoukankan      html  css  js  c++  java
  • python

    参考:

      通过编写聊天程序来熟悉python中多线程和socket的用法:https://www.cnblogs.com/mingjiatang/p/4905395.html

      python socket通信:https://yq.aliyun.com/articles/40745?spm=5176.100239.blogcont40768.17.FIFTZv

    1.socket使用方法

      a.在python中使用socket时要iamport socket

      b.在使用socket中又服务器端和客户端之分

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

      socket起源于Unix,而Unix/Linux基本哲学之一就是:一切皆文件,即都可以用“打开open—>读写write/read—>关闭close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。

      可以看下面的图示来形象说明:

    服务器:

    1、建立一个基于tcp协议的socket类

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    其中AF_INET指定的ipv4的协议,也可以使用AF_INET6指定ipv6的协议,而STREAM是指定面向流的tcp协议。

    2、s.bind(‘', 8089))

    绑定一个端口号,其中'127.0.0.1'是客户端的ip地址,可以使用’0.0.0.0’来绑定网络中所有的ip,8089是指定的端口,其中端口在小于1024的时候需要有管理员的权限才能绑定。

    3、s.listen(5)

    开始实行监听参数:代表连接的最大数量

    4、sock, addr = s.accept()

    接受一个客户端的连接,返回的是一个与客户端保持连接的socket对象以及客户端的ip地址和端口。该方法也会阻塞线程,直到获得客户端的连接。

    客户端:

    1、s.connect(('127.0.0.1', 80))

    连接到服务器,其中'www.baidu.com’也可以是服务器的ip地址。

    2、s.send('hello')

    发送数据’hello’。TCP连接创建的是双向通道,双方都可以同时给对方发数据。但是谁先发谁后发,怎么协调,要根据具体的协议来决定。

    3、s. recv(1024)

    接受连接的对方发来的数据。该方法会阻塞当前线程,所以需要一个专门的线程来接受数据。

    2.socket 案例

    a.socket单线程的阻塞

    # 本机实现的单线程非交互式的socket程序
    # #### server ###########################################################################################
    import socket                #导入socket类
     
    HOST =''                     #定义侦听本地地址口(多个IP地址情况下),这里表示侦听所有,也可以写成0.0.0.0
    PORT = 50007                 #Server端开放的服务端口
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    #选择Socket类型和Socket数据包类型
    s.bind((HOST, PORT))         #绑定IP地址和端口
    s.listen(1)                  #定义侦听数开始侦听(实际上并没有效果)
    conn, addr = s.accept()      #定义实例,accept()函数的返回值可以看上面的socket函数说明
     
    print 'Connected by', addr
    while 1:
        data = conn.recv(1024)    #接受套接字的数据
        if not data:break         #如果没有数据接收,则断开连接
        print 'revc:',data        #发送接收到的数据
        conn.sendall(data)        #发送接收到的数据
    conn.close()                      #关闭套接字
    
    
    # #### client ###########################################################################################
    import socket
     
    HOST = '192.168.1.13'        #定义目标主机名
    PORT = 50007                 #定义目标主机开放服务端口号
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #选择Socket类型和Socket数据包类型 
    s.connect((HOST, PORT))      #连接到目标主机的socket(套接字)中
     
    s.sendall('Hello, world!')   #发送数据
    data = s.recv(1024)          #接收数据
    s.close()                    #关闭socket
    print 'Received', repr(data)
    # 单线程+阻塞+交互式socket程序
    # #### server:内容同上个例子#######################################################################33######
    import socket                #导入socket类
      
    HOST =''                     #定义侦听本地地址口(多个IP地址情况下),这里表示侦听所有,也可以写成0.0.0.0
    PORT = 50007                 #Server端开放的服务端口
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    #选择Socket类型和Socket数据包类型
    s.bind((HOST, PORT))         #绑定IP地址和端口
    s.listen(1)                  #定义侦听数开始侦听(实际上并没有效果)
    conn, addr = s.accept()      #定义实例,accept()函数的返回值可以看上面的socket函数说明
      
    print 'Connected by', addr
    while 1:
        data = conn.recv(1024)    #接受套接字的数据
        if not data:break         #如果没有数据接收,则断开连接
        print 'revc:',data        #发送接收到的数据
        conn.sendall(data)        #发送接收到的数据
    conn.close()                      #关闭套接字
    
    
    # #### client ##########################################################################################
    import socket
     
    HOST = '192.168.1.13'
    PORT = 50007
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))
     
    while True:
        user_input = raw_input('msg to send:').strip()    #由User输入要发送的数据
        s.sendall(user_input)
        data = s.recv(1024)
        print 'Received', repr(data)
     
    s.close()

    单线程,即数据的串行发送,会导致阻塞,如果要解决这个问题,当然在Server端就需要支持多线程,即数据折并发。

    b.socket(多线程聊天程序实例)

      该程序实现的是一个相对比较简单的聊天程序,由于是基于控制台实现的,所只设计容许两个人聊天,另外消息的编码的分割符为|

    # 首先建立一个User的数据结构
    import socket 
    
    class User:
        def __init__(self,skt,username='none'):
            self.skt=skt
            self.username=username
        def send_msg(self,msg):
            self.skt.send(msg)
        def logout(self):
            self.skt.close()
    # server
    import sys 
    import socket
    import threading,time
    import User
    
    #global variable
    userlist=[] 
       
    def hand_user_con(usr):
        try:
            isNormar=True
            while isNormar:
                data=usr.skt.recv(1024)
                time.sleep(1)
                msg=data.split('|')#分析消息
                if msg[0]=='login':
                    print 'user [%s] login' % msg[1]
                    usr.username=msg[1]
                    notice_other_usr(usr)
                if msg[0]=='talk':
                    print 'user[%s]to[%s]:%s' % (usr.username,msg[1],msg[2])
                    send_msg(msg[1],msg[2])#发送消息给目标用户,参数1:目标用户,参数2:消息内容
                if msg[0]=='exit':
                    print 'user [%s] exit' % msg[0]
                    isNormar=False
                    usr.close()
                    userlist.remove(usr)
        except:
            isNormar=False
            
    #通知其他用户以上的好友       
    def notice_other_usr(usr):
        if(len(userlist)>1):
            print 'The two users'
            userlist[0].skt.send(("login|%s" % userlist[1].username))
            userlist[1].skt.send(("login|%s" % userlist[0].username))
        else:
            print 'The one users'
             
    def send_msg(username,msg):
        for usr in userlist:
            if(usr.username==username):
                usr.skt.send(msg)
                
    
    def main():
        s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        s.bind(('0.0.0.0',9999))
        s.listen(5)
        print u'waiting for connection...'
        while True:
            sock,addr=s.accept()#等待用户连接
            user=User.User(sock)
            userlist.append(user)
            t=threading.Thread(target=hand_user_con,args=(user,));
            t.start()  
        s.close()
        
    
    if(__name__=="__main__"):
        main()    
    import sys 
    import socket
    import threading,time
    
    #global variable    
    isNormar=True
    other_usr=''
    
    
    def recieve_msg(username,s):
        global isNormar,other_usr
        print 'Please waiting other user login...'
        s.send('login|%s' %username)
        while(isNormar):
            data= s.recv(1024)#阻塞线程,接受消息
            msg=data.split('|')
            if msg[0]=='login':
                print u'%s user has already logged in, start to chat' % msg[1]
                other_usr=msg[1]
            else:
                print msg[0]
                
    #程序入口
    def main(): 
        global isNormar,other_usr  
        try:
            print 'Please input your name:'
            usrname=raw_input()
            s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
            s.connect(("127.0.0.1",9999))
            t=threading.Thread(target=recieve_msg,args=(usrname,s))
            t.start()
        except:
            print 'connection exception'
            isNormar=False
        finally:
            pass
        while isNormar:
            msg=raw_input()#接受用户输入
            if msg=="exit":
                isNormar=False
            else:
                if(other_usr!=''):
                    s.send("talk|%s|%s" % (other_usr,msg))#编码消息并发送
        s.close()
        
    if __name__=="__main__":
        main()

    c.socketserver模块,实现socket并发

      socketserver说明如下图所示:

    # SockteServer例子说明
    # #### server ####################################
    import SocketServer #导入SocketServer,多线程并发由此类实现 class MySockServer(SocketServer.BaseRequestHandler): #定义一个类 def handle(self): #handle(self)方法是必须要定义的,可以看上面的说明 print 'Got a new connection from', self.client_address while True: data = self.request.recv(1024) #需要通过self的方法调用数据接收函数 if not data:break print 'recv:', data self.request.send(data.upper()) #需要通过self的方法调用数据接收函数 if __name__ == '__main__': #并非一定要用这样的方式,只是建议这样使用 HOST = '' #定义侦听本地地址口(多个IP地址情况下),这里表示侦听所有 PORT = 50007 #Server端开放的服务端口 s = SocketServer.ThreadingTCPServer((HOST, PORT), MySockServer) #调用SocketServer模块的多线程并发函数 s.serve_forever() #持续接受客户端的连接 # #### client ##################################### import socket HOST = '192.168.1.13' PORT = 50007 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) while True: user_input = raw_input('msg to send:').strip() s.sendall(user_input) data = s.recv(1024) print 'Received', repr(data) s.close()

    d.基于SocketServer多线程的简化SSH程序

    # #### server ############################################
    import SocketServer
    import commands    #使用其中的getstatusoutput()函数,让Server端可以识别Client端发送过来的命令并执行
    import time        #主要使用其中的time.sleep()函数,用来解决Server端发送数据的“连块”问题
     
    class MySockServer(SocketServer.BaseRequestHandler):
     
        def handle(self):
            print 'Got a new connection from', self.client_address
            while True:
                cmd = self.request.recv(1024)
                if not cmd:
                    print 'Last connection with:',self.client_address
                    break
                 
                cmd_result = commands.getstatusoutput(cmd)    #获取Client端的指令并执行,返回结果是一个存储两个元素的元组,第一个元素为0表示成功执行,第二个元素则是命令的执行结果
     
                self.request.send(str(len(cmd_result[1])))    #发送命令执行结果的大小长度,Client端要想接收任意大小的执行结果,就需要根据命令执行结果的大小来选择策略,这里需要注意的是,发送的数据是字符串,所以需要作类型转换
     
                time.sleep(0.2)        #睡眠0.2s,是为了解决“连块”的问题
     
                self.request.sendall(cmd_result[1]) #发送命令执行结果
     
    if __name__ == '__main__':
        HOST = ''
        PORT = 50007
        s = SocketServer.ThreadingTCPServer((HOST, PORT), MySockServer)
     
        s.serve_forever()
    
    
    # #### client #######################################
    import socket
     
    HOST = '192.168.1.13'
    PORT = 50007
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))
     
    def data_all(obj, lenth_size):
        data = ''                #用来存储每一次循环时socket接收的数据,解决socket大概两万多字节的缓冲瓶颈限制
        while lenth_size != 0:   #如果接收的数据长度不为0,开始执行接收数据处理策略
            if lenth_size <= 4096:    #这里以4096为单位块,作为每次的数据处理量大小
                data_recv = obj.recv(lenth_size)    #通过recv()接收数据
                lenth_size = 0    #通过这一步的处理,数据全部接收完毕,置lenth_size为0,结束循环,完成数据的接收处理工作
            else:
                data_recv = obj.recv(4096)    #以4096为单位块,一次接收4096的数据量大小
                lenth_size -= 4096            #处理完一次4096字节的数据后,将lenth_size减去4096
            data += data_recv                     #判断外层,用本地的data来存储接收到的数据,因为本地的data大小没有限制,所以不存在data饱和无法继续存储数据的问题,但前面socket的recv()函数一次最多只能接收的数据量大小是有限制的,这取决于socket的缓冲区大小,因此data_all函数的作用就是通过使用多次recv()函数,并且每次接收一定量的数据后就进行本地存储,直到把所有的数据都接收完毕
        return data
     
    while True:
        user_input = raw_input('cmd to send:').strip()
        if len(user_input) == 0:continue
        s.sendall(user_input)
     
        data_size = int(s.recv(1024))      #得到命令执行结果的大小长度,因为发送过来的数据是字符串,所以这里要作类型转换
        print '33[32;1mdata size:33[0m',data_size    #打印命令执行结果的大小
        result = data_all(s, data_size)    #通过data_all函数来执行相应的数据接收处理策略
        print result                       #打印命令执行结果
     
    s.close()                                  #关闭套接字

    PS:

      1.Server端发送数据的“连块”问题,即发送两次数据时,如果发送间隔比较短,socket会把两次发送的数据放在一起来发送,这里通过time.sleep()函数来解决。

      2.socket的缓冲区大小问题,即当执行top -bn 3这样执行结果长度大的命令时,socket缓冲区一次是无法存储这么多数据的,所以只能分多次来接收数据,这样就会在Client端带来一定的问题,比如命令执行的不同步等,解决的方法是通过用循环接收的方法来进行本地存储Server端发送的数据。

      当然,如果要执行man等查询方面的命令,上面的程序也是无法做到的,所以这里说,这只是一个简版的SSH程序

  • 相关阅读:
    关联容器:unordered_map详细介绍
    c++类成员变量初始化相关问题
    全方位深入理解JavaScript面向对象
    彻底搞懂 JS 中 this 机制
    IDEA 服务器热部署详解(On Update action/On frame deactivation)
    Java书籍推荐
    JSTL
    谭浩强的书中的一些知识点(1)
    EL表达式
    第一章第二章
  • 原文地址:https://www.cnblogs.com/blitheG/p/7891690.html
Copyright © 2011-2022 走看看