一、CS架构

server端要求:

1、力求一直提供服务

2、一个server端socket绑定到一个唯一的IP+端口地址,多个客户端发起connect各带一个conn套接字通道 去连接服务端。

 

二、socket

socket就是为了完成C/S架构软件的开发,但是如果是C/S架构的软件就一定需要解决双方通信问题。

基于网络通信就需要了解复杂的网络协议 TCP/IP协议,于是socket出现了;

 

1、什么是sock?

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的

 

2、socket分类

(1)基于文件类型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信(Linux操作系统)

 

(2)基于网络类型的套接字家族   AF_INET

(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)(ssh,nginx,mysql)

 

3、soket套接字的工作流程

服务端:得到Socket对象,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。

客户端:客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。

C/S交换阶段:客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来

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

 

4、基于TCP协议的套接字

 

(1)实现一个简单的套接字

import socket
phon=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建一个基于网络的(socket.AF_INET)tcp协议传输的sock(socket.SOCK_STREAM)
phon.bind(("127.0.0.1",8090)) #把创建好的套接字,绑定在 唯一的IP和端口上。
phon.listen(80)            #开始监听
conn,addr=phon.accept()   #等待客户端通过TCP三次握手发起 连接  (把某个连接,和连接的对象分解赋值给 conn,和addr)
data=conn.recv(1024)      #接收 连接进来的某一个客户端,发送过来的消息
print("接受到来自客户端发来的消息",data)
conn.send(data.upper())    #发送接受的消息 给某一个客户端

执行结果

服务端:

 

客户端:

 

 

 

 

(2)循环通信的套接字

 

虽然上述已经实现了一个简单的套接字,但服务端的 socket 只能接受 1个客户端的连接,1次通信后就结束了。

 

 

所以可以利用while..Ture构建一个通信循环,实现1个客户端可以和服务端一直通信(循环通信)

服务端:

import socket
phon=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建一个基于网络的(socket.AF_INET)tcp协议传输的sock(socket.SOCK_STREAM
phon.bind(("127.0.0.1",8090)) #把创建好的套接字,绑定在 唯一的IP和端口上。
phon.listen(80)                 #开始监听
conn,addr=phon.accept()   #等待客户端通过TCP三次握手发起 连接  (把某个连接,和连接的对象分解赋值给 conn,和addr)
while True:              #循环通信----------------------------------------------------------------------->
    data=conn.recv(1024)      #接收 连接进来的某一个客户端,发送过来的消息
    print("接受到来自客户端发来的消息",data)
    conn.send(data.upper())    #发送接受的消息 给某一个客户端
conn.close()
phon.close()

客户端:

import socket
phon=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phon.connect(('127.0.0.1',8090))     #客户端的phon.connect正好对于服务端的phon1.accept()
while True: #循环通信----------------------------------------------------------------------------------->>
    mes=input('---->: '.strip())
    if not mes:
        continue
    res=phon.send(mes.encode('utf-8'))  #发消息
    data=phon.recv(1024)             #收消息
    print(data.decode('gbk'))

phon.close()

 

 3、循环连接的套接字

由于1客户端连接到服务端就会建立一个连接通道(电话线),但是如果客户端关闭了这个通道,服务端的conn.recv(1024)方法无法接收到客户端的数据。

在Windows平:台sock服务端就会报错,抛出异常;

在Linux平台:服务端循环收空数据,陷入死循环;

解决方法:建连接循环,捕捉到客户端关闭了连接通道,服务端继续accept()等待下一次连接

 

 

tcp服务端

import socket
import subprocess
iphon=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #(建立一个socket对象)
iphon.bind(('127.0.0.1',8080))  #绑定到 IP+端口上 成为唯一的socket
iphon.listen(5)                   #设置连接池的个数
print('starting........')
while True:  #连接循环------------------------------------------------------------------>
    conn,addr=iphon.accept()  #等待电话连接
    print('电话线路是',conn)
    print('客户手机号:',addr)
    while True: #通信循环 发送和接收
        try:
            data=conn.recv(1024) #接受消息 最大从内存里接受1024MB数据
            print('客户端发来的消息是%s'%data)
            data=data.decode('utf-8') #从客户端发来的数据是经过编码的字节数据 所以需要解码 成Unicode
            res=subprocess.Popen( data, shell=True, stdout=subprocess.PIPE)  #解码后传进subprocess.Popen去cmd执行
            data1 = res.stdout.read().decode('gbk')      #cmd是gbk编码所以需要gbk解码
            print(data1)                                  #打印解码后的结果
            data1=data1.encode('gbk')                   #编码
            conn.send(data1)  #发送消息                  #编码后再传输给客户端
        except Exception:
            break
    conn.close()
iphon.close()
#

 

TCP客户端

import socket
phon=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phon.connect(('127.0.0.1',8080))     #客户端的phon.connect正好对于服务端的phon1.accept()
while True: #循环通信
    mes=input('---->: '.strip())
    if not mes:
        continue
    res=phon.send(mes.encode('utf-8'))  #发消息
    data=phon.recv(1024)             #收消息
    print(data.decode('gbk'))


phon.close()

 

4、由于客户端连接服务端断开后,仍然存在四次挥手的time_wait状态在占用地址(所以 服务器高并发情况下会有大量的time_wait状态的需要优化)

import socket
phon=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建一个基于网络的(socket.AF_INET)tcp协议传输的sock(socket.SOCK_STREAM)
phon.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #处理客户端连接断开,后的time_out------------------------->
phon.bind(("127.0.0.1",8090)) #把创建好的套接字,绑定在 唯一的IP和端口上。
phon.listen(80)                 #开始监听
conn,addr=phon.accept()   #等待客户端通过TCP三次握手发起 连接  (把某个连接,和连接的对象分解赋值给 conn,和addr)
while True:              #循环通信
    data=conn.recv(1024)      #接收 连接进来的某一个客户端,发送过来的消息
    print("接受到来自客户端发来的消息",data)
    conn.send(data.upper())    #发送接受的消息 给某一个客户端
conn.close()
phon.close()

 

 

5、1个服务端套接字 和 多个客户端 套接字通信(socketserver模块实现并发 

 

循环通信:解决了客户端和服务端可以持续发消息。

连接循环:解决了客户端 突然断开和服务端建立的通道,服务端无法继续和这个关闭了这个通道通信,抛异常退出的问题;

但此时的服务端套接字 还只能和1客户端建立通信管道,无法同时接收 多个客户端套接字的连接,实现并发

 

socketserve模块执行流程:

#1、接收多个连接:一个客户端连接建立之后,服务端开一个socketserver.ThreadingTCPServe线程 实例化出一个FTP类的对象。
#2、自动执行Ftp对象handle方法
#3、自动执行对象 handle方法,进入通信循环,self.request就是这个连接客户端的套接字对象。

import  socketserver                      #socketserver()模块基于 线程实现并发

class FTP(socketserver.BaseRequestHandler):#继承socketserver.BaseRequestHandler类
    def handle(self):                      #2、自动执行对象 handle方法,进入通信循环,
                                           #3 self.request就是这个连接客户端的套接字对象

        print('==========>')
        while True:                      #4、进入循环通信
            data=self.request.recv(1024)  #接收数据方法:self.request.recv(1024)
            print(data)
            self.request.send(data)       #发送数据方法:self.request.send(data)

if __name__ == '__main__':
    obj=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FTP)#1、接受多个连接:一个客户端连接建立之后,服务端开一个socketserver.ThreadingTCPServe线程 实                                                                  例化出一个FTP对象
    obj.serve_forever()
    obj.serve_forever()                   #等于accpet()永远接收连接

 



posted on 2017-05-03 19:13  Martin8866  阅读(306)  评论(0编辑  收藏  举报