Socket套接字编程 tcp协议

一:socket的通信流程介绍

 

1.什么是Socket

socket是应用层 与 传输层 中间的软件抽象层,是一组接口。

在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面.

对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

 

2.Socket套接字的发展史

套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。

因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。

一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯,这也被称进程间通讯或 IPC。

套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。

 

①基于文件类型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

 

②基于网络类型的套接字家族

套接字家族的名字:AF_INET

还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET。

 

3.TCP与UDP

TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)

 

①话不多说,上图:

 

②UDP详解

UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。

1.面向无连接

首先 UDP 是不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工不会对数据报文进行任何拆分拼接操作。

  • 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了
  • 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作
2. 有单播,多播,广播的功能

UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。

3. UDP是面向报文的

发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文

4. 不可靠性

首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠

并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。

再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据

即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。

5.头部开销小,传输数据报文时是很高效的

UDP 头部包含了以下几个数据:

  • 2个十六位的端口号,分别为源端口(可选字段)和目标端口
  • 整个数据报文的长度
  • 整个数据报文的检验和(IPv4 可选 字段),该字段用于发现头部信息和数据中的错误

因此 UDP 的头部开销小,只有8字节,相比 TCP 的至少20字节要少得多,在传输数据报文时是很高效

 

③TCP详解

当一台计算机想要与另一台计算机通讯时,两台计算机之间的通信需要畅通且可靠,这样才能保证正确收发数据。

例如,当你想查看网页或查看电子邮件时,希望完整且按顺序查看网页,而不丢失任何内容。当你下载文件时,希望获得的是完整的文件,而不仅仅是文件的一部分,因为如果数据丢失或乱序,都不是你希望得到的结果,于是就用到了TCP。

TCP协议全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,由 IETF 的RFC 793定义。

TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,你可以把它想象成排水管中的水流。

1.三次握手 与 四次挥手

详细请移步http://www.xuexianqi.top/index.php/archives/154/

2.面向连接

面向连接,是指发送数据之前必须在两端建立连接

建立连接的方法是“三次握手”,这样能建立可靠的连接

建立连接,是为数据的可靠传输打下了基础

3.仅支持单播传输

每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播广播传输方式。

4.面向字节流

TCP不像UDP一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输

5.可靠传输

对于可靠传输,判断丢包,误码靠的是TCP的段编号以及确认号

TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传

6.提供拥塞控制

网络出现拥塞的时候,TCP能够减小向网络注入数据速率和数量缓解拥塞

7.TCP提供全双工通信

TCP允许通信双方的应用程序任何时候都能发送数据,因为TCP连接的两端都设有缓存,用来临时存放双向通信的数据

当然,TCP可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于MSS)

 

④TCP与UDP对比

对比UDPTCP
是否连接 无连接 面向连接
是否可靠 不可靠传输,不使用流量控制和拥塞控制 可靠传输,使用流量控制和拥塞控制
连接对象个数 支持一对一,一对多,多对多交互通信 只能是一对一通信
传输方式 面向报文 面向字节流
首部开销 首部开销小,仅8字节 首部最小20字节,最大60字节
适用场景 适用于实时应用(IP电话、视频会议、直播等) 适用于要求可靠传输的应用,例如文件传输
 

⑤查看网络接口信息:

netstat -an

netstat -an |findstr 8080
 

二:基于TCP协议通信的套接字程序

服务端要满足的特性:

1.一直对外提供服务

2.并发地提供服务

 

1.基础版

server.py

import socket

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM --> TCP协议(流式协议)

# 2.插手机卡
server_ip = '127.0.0.1'
server_port = 8080
phone.bind((server_ip, server_port))  # 127.0.0.1:本地回环地址,只能本机访问

# 3.开机
phone.listen(5)  # backlog:半连接池
print(f'服务端开始启动,IP:{server_ip},端口:{server_port}')

# 4.等电话连接 - 循环
while True:
    conn, client_addr = phone.accept()  # conn代表双向连接,可以收数据,也可以发数据;client_addr:客户端地址
    print(f'客户端地址:{client_addr}')

    # 5.收消息
    while True:
        try:
            recv_data = conn.recv(1024)  # 1024:最大接收的字节个数,超过1024个的字节,下次发送
            data = recv_data.decode('UTF-8')
            print('收到的客户端的数据:', data)
            conn.send(recv_data.upper())  # 此处的data是bytes类型,可以直接转换成大写
            print(f'服务端将接收的数据:{data} 转成了大写,发给了客户端')
        except Exception:
            print('出现了异常')
            break

    # 6.关闭
    conn.close()  # 挂掉电话
phone.close()  # 关机

client.py

import socket

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM --> TCP协议(流式协议)

# 2.拨通电话
phone.connect(('127.0.0.1', 8080))

# 3.发消息 - 循环
while True:
    msg = input('请输入要发送的消息:').strip()
    phone.send(msg.encode('UTF-8'))
    print(f'客户端发送了数据:{msg}')

    # 4.收消息
    data = phone.recv(1024)
    print('服务端返回的数据:', data.decode('UTF-8'))

# 5.关闭连接
phone.close()
 

2.优化版

server.py

import socket

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM --> TCP协议(流式协议)

# 2.插手机卡
server_ip = '127.0.0.1'
server_port = 8080
phone.bind((server_ip, server_port))  # 127.0.0.1:本地回环地址,只能本机访问

# 3.开机
phone.listen(5)  # backlog:半连接池
print(f'服务端开始启动,IP:{server_ip},端口:{server_port}')

# 4.等电话连接
conn, client_addr = phone.accept()  # conn代表双向连接,可以收数据,也可以发数据;client_addr:客户端地址
print('服务端启动成功,等待客户端的接入')

# 5.收消息
recv_data = conn.recv(1024)  # 1024:最大接收的字节个数,超过1024个的字节,下次发送
data = recv_data.decode('UTF-8')
print('收到的客户端的数据:', data)

# 6.发消息
conn.send(recv_data.upper())  # 此处的data是bytes类型,可以直接转换成大写
print(f'服务端将接收的数据:{data} 转成了大写,发给了客户端')

# 7.关闭
conn.close()  # 挂掉电话
phone.close()  # 关机

# 输出:
# 服务端开始启动,IP:127.0.0.1,端口:8080
# 服务端启动成功,等待客户端的接入
# 收到的客户端的数据: Hello World
# 服务端将接收的数据:Hello World 转成了大写,发给了客户端

client.py

import socket

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM --> TCP协议(流式协议)

# 2.拨通电话
phone.connect(('127.0.0.1', 8080))

# 3.发消息
msg = 'Hello World'
phone.send(msg.encode('UTF-8'))
print(f'客户端发送了数据:{msg}')

# 4.收消息
data = phone.recv(1024)
print('服务端返回的数据:', data.decode('UTF-8'))

# 5.关闭连接
phone.close()

# 输出:
# 客户端发送了数据:Hello World
# 服务端返回的数据: HELLO WORLD
 

3.添加循环、检测异常

server.py

import socket

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM --> TCP协议(流式协议)

# 2.插手机卡
server_ip = '127.0.0.1'
server_port = 8080
# phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 重用端口资源,但是不建议用
phone.bind((server_ip, server_port))  # 127.0.0.1:本地回环地址,只能本机访问

# 3.开机
phone.listen(5)  # backlog:半连接池
print(f'服务端开始启动,IP:{server_ip},端口:{server_port}')

# 4.等电话连接
while True:
    conn, client_addr = phone.accept()  # conn代表双向连接,可以收数据,也可以发数据;client_addr:客户端地址
    print(f'客户端地址:{client_addr}')

    # 5.收消息
    while True:
        try:
            recv_data = conn.recv(1024)  # 1024:最大接收的字节个数,超过1024个的字节,下次发送
            data = recv_data.decode('UTF-8')
            if len(data) == 0:  # 针对Linux系统
                break
            print('收到的客户端的数据:', data)
            conn.send(recv_data.upper())  # 此处的data是bytes类型,可以直接转换成大写
            print(f'服务端将接收的数据[{data}] 转成了大写,发给了客户端')
        except Exception:   # 针对Windows系统
            print(f'客户端{client_addr}异常断开')
            break

    # 6.关闭
    conn.close()  # 挂掉电话
phone.close()  # 关机

client.py

import socket

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM --> TCP协议(流式协议)

# 2.拨通电话
phone.connect(('127.0.0.1', 8080))

# 3.发消息
while True:
    msg = input('请输入要发送的消息:').strip()
    phone.send(msg.encode('UTF-8'))
    print(f'客户端发送了数据:{msg},等待服务器的响应...')

    # 4.收消息
    data = phone.recv(1024)
    print('服务端返回的数据:', data.decode('UTF-8'))

# 5.关闭连接
phone.close()
 

4.远程控制

服务端开启后,客户端可以直接输入命令控制服务端,服务端返回输出的值

remote_server.py

import socket
import subprocess

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM --> TCP协议(流式协议)

# 2.插手机卡
server_ip = '127.0.0.1'
server_port = 8080
# phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 重用端口资源,但是不建议用
phone.bind((server_ip, server_port))  # 127.0.0.1:本地回环地址,只能本机访问

# 3.开机
phone.listen(5)  # backlog:半连接池
print(f'服务端开始启动,IP:{server_ip},端口:{server_port}')

# 4.等电话连接
while True:
    conn, client_addr = phone.accept()  # conn代表双向连接,可以收数据,也可以发数据;client_addr:客户端地址
    print(f'客户端地址:{client_addr}')

    # 5.收消息
    while True:
        try:
            recv_cmd = conn.recv(1024)  # 1024:最大接收的字节个数,超过1024个的字节,下次发送
            if len(recv_cmd) == 0:  # 针对Linux系统
                break
            cmd = recv_cmd.decode('UTF-8')
            print('收到的客户端的数据:', cmd)
            obj = subprocess.Popen(cmd,
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            res = obj.stdout.read() + obj.stderr.read()  # 能不能不拼接?
            conn.send(res)
        except Exception:  # 针对Windows系统
            print(f'客户端{client_addr}异常断开')
            break

    # 6.关闭
    conn.close()  # 挂掉电话
phone.close()  # 关机

remote_client.py

import socket

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM --> TCP协议(流式协议)

# 2.拨通电话
phone.connect(('127.0.0.1', 8080))

# 3.发消息
while True:
    cmd = input('请输入要发送的消息:').strip()
    phone.send(cmd.encode('UTF-8'))
    print(f'客户端输入命令:{cmd},等待服务器的响应...')

    # 4.收消息
    recv_data = phone.recv(1024)
    data = recv_data.decode('GBK')
    print('服务端返回的数据:', data)

# 5.关闭连接
phone.close()
每天逼着自己写点东西,终有一天会为自己的变化感动的。这是一个潜移默化的过程,每天坚持编编故事,自己不知不觉就会拥有故事人物的特质的。 Explicit is better than implicit.(清楚优于含糊)
原文地址:https://www.cnblogs.com/kylin5201314/p/13508174.html