TCP网络编程相关概念原理及其代码实现

一、ip地址

IP地址:是标识网络设备的一个地址,作用是标识网络中唯一的一台设备,即通过IP地址就可以找到网络中的某台设备

  • 分类
    • IPv4:网际协议版本4,是网际协议开发过程中的第四个修订版本,也是该协议第一个被广泛部署的版本,2019年IPv4地址已分配完毕
    • IPv6:网际协议第6版,是互联网工程任务组(IETF)设计的用于替代IPv4的下一代IP协议,其地址数量号称可以为全世界的每一粒沙子编上一个地址
  • 查看IP地址
    • Linux和macOS命令:ifconfig
    • Windows命令:ipconfig
    • ifconfig和ipconfig都是查看网卡信息的,其中包含该设备对应的IP地址
    • ipconfig看到的是私网IP,浏览器查到的是公网IP
  • 检测网络是否正常
    • 命令:ping 域名/ip地址
    • 域名是IP地址的别名,通过域名能解析出一个对应的IP地址
  • 说明
    • ping baidu.com:检查能否上公网,若不能将显示请求超时
    • ping 当前局域网IP:检查是否在同一个局域网内
    • ping 127.0.0.1:检查本地网卡是否正常

二、端口与端口号

端口:是传输数据的通道,每运行一个应用程序都会有一个端口,要想给对应程序发送数据,找到对应端口即可,端口是数据传输的必经之路

端口号:是操作系统为了统一管理端口而对端口所设定的编号,端口号为一个数字,就好比门牌号,可以唯一标识一个端口,端口号有65536个

程序间的通信流程:通过IP地址找到对应设备,再通过端口号找到对应的端口,然后通过端口将数据传输给相应的应用程序。数据不可随意发送,发送前要选择一个传输协议,保证程序间按指定的传输规则进行数据的通信

  • 端口号分类
    • 知名端口号:指众所周知的端口号,范围从0~1023,该类端口号一般固定分配给一些服务,如端口
      • 21:分配给FTP文件传输协议服务
      • 22:分配给ssh远程登录协议服务
      • 23:分配给Telnet终端仿真协议服务
      • 25:分配给SMTP简单邮件传输协议服务
      • 80:分配给http超文本传输协议服务
    • 动态端口号:一般指程序员开发应用程序使用的端口号,范围从1024~65535
      • 若程序员开发的应用程序没有设置端口号,操作系统会在动态端口号范围内随机生成一个给开发的应用程序使用
      • 运行一个程序默认会有一个端口号,程序退出时,所占用端口号就会释放

三、TCP及其应用开发

TCP(Transmission Control Protocol):即传输控制协议,是一种面向连接的、可靠的基于字节流的传输层通信协议,常用于对数据进行准确无误的传输

  • TCP通信步骤
    • 创建连接
    • 传输数据
    • 关闭连接
  • TCP特点
    • 面向连接:通信双方必须先建立好连接才能进行数据的传输,数据传输完成后,双方必须断开此连接,已释放系统资源
    • 可靠传输:TCP采用发送应答机制,超时重传,错误检测,流量控制和阻塞管理

3.1 socket

socket:简称套接字,是进程之间通信的一个工具,好比插座,所有电器要想工作都是基于插座进行,进程之间想要进行通信则是基于socket

socket作用:负责进程之间的网络数据传输通信,是数据的搬运工

3.2 TCP网络应用程序开发流程

  • TCP网络应用程序开发分为
    • TCP客户端程序开发
      • 客户端程序:指运行在用户设备上的程序,主动发起建立连接请求
      • 开发步骤流程
        • 1.创建客户端套接字对象socker()
        • 2.和服务端套接字建立连接connect(),TCP三次握手
        • 3.发送数据send(),请求
        • 4.接收数据recv(),应答
        • 5.关闭客户端套接字close()
    • TCP服务端程序开发
      • 服务端程序:指运行在服务器设备上的程序,专为客户端提供数据服务,等待接受连接请求
      • 开发步骤流程
        • 1.创建服务端套接字对象socket()
        • 2.绑定端口号bind()
        • 3.设置监听listen()
        • 4.等待接收客户端的连接请求accept()
        • 5.接收数据recv()
        • 6.发送数据send()
        • 7.关闭套接字close()

3.3 socket类及方法说明

  • 创建客户端或服务端socket对象:对象名 = socket.socket(AddressFamily,Type)
    • AddressFamily:表示IP地址类型,分为IPv4和IPv6
    • Type:表示传输协议类型
  • 方法说明
    • 客户端
      • connect((host,port)):表示和服务端套接字建立连接,host是服务器IP地址,port是应用程序端口号
      • send(data):表示发送数据,data为二进制数据,字符串需用encode()编码
      • recv(buffersize):表示接收数据,buffersize是每次接收数据的长度,使用decode()解码为字符串
      • closed():关闭套接字表示通信完成
      • str.encode(编码格式):表示把字符串编码为二进制
      • data.decode(编码格式):表示把二进制解码为字符串
    • 服务端
      • bind((host,port)):表示绑定端口,host是IP地址,port为端口号,IP地址一般不指定,表示本机的任何一个IP地址都可以
      • listen(backlog):表示设置监听,backlog参数表示最大等待建立连接的个数
      • accept():表示等待接受客户端的连接请求
      • send(data):表示发送数据,data为二进制数据
      • recv(buffersize):表示接收数据,buffersize是每次接收数据的长度

3.4 TCP客户端

client.py文件如下

import socket  # 导入socket模块
if __name__ == '__main__':
    client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)   # 创建TCP客户端套接字,AF_INET表示IPv4,SOCK_STREAM表示TCP传输协议
    client_socket.connect(("192.168.209.1", 8080))   # 和服务端应用程序建立连接
    send_data = "Hello server,I am client!".encode('gbk')   # 要发送的数据
    client_socket.send(send_data)   # 发送数据
    recv_data = client_socket.recv(1024)   # 接收数据,最大字节为1024
   print('服务端发送的二进制数据为:', recv_data)
    recv_decode = recv_data.decode('gbk')   # 对数据解码
    print('接收到的服务端数据为:', recv_decode)
    client_socket.close()

服务端发送数据后,输出为:
服务端发送的二进制数据为:b'Hello client\xa3\xa1I am server\xa3\xa1'
接收到的服务端数据为: Hello client!I am server!

使用网络调试助手netassist充当服务端程序

3.5 TCP服务端

  • 客户端和服务端建立连接后,服务端程序退出后端口号不会立即释放,需等待1~2分钟
  • 解决方法
    • 更换服务端端口号
    • 设置端口号复用,让服务端程序退出后端口号立即释放
      • 设置代码:server_socket.setsocket(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        • 参数1:表示当前套接字
        • 参数2:设置端口号复用选项
        • 参数3:设置端口号复用选项对应的值

server.py文件如下 

import socket
if __name__ == '__main__':
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # 创建TCP服务端套接字
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)   # 设置端口号复用,让程序退出端口号立即释放
    server_socket.bind(("", 8989))   # 绑定端口号,ip不指定,表示本机的任意一个IP地址均可
    server_socket.listen(128)   # 设置监听,最大等待连接的个数为128,此处为单任务服务端,同一时刻只能服务一个客户端
    # 无需让客户端进行等待建立连接
    # listen监听后的这个套接字server.socket只负责接收客户端连接请求,不能收发消息,收发消息使用返回的这个新套接字comm_socket来完成

    comm_socket, ip_port = server_socket.accept()   # 等待客户端建立连接的请求,只有客户端与服务端成功建立连接才会解阻塞,代码才能继续执行
    # comm_socket为专门和客户端通信的套接字,ip_port为客户端的IP地址和端口号

    print('客户端ip地址和端口号:', ip_port)
    recv_data = comm_socket.recv(1024)   # 接收客户端发送的二进制数据
    print('接收到的数据为:%s,长度为:%d' % (recv_data, len(recv_data)))
    recv_data_decode = recv_data.decode('gbk')  # 对二进制数据进行解码
    print('接收到的客户端数据为:', recv_data_decode)
    send_data = '已收到,正在处理……'.encode('gbk')   # 准备要发送给的数据
    comm_socket.send(send_data)   # 发送数据给客户端
    comm_socket.close()   # 关闭客户端的套接字,终止和客户端通信服务
    server_socket.close()   # 关闭服务端的套接字,终止和客户端提供建立连接请求的服务

客户端发送数据后,输出为:
客户端ip地址和端口号: ('192.168.76.1', 64494)
接收到的数据为:b'Hello server\xa3\xacI am client\xa3\xa1\xa3\xa1\xa3\xa1',长度为:31
接收到的客户端数据为: Hello server,I am client!!!

使用网络调试助手netassist充当客户端程序

使用client.py作为客户端,server.py作为服务端

# 需更改client.py文件中代码如下
client_socket.connect(("127.0.0.1", 8989))   # 和服务端server.py程序建立连接

client.py输出:
服务端发送的二进制数据为: b'\xd2\xd1\xca\xd5\xb5\xbd\xa3\xac\xd5\xfd\xd4\xda\xb4\xa6\xc0\xed\xa1\xad\xa1\xad'
接收到的服务端数据为: 已收到,正在处理……

server.py输出:
客户端ip地址和端口号: ('127.0.0.1', 60313)
接收到的数据为:b'Hello server\xa3\xacI am client\xa3\xa1',长度为:27
接收到的客户端数据为: Hello server,I am client!

四、多任务版TCP服务端程序开发

  • 需求:开发一个多任务的TCP服务端程序能够服务于多个客户端,使用线程,更节省资源
  • 具体步骤
    • 编写TCP服务端程序,循环等待接受客户端连接请求
    • 若客户端和服务端成功建立连接,创建子线程,用子线程专门处理客户端的请求,防止主线程阻塞
    • 把创建的子线程设为守护主线程,防止主线程无法退出

实现代码

import socket
import threading
def handle_client_request(comm_socket, ip_port):   # 处理客户端请求操作
    while True:   # 循环接收客户端发送的数据
        recv_data = comm_socket.recv(1024)   # 接收客户端发送的数据
        if recv_data:  # 判断是否有数据
            print('接收到的数据为:%s,IP地址和端口号为:%s' % (recv_data.decode('gbk'), ip_port))
            comm_socket.send('已收到,正在处理……'.encode('gbk'))
        else:
            print('客户端已下线,IP地址和端口号为', ip_port)
            break
    comm_socket.close()   # 终止与客户端的通信

if __name__ == '__main__':
    server_scoket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # 创建TCP服务端套接字
    server_scoket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)   # 设置端口号复用,让程序退出端口号立即释放
    server_scoket.bind(("", 9090))   # 绑定端口号
    server_scoket.listen(128)   # 设置监听, listen后的套接字是被动套接字,只负责接收客户端的连接请求
    while True:   # 循环等待接收客户端连接请求
        comm_socket, ip_port = server_scoket.accept()   # 等待接收客户端的连接请求
        print('客户端连接成功!IP地址和端口号为:', ip_port)
        thread = threading.Thread(target=handle_client_request, args=(comm_socket, ip_port))   # 成功创建连接后,需创建一个子线程,不同子线程负责接收不同客户端的消息,防止主线程阻塞
        thread.setDaemon(True)   # 设置守护主线程,防止主线程无法退出
        thread.start()
        # server_scoket.close()   # 服务端套接字可不关闭,因其需要一直运行

使用网络调试助手和client.py作为客户端发送数据后,输出如下:
客户端连接成功!IP地址和端口号为: ('192.168.76.1', 63805)
接收到的数据为:Hello server,I am client1!,IP地址和端口号为:('192.168.76.1', 63805)
客户端连接成功!IP地址和端口号为: ('127.0.0.1', 63820)
接收到的数据为:Hello server,I am client!,IP地址和端口号为:('127.0.0.1', 63820)
客户端已下线,IP地址和端口号为 ('127.0.0.1', 63820)
# 等待客户端连接状态

五、send()与recv()原理

创建一个TCP socket对象时,会有一个发送缓冲区和接收缓冲区,缓冲区即指一片内存空间

  • send()原理:send想要发送数据,需通过网卡,应用程序无法直接通过网卡发送数据,需调用操作系统接口,即应用程序会先将要发送的数据写入到发送缓冲区,再由操作系统控制网卡把发送缓冲区的数据发送给服务端网卡
  • recv()原理:recv想要接收数据,应用程序无法直接通过网卡接收数据,需调用操作系统接口,由操作系统通过网卡接收数据,先把要接受的数据写入到接收缓冲区,应用程序再从接收缓冲区获取客户端发送的数据
  • 都不能直接接收或发送数据至对方,发送数据写入到发送缓冲区,接收数据从接收缓冲区读取,发送和接收数据最终操作系统控制网卡来完成

学习导航:http://xqnav.top/ 

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

learning-striving

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值