番外篇幅-socket编程以及例子

前言:

在学习自顶向下应用层时候,老师讲到socket编程,听的一愣一愣的,于是决定要加餐补课,自己用python 实现了 一个服务器 和客户端 实现 一人发一句 那边收到再回复,这篇文章争取把socket编程说透彻。

Socket在网络中位置:

图片[1]-番外篇幅-socket编程以及例子-Drton1博客

定义:

socket 的诞生是为了应用程序能够更方便的将数据经由传输层来传输,所以它本质上就是对 TCP/IP 的运用进行了一层封装,然后应用程序直接调用 socket API 即可进行通信。那么它是如何工作的呢?它分为 2 个部分,服务端需要建立 socket 来监听指定的地址,然后等待客户端来连接。而客户端则需要建立 socket 并与服务端的 socket 地址进行连接。

就是一个轮子 别人给你做好了 ,这样你进行网络编程的时候 就可以很方便的调用这些轮子,极大的提升生产力。

图片[2]-番外篇幅-socket编程以及例子-Drton1博客

基于 TCP 协议的客户端和服务器工作

  • 服务端和客户端初始化 socket,得到文件描述符;
  • 服务端调用 bind,将绑定在 IP 地址和端口;
  • 服务端调用 listen,进行监听;
  • 服务端调用 accept,等待客户端连接;
  • 客户端调用 connect,向服务器端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。 这里需要注意的是,服务端调用 `accept` 时,**连接成功了会返回一个已完成连接的 socket**,后续用来传输数据。 所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。 参数解释: domain:指明所使用的协议族,通常为AF_INET ,表示互联网协议族( TCP/IP协议族) ;
  • AF_INET——IPv4因特网域
  • AF_INET6——IPv6因特网域
  • AF_UNIX——Unix域
  • AF_ROUTE——路由套接字
  • AF_KEY——密钥套接字
  • AF_UNSPEC——未指定

(1)TCP服务端程序

# ! /usr/bin/env python3
# _*_ coding: utf-8 _*_

from socket import *

# 变量名大写,约定作为配置项的意思,这里定义了三个配置变量作为实例化的参数
# 定义IP地址、端口、缓存,缓存的单位是Byte
IP = '127.0.0.1'
PORT = 60000
BUFLEN = 1024

# 实例化一个socket赋值给变量listen_socket
# 参数AF_INET就是IP地址族
# 参数SOCK_STREAM就是TCP、参数SOCK_DGRAM就是UDP、参数SOCK_RAW是原始套接字,为TCP、UDP之外的协议提供接口
listen_socket = socket(AF_INET, SOCK_STREAM)

# 为这个socket绑定IP和端口,bind的对象必须是一个包含IP和端口元组
listen_socket.bind((IP, PORT))

# 使socket处于监听状态,等待客户端的请求,只有服务端能使用监听
# 参数5表示,最多接收5个等待连接的客户端
listen_socket.listen(5)
print(f'Server: 服务启动成功,在{PORT}端口,等待 Client 连接... ...')

# 监听socket的accept方法,用来接收客户端的连接,如果没有客户端连接,就一直处于监听状态(阻塞状态)
# 直到有客户端连接,一旦客户段发起连接(TCP三次握手)accept方法就会返回一个元组,一个data_socket来传输数据,一个ipaddress包含IP和PORT
data_socket, ip_port = listen_socket.accept()
print(f'Server>>> 接受一个客户端{ip_port}连接... ...')

while True:
    # 尝试读取客户端发来的消息
    # BUFLEN指定从接收缓冲里最多读取多少Bytes,recved是一个字节串变量
    recved = data_socket.recv(BUFLEN)

    # 如果返回空的Bytes,意味着对方关闭了连接
    # 退出循环,结束消息收发
    if not recved:
        break

    # 把读取的Bytes数据,转换成字符串编码,赋值给变量info,然后打印出来
    info = recved.decode('utf-8')
    print(f'Server>>> 收到客户端{ip_port}消息: {info}')

    # 通知客户端的字符串,编码为utf-8的Bytes类型数据
    #data_socket.send(f'Server>>> 服务器{(IP, PORT)}已经收到发来的消息内容为 {info}'.encode('utf-8'))
    sent=input('Server>>>')
    data_socket.send(sent.encode('utf-8'))
data_socket.close()
listen_socket.close()

(2)TCP客户端程序

#! /usr/bin/env python3
# _*_ coding: utf-8 _*_

from socket import *

IP = '127.0.0.1'
PORT = 60000
BUFLEN = 1024

# 实例化了一个socket对象,赋给变量data_socket
data_socket = socket(AF_INET, SOCK_STREAM)

# 调用connect方法,连接服务器,connet就是连接正在等待的服务器端的listen_socket
data_socket.connect((IP, PORT))

while True:
    toSend = input('client>>> ')
    if toSend == 'exit':
        break

    data_socket.send(toSend.encode('utf-8'))

    recved = data_socket.recv(BUFLEN)
    if not recved:
        break
    #print(recved.decode('utf-8'))
    info = recved.decode('utf-8')
    print(f'client>>> 收到服务器端消息: {info}')
data_socket.close()

实验截图:

先运行 server ,再运行client 连接成功

图片[3]-番外篇幅-socket编程以及例子-Drton1博客

客户端发出第一句话

图片[4]-番外篇幅-socket编程以及例子-Drton1博客

服务端看到的:

图片[5]-番外篇幅-socket编程以及例子-Drton1博客

服务端回应:

图片[6]-番外篇幅-socket编程以及例子-Drton1博客

客户端看到的:

图片[7]-番外篇幅-socket编程以及例子-Drton1博客

打开cmd 查看 网络状态

图片[8]-番外篇幅-socket编程以及例子-Drton1博客

两个已经建立的连接 就是我们刚才连接的客户端和服务器

还有一个服务器的监听,因为设置了服务器最多连接五个客户端 ,所以目前还在监听。

实验结束。

到这里 基本已经理解了Socket编程,想要更深入的就需要剖析源码了,我找了些扩展,可以深入了解。

深度扩展:

为什么服务端程序都需要先 listen 一下?

Socket视角下深度理解三次握手

Linux 网络包发送过程

Linux网络包接收过程

深入理解高性能网络开发路上的绊脚石 – 同步阻塞网络 IO

深入揭秘 epoll 是如何实现 IO 多路复用的

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 共1条

请登录后发表评论