1、socket 套接字工作流程图

2、收发功能

3、不间断一发一收

4、多客户端连接

5、UDP:收发功能

6、UDP:实现时间功能

7、执行命令

8、黏包


socket 套接字工作流程图

  image.png

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


AF_UNIX: 基于文件编程 

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


AF_INET: 基于网络编程 有AF_INET6 ipv6

CS架构


SOCK_STREAM: TCP协议   数据流式通信

SOCK_DGRAM:  UDP协议   数据报式的套接字



## 收发功能 

socket服务端

 import socket
 cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 cat.bind(('192.168.0.12',9090))

 cat.listen(4)
 conn,addr=cat.accept()

 msg=conn.recv(1024)
 conn.close()
 print('接收到的信息: %s'%msg)
 conn.send(msg.upper())
 cat.close()

socket客户端

 import socket
 cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 cat.connect(('192.168.0.12',9090))
 cat.send('xiong'.encode('utf-8'))
 data=cat.recv(1024)
 print('接收到的信息: %s'%data)


#服务端会主动断开 已经传输完客户端数据的连接,将状态改变为TIME_WAIT, 四次挥手之后确定数据已经完全传输完,直接断开并清理状态连接信息


# 收发都是在操作自己的缓存区

# recv 接收的字节, 由recv用户态的应用程序发起

# 回车: 当前 socket 内核态缓存没数据 对端内核态也就没法收到数据,自然也就卡死了


##不间断一发一收

socket服务端

 import socket
 cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 cat.bind(('127.0.0.1',9009))
 cat.listen(5)
 conn,addr=cat.accept()
 while True:
     msg=conn.recv(1024)     #回车: 当前 socket 内核态缓存没数据 对端内核态也就没法收到数据,自然也就卡死了
     print('接收到的信息: %s'%msg.decode('utf-8'))
     if msg == b'q' : break
     inp = input('输入一个值: ')
     conn.send(inp.encode('utf-8'))
     continue
 conn.close()
 cat.close()

socket客户端

 import socket
 cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 cat.connect(('127.0.0.1',9009))
 while True:
     inp = input('输入一个值: ')
     if inp == 'q':
         cat.send(inp.encode('utf-8'))
         break
     cat.send(inp.encode('utf-8'))
     msg = cat.recv(1024)
     print('接收到的信息: %s' % msg.decode('utf-8'))
     continue
 cat.close()


## 多客户端连接

# 1、当客户端与服务端建立连接,每次最大客户端连接数由listen控制,我这里最大是5个连接

# 2、多个客户端与服务端建立连接,每次只能有一个客户端与服务端通信,其它队列都会保持在队列中

# 3、unix有些系统使用try except无法解决客户端conn连接突然中断, 可以使用 if not conn: break 


socket服务端

 import socket
 cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 cat.bind(('192.168.2.192',9009))
 cat.listen(5)
 while True:     # 客户端退出,如果队列中还有连接那么再重新建立连接
     conn,addr=cat.accept()      # 连接一次
     while True:     # 与单个用户建立连接
         try:
             msg=conn.recv(1024)   # 当客户端关掉连接,而服务端连接却没有中断,它就直接报错 ConnectionResetError: [WinError 10054]
         except Exception:
             break
         print('接收到的信息: %s'%msg.decode('utf-8'))
         if msg == b'q' : break
         conn.send(msg.upper())
     conn.close()        # 关闭连接
 cat.close()     # 关闭程序

socket客户端

 import socket
 cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 cat.connect(('192.168.2.192',9009))
 while True:
     inp = input('输入一个值: ')
     if inp == 'q':
         cat.send(inp.encode('utf-8'))
         break
     cat.send(inp.encode('utf-8'))
     msg = cat.recv(1024)
     print('接收到的信息: %s' % msg.decode('utf-8'))
     continue
 cat.close()


UDP:收发功能

1、udp不需要建立accept连接,因为无需三次握手建立一条固定的通道

2、多个客户端同时连接服务端,可同时收发信息

3、udp可以接受空 (直接回车)???


recv在自己这端的缓冲区为空时,阻塞

recvfrom在自己这端的缓冲区为空时,就收一个空


UDP_socket服务端

 import socket

 ip_port=('127.0.0.1',9999)
 udp_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 udp_sock.bind(ip_port)

 while True:
     data,addr=udp_sock.recvfrom(1024)
     print(data)
     udp_sock.sendto(data,addr)

UDP_socket客户端

 import socket
 ip_port=('127.0.0.1',9999)
 udp_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

 while True:
     inp = input('>>: ')
     udp_sock.sendto(inp.encode('utf-8'),ip_port)
     data,addr=udp_sock.recvfrom(1024)
     print(data)


UDP:实现时间功能

  # 1、实现时间功能

  # 2、data传递进来是二进制的格式,在strftime之前需要先将格式转换回来

  # 3、注意格式转换,发送都是encode,接受基本都是recvfrom

# udp_socket_server端

 import socket
 import time
 ip_port=('127.0.0.1',9001)
 udp_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 udp_sock.bind(ip_port)

 while True:
     data,addr=udp_sock.recvfrom(1024)
     print(data)
     if not data:
         default_time='%Y-%m-%d %X'
     else:
         default_time=data.decode('utf-8')
     udp_back_time=time.strftime(default_time)
     udp_sock.sendto(udp_back_time.encode('utf-8'),addr)

# udp_socket_client端
 import socket
 udp_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 ip_port=('127.0.0.1',9001)   #TypeError: sendto() takes 2 or 3 arguments (1 given)  需要带地址
 while True:
     inp=input('>>: ')
     udp_client.sendto(inp.encode('utf-8'),ip_port)
     data,addr=udp_client.recvfrom(1024)
     print('现在时间是: %s'%data.decode('utf-8'))
# 1、先运行服务端
# 2、再运行客户端打印结果如下:
		现在时间是: 18
		>>: %Y
		现在时间是: 2018
		>>: 
		现在时间是: 2018-01-04 20:49:53
		>>:


### 执行命令

# socket TCP服务端

 import socket
 import subprocess

 ip_port=('127.0.0.1',9001)
 ip_connect=5
 buff_size=1024

 command=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 command.bind(ip_port)
 command.listen(ip_connect)

 while True:
 	# 客户端断开之后保持重连
     data,addr=command.accept()
     print('客户端连接信息: %s' %data)
     while True:
         try:
         	# 接收客户端传递过来的值
             cmd_value=data.recv(buff_size)
         except Exception:
             break
         # 执行传递过来的命令,将结果保存到管道对象中赋值给res  
         res=subprocess.Popen(cmd_value.decode('utf-8'),shell=True,
                              stdout=subprocess.PIPE,
                              stdin=subprocess.PIPE,
                              stderr=subprocess.PIPE)

         # 取出stderr的值,如果是空那么执行stdout,不为空说明报错了
         err=res.stderr.read()
         if err:
             cmd_res=err
         else:
             cmd_res=res.stdout.read()
         if not cmd_res:     # 判断如果是类似 cd .. 的命令,它到subprocess值为空
             cmd_res='命令为空'.encode('gbk')
         data.send(cmd_res)
         continue
     data.close()
 command.close()


# socket_客户端 

 import socket

 ip_port=('127.0.0.1',9001)
 ip_connect=5
 buff_size=1024

 command=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 command.connect(ip_port)

 while True:
     cmd_inp=input('请输入命令: ').strip()
     if not cmd_inp: command
     if cmd_inp == 'quit': break
     command.send(cmd_inp.encode('utf-8'))
     data=command.recv(buff_size)
     print(data.decode('gbk'))
 command.close()

#  最后在客户端这边输入 dir就能看到结果了#  最后在客户端这边输入 dir就能看到结果了


黏包

udp不会黏包,一个recvfrom对应一个sendto,每发送一个包就接收一个包

tcp黏包:优化算法合并了每次send发送的数据, 更优的减少网络负载量, 如果第一次没有收完,那么第二次发送过来的,还是第一次缓存冲区的数据


###### 黏包解决办法 

解决粘包的思路:

1、判断数据的长度,

2、封闭消息头


UDP 数据报: TCP数据流的方式 封装了一个消息头(消息来源地址,发送端的IP+端口) 


应用程序永远不能操作硬件,能操作硬件的只能是操作系统的内核

用户态内存: socket程序

内核态内存: 内核操作硬件


Forking 进程

threading 线程

多进程比多线程的开销更大

### 服务端配置
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from socket import *
import subprocess
import struct

ip_port=('127.0.0.1',9002)
ip_listen=5
buff_size=1024

# TCP 流式协议
tcp_server=socket(AF_INET,SOCK_STREAM)
# 绑定并监听端口
tcp_server.bind(ip_port)
# 允许back_log 允许有多少个队列
tcp_server.listen(ip_listen)

while True:
    # 客户端连接信息,当客户端断开之后,服务端应允许其它客户端连接
    conn,addr=tcp_server.accept()
    print('客户端连接信息: %s' %conn)
    while True:
        try:
            # 如果是空的,或者客户端非法退出,那么就直接退出程序
            data=conn.recv(buff_size)
            if not data:break
        except Exception:
            break
        # subprocess接收到的命令应该是str格式,客户端发送过来的值是bytes格式,
        res=subprocess.Popen(data.decode('utf-8'),shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             stdin=subprocess.PIPE)
        err=res.stderr.read()
        # 如果这个值里有东西,说明它报错了直接返回它
        if err:
            cmd_res=err
        else:
            cmd_res=res.stdout.read()
        # 如果为空比如cd .. 那么应该给它返回一个空值
        if not cmd_res:
            cmd_res='value is null '.encode('utf-8')

        # 取出这个值最大的长度,并传递给客户端
        length=len(cmd_res)
        # ---------------  方法一 ---------------
        # # length=str(length).encode('utf-8')
        # conn.send(length)

        # 将最大值给客户端,返回一个值过来,避免黏包
        # retu_data=conn.recv(buff_size)
        # if retu_data == b'ok':
        #     conn.send(cmd_res)
        # --------------- 方法二 ---------------
        # 黏包发送,第一次发送的值,固定为4字节,客户端接收的时候先接收4字节,再取数据
        length_data=struct.pack('i',length)
        conn.send(length_data)
        conn.send(cmd_res)

    conn.close()
tcp_server.close()


### 客户端配置
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from socket import *
import struct
ip_port=('127.0.0.1',9002)
ip_listen=5
buff_size=1024

tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    inp=input('请输入命令: ')
    tcp_client.send(inp.encode('utf-8'))
    # 如果直接回车,为空,那么应该直接让它退出循环重新再来一次
    if not inp:continue
    # 如果输入quit,那么直接退出程序
    if tcp_client == 'quit': break

    # ---------------  方法一 ---------------
    # 解决黏包
    # length=tcp_client.recv(buff_size)
    # tcp_client.send('ok'.encode('utf-8'))
    #
    # length=int(length.decode('utf-8'))

    # ---------------  方法二 ---------------
    # 服务端发送的第一个值是4字节大小的,手动调置一下
    length=tcp_client.recv(4)
    # 取出大小
    length=struct.unpack('i',length)[0]

    # 设置初始值,用于保存
    recv_size=0
    # 配置二进制的大小
    recv_msg=b''

    while recv_size < length:
        recv_msg+=tcp_client.recv(buff_size)
        recv_size = len(recv_msg)
    # data=tcp_client.recv(buff_size)
    print(recv_msg.decode('gbk'))


# 结果
请输入命令: cd ..
	value is null 
	
请输入命令: ipconfig
	Windows IP 配置
	以太网适配器 本地连接:  xxxxxxxxxxxxxxxxxxxx