socket网络编程

发布时间 2023-10-12 22:17:48作者: 七落安歌

Socket网络编程

一、计算机网络概述

1、IP地址的概念

IP地址就是标识网络中设备的一个地址,好比现实生活中的家庭住址。

网络设备的效果图:

image-20231009172439930

2、IP地址的表现形式

说明:

  • IP地址分为两类:IPv4 和 IPv6
  • IPv4 是目前使用的IP地址
  • IPv6 是未来使用的IP地址
  • IPv4是由点分十进制组成
  • IPv6是由冒号十六进制组成

3、IP地址的作用

IP地址的作用是标识网络中IP地址唯一的一台设备的,也就是说通过IP地址能找到网络中某台设备。

IP地址作用效果图:

image-20231009193838875

4、查看计算机的IP地址

linux 和 mac OS 使用ifconfig 这个命令

window 使用 ipconfig 这个命令

说明:

ifconfig 和 ipconfig 都是查看网卡信息的,网卡信息中包括这个设备对应的ip地址。

image-20231009194245019

在计算机中一般有两个IP地址:

  1. 上网的IP地址 => 192.168.89.100
  2. 本地回环地址 => 127.0.0.1 (一般用来本地测试)

5、检查网络是否正常

  • 检查网络是否正常使用ping命令

检查网络是否正常效果图:

image-20231009194433475

6、端口

问题思考

不同电脑上的微信之间进行数据通信,它是如何保证把数据给微信而不是给其它软件呢?

答:其实,每运行一个网络程序都会有一个端口,想要给对应的程序发送数据,找到对应的端口号即可。

端口效果图:

image-20231009200823826

什么是端口

  • 端口是传输数据的通道,好比教室的门,是数据传输的必经之路
  • 那么如何准确的找到对应的端口呢?
  • 其实,每一个端口都会有一个对应的端口号,好比每个教室的门都会有一个门牌号,想要找到端口通过端口号即可

端口号效果图:

什么是端口号

  • 操作系统为了统一管理这么多端口,就对端口进行了编号,这就是端口号,端口号其实就是一个数字,好比我们现在的门牌号
  • 端口号有65 536
  • 那么最终微信之间进行数据通信的流程是这样的,通过IP地址找到对应的设备,通过端口号找到对应的端口,然后通过端口将数据传输给应用程序

最终通信流程的效果图:

端口号的分类

  • 知名端口号
  • 动态端口号

知名端口号:

知名端口号是指众所周知的端口号,范围从0到1023

这些端口号一般固定分配给一些服务,比如21端口分配给FTP(文件传输协议)服务,25端口分配给SMTP(简单邮件传输协议)服务,80端口分配给HTTP服务。

动态端口号:

一般程序员开发应用程序 使用端口号 称为动态端口号,范围是从1024到65535。

如果程序员开发的程序没有设置端口号,操作系统会在动态端口号这个范围内随机生成一个给开发应用程序使用。

注:当运行一个程序会默认有一个端口号,当这个程序退出时,所占用的这个端口号就会被释放。

小结

  • 端口的作用就是给运行的应用程序提供传输数据的通道。
  • 端口号的作用是用来区分和管理不同端口的,通过端口号就能找到唯一的一个端口。
  • 端口号可以分为两类:知名端口号动态端口号
    • 知名端口号的范围是0到1023
    • 动态端口号的范围是1024到65535

二、TCP概述

1、网络应用程序之间的通信流程

通过上述我们知道,通过IP地址能够找到对应的设备,然后再通过端口号找到对应的端口,再通过端口把数据传输给应用程序,这里要注意,数据不能随便发送,在发送之前还要选择一个对应的传输协议(TCP或UDP),保证程序之间按照指定的传输规则进行数据的通信,这个协议就是TCP协议。

2、TCP概念

TCP的英文全称为(Transmission Control Protocol)简称为传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议

面向连接的效果图:

image-20231009210910224

TCP通信步骤: ①创建连接 ②传输数据 ③关闭连接

  • TCP通信模型相当于生活中的打电话,在通信之前,一定要建立起连接,才能发送数据,通信结束要关闭连接。

3、TCP特点

① 面向连接

通信双方必须先建立好连接才能进行数据的传输,并且双方都会为此连接 分配 必要的资源来记录连接的状态和信息。当数据传输完成之后,双方必须断开连接,以释放系统资源。

② 可靠传输

  • TCP采用发送应答机制

通过TCP这个方式发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段发送成功

  • 超时重传

发送端发送一个报文之后就会启动定时器,如果指定时间没有得到应答就会重新发送这个报文段

  • 错误检验

TCP使用一个校验和函数来检验数据是否有错误,在发送和接收时都要计算校验和

  • 流量控制和阻塞管理

流量控制用来控制避免发送段发送过快而使得接收方来不及接收

4、扩展:UDP协议(不可靠传输协议)

TCP可靠协议(数据100%传输)

日常通信、数据传输一定要保证可靠性,使用TCP

UDP不可靠协议(只能保证速度,但是没办法保证数据传输的质量,可能发送5M => 数据3.75M)

有些情况下,我们对数据的质量要求不大,可以使用UDP,比如视频通话。

三、socket介绍

1、问题思考

到目前为止我们学习了 IP 地址和端口号还有 TCP 传输协议,为了保证数据的完整性和可靠性我们使用 TCP 传输协议进行数据的传输,为了能够找到对应设备我们需要使用 IP 地址,为了区别某个端口的应用程序接收数据我们需要使用端口号,那么通信数据是如何完成传输的呢?

答:使用socket通信工具

2、socket套字节

socket(简称 套接字)是进程之间的一个通信工具,好比生活中的一个插座,所有的家用电器要想工作必须基于插座进行,进程之间想要进行网络通信需要基于这个 socket

socket效果图:

image-20231009214917308

3、socket的应用场景

socket的作用:负责进程之间的网络数据传输,好比数据的搬运工。

socket的应用场景:

不夸张的说,只要跟网络相关的应用程序或者软件都使用到了 socket

image-20231009215127077

四、网络应用程序开发流程

1、TCP网络应用程序开发流程的介绍

TCP网络应用程序开发分为:

① TCP客户端程序开发

② TCP服务端程序开发

说明:

客户端程序是指运行在用户设备上的程序

服务端程序是指运行在服务器设备上的程序,专门为客户端提供数据服务。

2、客户端程序开发

流程介绍

  1. 创建客户端套接字对象 (socket() )
  2. 和服务端套接字建立连接 (connect() )
  3. 发送数据 (send() )
  4. 接收数据 (recv() )
  5. 关闭客户端套接字 (close() )

socket类介绍

  • 导入socket类

    import socket

  • 创建客户端socket类对象

    socket.socket(AddressFamily, Type)

  • 参数说明:

    AddressFamily:表示IP地址类型,分为IPv4和IPv6

    Type 表示传输协议类型

    socket.AF_INET : IPv4
    socket.SOCK_STREAM :TCP协议
    
  • 方法说明:

    connect((host, port)) 表示和服务端套接字建立连接,host是服务器的ip地址,port是应用程序的端口号

    send( data ) 表示发送数据,data是二进制数据

    recv(buffersize) 表示接受数据,buffersize是每次接受数据的长度。

"""
TCP 客户端开发五步走: ① 创建客户端套接字对象 ② 建立连接 ③ 发送数据 ④ 接收数据 ⑤ 关闭套接字对象
注意:
1.tcp_client_socket.connect( () )  => 连接服务器(ip地址,端口号) 参数是一个元组
2.发送send()、接收recv() 无论数据的发送或接收都必须是二进制流数据
encode():将字符串转换为二进制流数据
decode():将二进制流数据转换为字符串
"""

import socket

# 创建客户端套接字对象
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 和服务器建立连接(需要IP地址和端口号)
tcp_client_socket.connect(('127.0.0.1', 8080))  # 接收的是一个元组

# 发送数据 send()发送的必须是一个二进制流的数据
tcp_client_socket.send('I love python'.encode('gbk'))

# 接收服务器应答返回的数据
content = tcp_client_socket.recv(1024).decode('gbk')  # 设置接收返回的大小,并将服务器返回的二进制流数据解码
print(f'服务器应答返回的数据:{content}')

# 关闭套接字对象
tcp_client_socket.close()

image-20231010102458521

3、服务端程序开发

流程介绍

  1. 创建服务端套接字对象 (socket() )
  2. 绑定端口号 (bind() )
  3. 设置监听 (listen() )
  4. 等待接收客户端的连接请求 (accept() )
    • 思考一个问题,怎么判断到底是哪个客户发起的连接呢?
  5. 接收数据 (recv() )
  6. 发送数据 (send() )
  7. 关闭套接字 ( close() )

socket类

  • 导入 socket 模块
    import socket

  • 创建服务端 socket 对象
    socket.socket(AddressFamily, Type)

  • 参数说明:

​ AddressFamily 表示IP地址类型, 分为IPv4和IPv6

​ Type 表示传输协议

  • 方法说明:

bind((host, port)) 表示绑定端口号, host 是 ip 地址,port 是端口号,ip 地址一般不指定,表示本机的任何一 个ip地址都可以,这个绑定的是运行这段代码的电脑而不能是其他的设备

listen (backlog) 表示设置监听,backlog参数表示最大等待建立连接的个数,就是能同时接收的最大客户端数

accept()表示等待接受客户端的连接请求

send(data) 表示发送数据,data 是二进制数据

recv(buffersize) 表示接收数据, buffersize 是每次接收数据的长度

"""
TCP服务器端开发七步走:① 创建socket服务器套接字对象 ② 绑定ip和端口号 ③ 设置监听 ④ 等待客户端连接请求
⑤ 接收数据 ⑥ 发送数据 ⑦ 关闭socket服务器套接字对象

"""
import socket

# 创建服务器端套接字对象
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 两个参数分别代表着ipv4 和 tcp协议

# 绑定ip和端口号 用于提供给客户端建立连接
# 这里传入的参数也是一个元组类型参数,如是绑定本机,ip地址不用设置,默认是127.0.0.1
tcp_server_socket.bind(('127.0.0.1', 8080))

# 设置监听
# listen后的这个套接字只负责接收客户端请求,不能收发信息,收发信息使用返回的新套接字来完成
# 128代表设置的最大等待建立连接的个数
tcp_server_socket.listen(128)

# 等待客户端发送连接请求
# 返回是一个元组,第一个元素为新的套接字,对于该客户端的收发信息都是这个套接字完成
# 第二个参数是请求连接客户端的IP地址和端口号,也是一个元组类型
# 等待客户端建立连接请求,只有客户端和服务端连接成功,代码才会解除阻塞,代码才能继续往下执行,类似于input函数
new_socket, ip_port = tcp_server_socket.accept()

# 接收数据 1024表示这次接收数据的最大字节数为1024
content = new_socket.recv(1024).decode('gbk')
print(f'已经接收到{ip_port}发送过来的数据:{content}')

# 应答客户端,发送数据
new_socket.send('信息已收到,over!'.encode('gbk'))  # 一定要编码为二进制流数据

# 关闭新套接字对象(不能收发信息)以及服务器端套接字对象(不能接收客户端的连接)
new_socket.close()
tcp_server_socket.close()

accept()功能:

image-20231010105523971

  • tcp_server_socket 内部只有服务器本身的信息,可以绑定端口,设置监听,接收客户端连接,但是本身不能收发数据

  • new_socket 新套接字对象,和tcp_server_socket 不同。里面保存了服务器端数据,还保存客户端数据,所以可以让服务器通过new_socket收发数据。

面向对象版的服务端的开发

import socket


class WebServer(object):
    def __init__(self):
        # 这里将创建socket套接字对象变量作为类实例化对象属性存储
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 绑定端口
        self.tcp_server_socket.bind(('', 8080))
        # 设置监听
        self.tcp_server_socket.listen(128)

    # 启动服务的功能
    def start(self):
        while True:  # 允许多个客户端连接 accept具有与input类似的阻塞功能,只有当接收到客户端的连接代码才会往下执行
            # 等待客户端的连接
            new_socket, ip_port = self.tcp_server_socket.accept()
            # 接收客户端数据
            content = new_socket.recv(1024).decode('gbk')
            print(f'已经接收到{ip_port}客户发来的数据:{content}')
            # 发送数据(做出应答)
            new_socket.send('信息已收到,over!'.encode('gbk'))
            # 关闭套接字对象
            new_socket.close()

if __name__ == '__main':
    # 首先实例化一个服务器对象,调用__init__()初始化方法
    ws = WebServer()
    # 启动服务,接收客户端连接
    ws.start()

端口复用

  • 防止服务器在重启时之前绑定的端口号还未释放
  • 程序突然退出而系统还没释放端口
def setsockopt(self, level: int, optname: int, value: Union[int, bytes]) -> None:   
参数:
    level:级别,有不同的选项,端口复用选项使用 SOL_SOCKET
    optname:选项的名称,也有不同的选项,两个常见的选项为:
        SO_REUSEADDR:允许使用本地地址,数据类型为int
        SO_REUSEPORT:允许重用本地端口,数据类型为int
    value:默认值为0,False
    1,True:可以复用
    0,False:不可以复用
  • 注意:端口复用,设置时机是在服务器绑定端口之前,即先setcockopt() 再bind()
import socket


class WebServer(object):
    def __init__(self):
        # 创建套字节对象,并采用IPv地址与TCP传输控制协议
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # 设置端口复用(让服务器端占用的端口号在执行结束之后可以立即被释放,不影响后续程序的使用)
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

        # 绑定端口号和ip地址
        self.tcp_server_socket.bind(('', 8080))
        # 设置监听,128表示同一时间最多允许128个客户端访问
        self.tcp_server_socket.listen(128)

    # 定义一个函数,封装发送和接收功能
    def handle_request(self, new_socket, ip_port):
        content = new_socket.recv(1024).decode('gbk')
        print(f'接收到客户端{ip_port}发送的数据:{content}')
        # 服务器做出应答
        reply = input('请输入服务器做出的应答:')
        new_socket.send(reply.encode('gbk'))
        # 关闭新的套接字对象
        new_socket.close()

    def start(self):
        while True:
            # 等待客户端的连接
            new_socket, ip_port = self.tcp_server_socket.accept()
            self.handle_request(new_socket, ip_port)


if __name__ == '__main__':
    # 实例化一个服务器对象
    ws = WebServer()
    # 启动服务器
    ws.start()

4、TCP网络应用程序的注意点

  1. 当TCP客户端程序想要和TCP服务端程序进行通信的时候必须先建立连接
  2. TCP客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的
  3. TCP服务端程序必须绑定端口号,不然客户端找不到这个TCP服务端程序
  4. listen后的套接字是被动套接字,只负责接收新的客户端的连接请求,不能收发信息
  5. 当TCP客户端程序和TCP服务器端程序连接成功后,TCP服务器端会产生一个新的套接字,收发信息使用这个套接字
  6. 关闭accept()返回的套接字意味着和这个客户端已经通信完毕
  7. 关闭listen后的套接字意味着新的服务器端的套接字已经关闭,会导致新的客户端不能连接服务器,但是之前已经连接成功的客户端还能正常通信
  8. 当客户端的套接字调用close后,服务器端的recv会解阻塞,返回的数据长度为0,服务器端可以通过返回数据的长度判断客户端是否已经下线,反之服务器端关闭套接字,客户端的recv()也会解阻塞,返回的数据长度也为0

image-20231009221405092