Python——Socket编程

前言

最近在做项目的时候需要进行一个简单的跨语言通信,有用到socket和websocket,这次详细记录下学习socket的过程。

什么是TCP?

1.TCP是面向连接的协议,是允许系统通过Internet进行通信的标准,它定义了如何建立和维护应用程序可以通过其交换数据的网络对话;保证了数据的传递,并保证数据包的发送顺序与发送数据包的顺序相同。(传输控制协议)

2.TCP是OSI层中的传输层协议,用于通过传输和确保通过支持网络和Internet传递消息来在远程计算机之间创建连接。

3.TCP是数字网络通信中最常用的协议之一,是Internet协议套件的一部分,它和IP一起是定义Internet的基本规则,通常称为TCP / IP套件。

4.主要作用:数据传输,确保不同不同节点之间的端到端的数据传输,确保不同节点之间的端到端的数据传输,在远程计算机之间创建连接。在传输过程中,为保证传输质量,TCP层将大数据分成长度合适的较小的数据包分别发送,并确保在目标节点重组后数据完整性保持不变。

一、什么是Socket?

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

二、Socket模块功能

1.socket()函数
socket.socket([family[, type[, proto]]] )使用给定的地址族套接字类型协议编号默认为0)来创建套接字。
套接字格式:

socket类型 描述
socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
socket.AF_INET 服务器之间通信
socket.AF_INET6 IPV6
socket.SOCK_STREAM 流式socket,for Tcp
socket.SOCK_DGRAM 数据报式socket,for UDP
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_SEQPACKET 可靠的连续数据包服务
创建TCP Socket: s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
创建UDP Socket: s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM

2.socket 对象(内建)方法

函数 描述
服务端socket函数
s.bind() 绑定地址(host,port)到套接字, 在 AF_INET下,以元组(host,port)的形式表示地址
s.listen() 开始 TCP 监听。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为 1,大部分应用程序设为 5 就可以了
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端 socket函数
s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收 TCP 数据,数据以字符串形式返回,bufsize 指定要接收的最大数据量。flag 提供有关消息的其他信息,通常可以忽略
s.send() 发送 TCP 数据,将 string 中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于 string 的字节大小
s.sendall() 完整发送 TCP 数据。将 string 中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回 None,失败则抛出异常。
s.recvfrom() 接收 UDP 数据,与 recv() 类似,但返回值是(data,address)。其中 data 是包含接收数据的字符串,address 是发送数据的套接字地址
s.sendto() 发送 UDP 数据,将数据发送到套接字,address 是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)
s.getsockname() 设置给定套接字选项的值
s.setsockopt(level,optname,value) 返回套接字选项的值
s.getsockopt(level,optname[.buflen]) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno() 返回套接字的文件描述符
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常
s.makefile() 创建一个与该套接字相关连的文件

三、简单实例

1.通用实例

服务端

#!/usr/bin/python3
# -*-coding:utf-8 -*-
from socket import *
import time
COD = 'utf-8'
HOST = '192.168.164.141' # 主机ip
PORT = 21566 # 软件端口号
BUFSIZ = 1024
ADDR = (HOST, PORT)
SIZE = 10 
tcpS = socket(AF_INET, SOCK_STREAM) # 创建socket对象
tcpS.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #加入socket配置,重用ip和端口
tcpS.bind(ADDR) # 绑定ip端口号
tcpS.listen(SIZE)  # 设置最大链接数
while True:
    print("服务器启动,监听客户端链接")
    conn, addr = tcpS.accept() 
    print("链接的客户端", addr)
    while True:
        try:
            data = conn.recv(BUFSIZ) # 读取已链接客户的发送的消息
        except Exception:
            print("断开的客户端", addr)
            break
        print("客户端发送的内容:",data.decode(COD))
        if not data:
            break
        msg = time.strftime("%Y-%m-%d %X") #获取结构化事件戳
        msg1 = '[%s]:%s' % (msg, data.decode(COD))
        conn.send(msg1.encode(COD)) #发送消息给已链接客户端
    conn.close() #关闭客户端链接
tcpS.closel()

客户端

#!/usr/bin/python3
# -*-coding:utf-8 -*-
from socket import *
from time import ctime
HOST = '192.168.164.141' #服务端ip
PORT = 21566 #服务端端口号
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcpCliSock = socket(AF_INET, SOCK_STREAM) #创建socket对象
tcpCliSock.connect(ADDR) #连接服务器
while True:
    data = input('>>').strip()
    if not data:
        break
    tcpCliSock.send(data.encode('utf-8')) #发送消息
    data = tcpCliSock.recv(BUFSIZ) #读取消息
    if not data:
        break
    print(data.decode('utf-8'))
tcpCliSock.close() #关闭客户端

2.文件传输

传输文件主要分以下两步:

1.将要传输的文件的基本信息发送到接收端(文件名、大小等其他信息)
2.发送端读取文件内容并发送过去,接受端将缓存里面的内容写入文件

服务端

#!/usr/bin/env python
# -*- coding=utf-8 -*-


"""
file: send.py
socket client
"""

import socket
import os
import sys
import struct


def socket_client():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('127.0.0.1', 6666))
    except socket.error as msg:
        print msg
        sys.exit(1)

    print s.recv(1024)

    while 1:
        filepath = raw_input('please input file path: ')
        if os.path.isfile(filepath):
            # 定义定义文件信息。128s表示文件名为128bytes长,l表示一个int或log文件类型,在此为文件大小
            fileinfo_size = struct.calcsize('128sl')
            # 定义文件头信息,包含文件名和文件大小
            fhead = struct.pack('128sl', os.path.basename(filepath),
                                os.stat(filepath).st_size)
            s.send(fhead)
            print 'client filepath: {0}'.format(filepath)

            fp = open(filepath, 'rb')
            while 1:
                data = fp.read(1024)
                if not data:
                    print '{0} file send over...'.format(filepath)
                    break
                s.send(data)
        s.close()
        break


if __name__ == '__main__':
    socket_client()

客户端

#!/usr/bin/env python
# -*- coding=utf-8 -*-


"""
file: recv.py
socket service
"""


import socket
import threading
import time
import sys
import os
import struct


def socket_service():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.bind(('127.0.0.1', 6666))
        s.listen(10)
    except socket.error as msg:
        print msg
        sys.exit(1)
    print 'Waiting connection...'

    while 1:
        conn, addr = s.accept()
        t = threading.Thread(target=deal_data, args=(conn, addr))
        t.start()

def deal_data(conn, addr):
    print 'Accept new connection from {0}'.format(addr)
    #conn.settimeout(500)
    conn.send('Hi, Welcome to the server!')

    while 1:
        fileinfo_size = struct.calcsize('128sl')
        buf = conn.recv(fileinfo_size)
        if buf:
            filename, filesize = struct.unpack('128sl', buf)
            fn = filename.strip('\00')
            new_filename = os.path.join('./', 'new_' + fn)
            print 'file new name is {0}, filesize if {1}'.format(new_filename,
                                                                 filesize)

            recvd_size = 0  # 定义已接收文件的大小
            fp = open(new_filename, 'wb')
            print 'start receiving...'

            while not recvd_size == filesize:
                if filesize - recvd_size > 1024:
                    data = conn.recv(1024)
                    recvd_size += len(data)
                else:
                    data = conn.recv(filesize - recvd_size)
                    recvd_size = filesize
                fp.write(data)
            fp.close()
            print 'end receive...'
        conn.close()
        break


if __name__ == '__main__':
    socket_service()

3.测试题目

本机实现一个服务器+客户端,包含以下功能
1、服务器可以同时接受一个客户端的链接
2、客户端连接成功后,可以输入以下信息并且获得正确的返回
a. Send: Hello Recive: Hello,This is Server {ip}:{port}
b. Send: 数学计算公式字符串 Recive: 计算结果后返回
c. 发送一个字符串 和一个匹配格式,返回匹配格式中的每一个值 : “Cpu:1235Id, \nTestId:0928N;” “Cpu:{cpuid}, \nTestId:{testid}” , 自己设计这个指令

3.断开链接后,服务器不能Crash,随时等待另外一次链接
4.客户端可以通过输入的方式,获取需要发送的信息,特殊指令:exit,断开客户端链接

服务端

# !/usr/bin/python
# -*- coding: utf-8 -*-
"""
@File    :   server.py
@Time    :   2022/04/12 
@Author  :   Algabeno
---------
Function List:
---------
"""



import re
import socket  # 导入 socket 模块
from threading import Thread
import time

ADDRESS = ('127.0.0.1', 8712)  # 绑定地址

g_socket_server = None  # 负责监听的socket



def init():
    """
    初始化服务端
    """
    global g_socket_server
    g_socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    g_socket_server.bind(ADDRESS)
    g_socket_server.listen(5)  # 最大等待数(有很多人理解为最大连接数,其实是错误的)
    print("server start,wait for client connecting...")


def accept_client():
    """
    接收新连接
    """
    while True:
        client, info = g_socket_server.accept()  # 阻塞,等待客户端连接
        # 给每个客户端创建一个独立的线程进行管理
        thread = Thread(target=message_handle, args=(client, info))
        # 设置成守护线程
        thread.setDaemon(True)
        thread.start()
        # thread.join()         # 阻塞线程,等待上一个线程处理完


def strip_str(string_value):
    """
    @description  : 去除字符串中的{}
    ---------
    @param  :字符串
    -------
    @Returns  :去除了{}的字符串
    -------
    """
    string_value = string_value.replace('{', '')
    string_value = string_value.replace('}', '')
    return string_value


def get_value(string_value):
    """
    @description  :对字符串进行正则化处理,获取冒号后的值
    ---------
    @param  :格式为:“name:nameinfo???,   \nAge:90” 或者  “name:{cpuid},   \nAge:{testid}
    -------
    @Returns  :nameinfo,90 或者 cpuid,testid
    -------
    """
    string_list = re.split(',', string_value)
    temp1 = re.split(':', string_list[0])[1]
    temp2 = re.split(':', string_list[1])[1]
    return strip_str(temp1), strip_str(temp2)


def match_str(string):
    """
    @description  : 对规定形式的字符串进行匹配
    ---------
    @param  :“name:nameinfo???,   \nAge:90”;“name:{cpuid},   \nAge:{testid}
    -------
    @Returns  :{"cpuid":"nameinfo???","testid":"90"}
    -------
    """
    value_list = re.split(';', string)
    name_info, name = value_list[0], value_list[1]
    value1, value2 = get_value(name_info)
    key1, key2 = get_value(name)
    final_value = '{' + f'{key1}:{value1},{key2}:{value2}' + '}'
    return final_value


def entrance_func(client, message):
    """
    @description  :对传入的message判断是计算字符串值还是进行字符串匹配
    ---------
    @param  :连接客户端的值,客户端发送过来的信息
    -------
    @Returns  : None
    -------
    """
    try:
        data = eval(message)
        client.sendall(f'The result is {data}'.encode('utf-8')) # 发送计算好的结果
    except Exception:
        try:
            data = match_str(message)
            client.sendall(f'{data}'.encode('utf-8'))   # 发送处理好的字符串
        except Exception:
            error = f'请发送正确的格式'
            client.sendall(f'{error}'.encode('utf-8'))  # 发送报错信息


def message_handle(client, info):
    """
    @description  :对连接的客户端发送过来
    ---------
    @param  :连接的客户端,客户端ip地址
    -------
    @Returns  :None
    -------
    """
    addr = client.getsockname()
    print(f"当前连接客户端ip为{info}")

    while True:
        try:
            msg = client.recv(1024).decode(encoding='utf8')  # 解析传来的信息
        except:
            print("断开的客户端", info)
            break
        if not msg:
            break
        print(f'已接收到{info}的信息:{msg}')
        if msg == 'exit':
            client.close()  # 关闭客户端
            break
        elif msg == 'Hello':
            host_info = g_socket_server.getsockname()
            client.sendall(f' Hello,This is Server {host_info[0]}:{host_info[1]}'.encode('utf-8'))  # 向客户端打招呼
        else:
            entrance_func(client, msg)





if __name__ == '__main__':
    init()
    # 新开一个线程,用于接收新连接
    thread = Thread(target=accept_client)
    thread.setDaemon(True)
    thread.start()
    # 主线程逻辑
    while True:
        time.sleep(0.1)


客户端

#!/usr/bin/python3
# -*-coding:utf-8 -*-
from socket import *
from time import ctime
HOST = '127.0.0.1'  # 服务端ip
PORT = 8712  # 服务端端口号
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcpCliSock = socket(AF_INET, SOCK_STREAM)  # 创建socket对象
tcpCliSock.connect(ADDR)  # 连接服务器
while True:
    data = input('请输入内容\n>>').strip()
    tcpCliSock.send(data.encode('utf-8'))  # 发送消息client.py
    if data == 'exit':
        break
    data = tcpCliSock.recv(BUFSIZ)  # 读取消息
    if not data:
        break
    print(data.decode('utf-8'))


# tcpCliSock.close()   # 关闭客户端

你可能感兴趣的:(Python,python,网络,开发语言)