网络编程-05基于TCP协议的socket套接字编程

一丶什么是Scoket

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

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

阿攀理解:

socket本质就是套接字,项目一般在ISO七层模型中除了应用层其他都会牵扯到socket,我们只要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的,大大增强了开发者的效率!

二丶服务端套接字函数

方法 用途
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.close() 关闭套接字

五丶TCP协议套接字编程

5.1服务端

import socket
import time


server = socket.socket()  # 买手机 不传参数默认用的就是TCP协议
server.bind(('127.0.0.1',8080))  # bind((host,port))  插电话卡  绑定ip和端口
server.listen(5)  # 开机    半连接池


conn, addr = server.accept()  # 接听电话  等着别人给你打电话     阻塞
# while True:
#     time.sleep(1)
#     # data = conn.recv(1024)  # 听别人说话 接收1024个字节数据          阻塞
#     # print(data)
for i in range(31):
    conn.send(b'hello baby~')  # 给别人回话



conn.close()  # 挂电话
server.close()  # 关机

5.2客户端

import socket


client = socket.socket()  # 拿电话
client.connect(('127.0.0.1',8080))  # 拨号   写的是对方的ip和port

for i in range(31):
    # client.send(b'hello world!')  # 对别人说话
    data = client.recv(1024)  # 听别人说话
    print(data,'000')


#
client.close()  # 挂电话import socket
# socket.AF
#1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(phone)
#2、拨电话
phone.connect(('127.0.0.1', 8081))  # 指定服务端ip和端口

#3、通信:发\收消息
phone.send('hello'.encode('utf-8'))
# phone.send(bytes('hello',encoding='utf-8'))
data = phone.recv(1024)
print(data)

# import time
# time.sleep(500)
#4、关闭
phone.close()

六丶连接循环+通信循环

6.1服务端

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()

6.2客户端

import socket


client = socket.socket()  # 拿电话
client.connect(('127.0.0.1',8080))  # 拨号   写的是对方的ip和port

for i in range(31):
    # client.send(b'hello world!')  # 对别人说话
    data = client.recv(1024)  # 听别人说话
    print(data,'000')


#
client.close()  # 挂电话

七丶TCP遇到的问题

地址占用

在重启服务端时可能会遇到:

这个是由于你的服务端仍然存在四次挥手的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))

方法二

发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
vi /etc/sysctl.conf

编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
 
然后执行 /sbin/sysctl -p 让参数生效。
 
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间

八丶TCP实现发送大文件

8.1服务端

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()


8.2客户端

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')

posted @ 2019-09-21 10:57  suren_apan  阅读(118)  评论(0编辑  收藏  举报