网络编程之socket

基于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()                 # 关闭客户套接字

TCP解决粘包

TCP粘包原因:接收方不知道到底要收多少数据

import socket
import subprocess
import struct
import json

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

while True:
    conn, addr = server.accept()
    print('连接成功')
    while True:
        try:
            cmd = conn.recv(1024)
            print('接收成功')
            # tcp客户端发空,会导致服务端夯住(udp不存在此问题,因为自带报头,为地址和端口的信息)
            if len(cmd) == 0:break
            cmd = cmd.decode('utf-8')
            #利用subprocess开启新进程,可接收命令,并调用shell去执行这个字符串
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            #res接收subprocess的shell标准化输出和标准化报误信息
            res = obj.stdout.read() + obj.stderr.read()
            #将模块返回的信息的长度,和其他需要提供的信息,做出字典
            d = {'file_size':len(res),'info':'xxxxxxx'}
            #字典序列化
            json_d = json.dumps(d)
            # 1.先制作一个提示客户端,将要发送的字典长度的报头
            # (struct模块,将int类型的长度,转化为二进制字符串,只占4字节)
            header = struct.pack('i',len(json_d))
            # header2=struct.pack('i',len(json_d.encode('utf8'))) 与上句效果相同,算的都是bytes长度

            # 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',8081))
print('连接成功')
while True:
    msg = input('>>>:').encode('utf-8')
    # tcp客户端发空,会导致服务端夯住(udp不存在此问题,因为自带报头,为地址和端口的信息)
    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.从字典中获取信息
    recv_size = 0
    real_data = b''
    # 必须是 < ,最后一次若本来可以刚好发完 即 recv_size = dict_json,大不了再接收一次
    # 若改为 = ,最后一次本来收完,却还满足判断条件,收到的就是空了,会夯住
    while recv_size < dict_json.get('file_size'):
        data = client.recv(1024)
        real_data += data
        recv_size += len(data)
    print(real_data.decode('gbk'))


"""
如何将对方发送的数据收干净
"""
客户端

 发送大文件

import socket
import os
import json
import struct


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

while True:
    conn,addr = server.accept()
    while True:
        try:
            header_len = conn.recv(4)
            # 解析字典报头
            header_len = struct.unpack('i',header_len)[0]
            # 再接收字典数据
            header_dic = conn.recv(header_len)
            real_dic = json.loads(header_dic.decode('utf-8'))
            # 获取数据长度
            total_size = real_dic.get('file_size')
            # 循环接收并写入文件
            recv_size = 0
            with open(real_dic.get('file_name'),'wb') as f:
                while recv_size < total_size:
                    data = conn.recv(1024)
                    f.write(data)
                    recv_size += len(data)
                print('上传成功')
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()
服务端
import socket
import json
import os
import struct

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

while True:
    # 获取电影列表 循环展示
    MOVIE_DIR = r'D:python脱产10期视频day25视频'
    movie_list = os.listdir(MOVIE_DIR)
    # print(movie_list)
    for i,movie in enumerate(movie_list,1):
        print(i,movie)
    # 用户选择
    choice = input('please choice movie to upload>>>:')
    # 判断是否是数字
    if choice.isdigit():
        # 将字符串数字转为int
        choice = int(choice) - 1
        # 判断用户选择在不在列表范围内
        if choice in range(0,len(movie_list)):
            # 获取到用户想上传的文件路径
            path = movie_list[choice]
            # 拼接文件的绝对路径
            file_path = os.path.join(MOVIE_DIR,path)
            # 获取文件大小
            file_size = os.path.getsize(file_path)
            # 定义一个字典
            res_d = {
                'file_name':'性感荷官在线发牌.mp4',
                'file_size':file_size,
                'msg':'注意身体,多喝营养快线'
            }
            # 序列化字典
            json_d = json.dumps(res_d)
            json_bytes = json_d.encode('utf-8')

            # 1.先制作字典格式的报头
            header = struct.pack('i',len(json_bytes))
            # 2.发送字典的报头
            client.send(header)
            # 3.再发字典
            client.send(json_bytes)
            # 4.再发文件数据(打开文件循环发送)
            with open(file_path,'rb') as f:
                for line in f:
                    client.send(line)
        else:
            print('not in range')
    else:
        print('must be a number')
客户端

 

socket收发消息原理剖析

# import socket
# 把后期经常改动的变量提取出来 
# 一般不提倡import*,socket比较特殊
# 都导入进来后就不需要每次socket.来调用方法了
from socket import *
ip_port=('127.0.0.1',8083)
back_log=5
buffer_size=1024
​
tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
​
while True:
  #循环的等待接收服务
    print('服务端开始运行了')
    conn,addr=tcp_server.accept() #服务端阻塞等待链接 
    print('双向链接是',conn)  #链接信息
    print('客户端地址',addr)  #地址+端口
while True:
    #循环的为这个链接服务
        try:
            data=conn.recv(buffer_size)
            print('客户端发来的消息是',data.decode('utf-8'))
            conn.send(data.upper())
        except Exception:
            break
    conn.close()
​
tcp_server.close()
服务端
# import socket
from socket import *
ip_port=('127.0.0.1',8083)
back_log=5
buffer_size=1024
​
tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
​
while True:
    msg=input('>>: ').strip()
    if not msg:continue #如果客户端输入为空,重新回到输入
    tcp_client.send(msg.encode('utf-8'))
    print('客户端已经发送消息')
    data=tcp_client.recv(buffer_size)
    print('收到服务端发来的消息',data.decode('utf-8'))
​
tcp_client.close()
​
# recv(1024),其实是在内核态内存收消息
# send(msg),其实是消息从用户态内存拷贝到内核态内存
客户端

服务端循环链接请求+循环收发消息

堆结构:先进先出,吃了拉 先吃进去的先拉出来 python中的堆结构为队列 栈结构:先进后出,后进先出 吃了吐,后面吃的先吐出来 python中没有专门的栈结构

客户端直接终止:四次挥手都没有,直接中断的,而服务端本来等着收,conn突然被干掉了,recv就没意义了(远程主机强迫关闭了一个现有连接)

C/S架构的美好愿望,服务端启动后,永远运行下去,服务很多人

循环提供服务 如果服务端正在为客户端a提供服务,那么服务端进入 “为a链接服务”的循环中,此时这个循环还没结束,为a链接服务的conn.close()没有执行,回不到上一个循环,即无法接收新的会话连接 此时客户端b试图建立链接,send到自己的内核态内存,并发往服务器,进入服务器的block_log连接池中挂起。 等对a的服务进程完成断开后,进入下一个接收连接循环,服务端的内核态内存才会把挂起的“b连接”发给用户态内存,服务端程序才能接收并进入服务循环

客户端b试图想建立链接,这时候客户端如果发了一条消息,其实发消息就已经进入客户端的循环,只是服务端没有回应,客户端的内核态内存没新数据,故客户端的tcp_client.recv(buffer_size)没有收到信息,自然前面的data= 就没有被赋值,再次就中断等待了

此时手动结束客户端a进程,服务端conn被干掉,报错:远程主机强迫关闭了一个现有连接,服务端进程卡死。故需要在 为a连接服务的 循环,加一个异常处理 ,遇到问题 break,继续运行退出循环

避免通信循环中的报错
1.服务端在conn.recv()下一句
用if 为空 :break 解决conn断开,服务端一直收空消息造成的死循环
2.服务端在通信循环 用处理客户端进程非正常中断造成的报错(远程主机强迫关闭了一个现有的连接)
try:
#通信循环
except Exception as e:
  print(e)
  break
import socket
"""
服务端
    固定的ip和port
    24小时不间断提供服务
"""
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()
服务端
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)
客户端

recv谁发起的:由用户态内存中的应用程序发起的,可以是客户端,可以使服务端 最多收到设定的字节数

TCP服务端防止收空

若客户端直接回车,即发了一个None,客户端send空到客户端内核态内存 而服务端的内核态缓存,没东西。,自然卡住了,后面的send就不会执行。客户端在send空后,会一直等着收内核态缓存的信息,而客户端没收到新消息,所以也卡住了

卡住recv 是由于:内核态缓存没东西,跟对方没关系

linux、unix遇到客户端终端,服务端不会抛出异常,而是recv会一直接收None,跟windows 不一样

解决方法:

while True:
   #循环的为这个链接服务
           data=conn.recv(buffer_size)
           if not data:break #如果收到的是空,退出循环
           print('客户端发来的消息是',data.decode('utf-8'))
           conn.send(data.upper())
   conn.close()

基于UDP的套接字

UDP服务端


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

没有listen:listen是用来挂连接请求的,半链接池,UDP没有链接 少了一个链接循环:不需要链接

UDP客户端

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

没有绑定:直接创建套接字,不需要绑定 只需要通讯循环

UDP服务端

from socket import *
ip_port=('127.0.0.1',8080)
buffer_size=1024
​
udp_server=socket(AF_INET,SOCK_DGRAM) #数据报
udp_server.bind(ip_port)
​
while True:
    data,addr=udp_server.recvfrom(buffer_size)
    #客户端发的是(二进制内容,ip_port),所以收到的是元组
    print(data)
​
    udp_server.sendto(data.upper(),addr)
UDP服务端
from socket import *
ip_port=('127.0.0.1',8080) #服务端的
buffer_size=1024
​
udp_client=socket(AF_INET,SOCK_DGRAM) #数据报
while True:
    msg=input('>>: ').strip()
    udp_client.sendto(msg.encode('utf-8'),ip_port)
    #没有链接,每次要指定发给哪个ip端口
    #发的是(二进制内容,ip_port)的元组
    data,addr=udp_client.recvfrom(buffer_size)
    # 收到的也是(二进制内容,ip_port)的元组
    # print(data.decode('utf-8'))
    print(data)
UDP服务端

recv与recvfrom的区别

tcp收和udp收,都是从自己的缓冲区拿内容,tcp收不到空内容,udp却可以收到(None,ip_port)元组 recv在自己的缓冲区为空时,阻塞 recvfrom在收到一个空内容时,虽然内容为空,但是带了ip_port的报文头

TCP 一端发完一个包后,收到对面的ACK确认收到响应,才会在内核缓冲区把数据清空 socket用户态内存发给内核态内存,本质是copy操作

UDP不依赖于服务端 UDP没有链接,天然实现会话的并发

 

socketserver模块

TCP的socketserver

import socketserver
​
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # print('来啦 老弟')
        while True:
            data = self.request.recv(1024)
            print(self.client_address)  # 客户端地址
            print(data.decode('utf-8'))
            self.request.send(data.upper())
​
if __name__ == '__main__':
    """只要有客户端连接  会自动交给自定义类中的handle方法去处理"""
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer)  # 创建一个基于TCP的对象
    server.serve_forever()  # 启动该服务对象
服务端

连续开多个客户端进行验证

import socket
​
client = socket.socket()
client.connect(('127.0.0.1',8080))
​
while True:
    client.send(b'hello')
    data = client.recv(1024)
    print(data.decode('utf-8'))
客户端

UDP的socketserver

import socketserver
​
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # print('来啦 老弟')
        while True:
            data,sock = self.request
            print(self.client_address)  # 客户端地址
            print(data.decode('utf-8'))
            sock.sendto(data.upper(),self.client_address)
​
if __name__ == '__main__':
    """只要有客户端连接  会自动交给自定义类中的handle方法去处理"""
    server = socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyServer)  # 创建一个基于TCP的对象
    server.serve_forever()  # 启动该服务对象
服务端

连续开多个客户端进行验证

import socket
import time
​
client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1',8081)
​
while True:
    client.sendto(b'hello',server_address)
    data,addr = client.recvfrom(1024)
    print(data.decode('utf-8'),addr)
    #睡一秒,不然速度太快,收不到其余客户端的消息
    time.sleep(1) 
客户端
原文地址:https://www.cnblogs.com/lishuaing/p/11321835.html