網絡編程-05基於TCP協議的socket套接字編程

一丶什麼是Scoket

Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。python

因此,咱們無需深刻理解tcp/udp協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的。linux

阿攀理解:

socket本質就是套接字,項目通常在ISO七層模型中除了應用層其餘都會牽扯到socket,咱們只要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的,大大加強了開發者的效率!git

二丶服務端套接字函數

方法 用途
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
#一、買手機
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(phone)
#二、撥電話
phone.connect(('127.0.0.1', 8081))  # 指定服務端ip和端口

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

# import time
# time.sleep(500)
#四、關閉
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狀態的優化方法)json

方法一

# 加入一條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')
相關文章
相關標籤/搜索