031.socket编程之基本流程及基于TCP的套接字

一、套接字的概念及分类

(一)套接字是什么

​ 套接字是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行,Linux所提供的功能(如打印服务,ftp等)通常都是通过套接字来进行通信的,套接字的创建和使用与管道是有区别的,因为套接字明确地将客户和服务器区分出来,套接字可以实现将多个客户连接到一个服务器。

​ 套接字,也称为BSD套接字,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。

​ 简单的举例说明:Socket=Ip address+ TCP/UDP + port。

(二)套接字的分类

1.基于文件类型

​ 套接字家族的名字:AF_UNIX。

2.基于网络类型

​ 套接字家族的名字:AF_INET。

二、套接字工作流程

(一)面向连接的套接字Socket通信工作流程

1.服务器先用socket函数来建立一个套接字,用这个套接字完成通信的监听;

2.用bind函数来绑定一个端口号和一个IP地址。因为本地计算机可能有多个网址和IP,每一个IP和端口有多个端口,需要指定一个IPhe端口进行监听。

3.服务器调用listen函数,使服务器的这个端口和IP处于监听状态,等待客户机的链接。

4.客户机用socket函数建立一个套接字,设定远程IP和端口。

5.客户机调用connect函数连接远程计算机指定的端口。

6.服务器用accept函数来接受远程计算机的连接,建立起与客户机之间的通信。

7.建立连接以后,客户机用write函数想socket中写入数据。也可以用read函数读取服务器发送来的数据。

8.服务器用read函数读取客户机发送来的数据,也可以用write函数来发送数据。

9.完成通信以后,用close函数关闭socket连接。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c99SnS2t-1597326413031)(D:\Onedrive\文档\01学习资料\全栈15博客笔记\python学习博客及作业\day031.socket编程之基本流程及基于TCP的套接字\TCP套接字流程.jpg)]

(二)面向无连接的套接字Socket通信工作流程

​ 无连接的通信不需要建立起客户机与服务器之间的连接,因此在程序中没有简历连接的过程。进行通信之前,需要简历网络套接字。服务器需要绑定一个端口,在这个端口上监听收到的信息。客户机需要设置远程IP和端口,需要传递的信息需要发送到这个IP和端口上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q4KT2jSe-1597326413033)(D:\Onedrive\文档\01学习资料\全栈15博客笔记\python学习博客及作业\day031.socket编程之基本流程及基于TCP的套接字\UDP套接字流程.jpg)]

(三)socket() 模块函数用法

1.socket() 模块函数基本用法

import socket
socket.socket(socket_family, socket_type, protocal=0)
# socket_family 可以是 AF_UNIX 或 AF_INET;
# socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM;
# protocal 一般不填,默认值为0

# 获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 由于 socket 模块中有太多属性我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。
# 例如:tcpSock = socket(AF_INET, SOCK_STREAM)

2.服务端套接字函数

s.bind()        绑定(主机、端口号)到套接字
s.listen()      开始TCP监听
s.accept()      被动接受TCP客户的连接·(阻塞式)等待连接的到来

3.客户端套接字函数

s.connect()         主动初始化TCP服务器连接
s.connect_ex()      connect()函数的扩展版本,出错时返回错误代码,而不是抛出异常

4.公共用途的套接字函数

s.recv()             接受TCP数据
s.send()             发送TCP数据(send在待发送数据量大于已己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall()          发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom()         接收UDP数据
s.sendto()           发送UDP数据
s.getpeername()      连接到当前套接字的远端地址
s.getsockname()      当前套接字的地址
s.getsockopt()       返回指定套接字的参数
s.setsockopt()       设置指定套接字的参数
s.close()            关闭套接字

5.面向锁的套接字方法

s.setblocking()       设置套接字阻塞与非阻塞模式
s.settimeout()        设置阻塞套接字操作的超时时间
s.gettimeout()        得到阻塞套接字的操作超时时间

6.面向文件的套接字函数

s.fileno()            套接字的文件描述符
s.makefile()          创建一个与该套接字相关的文件

三、基于TCP的套接字

(一)基本模板

1.tcp服务端及特性

ss = socket() #创建服务器套接字
ss.bind()      #把地址绑定到套接字
ss.listen()      #监听链接
inf_loop:      #服务器无限循环
    cs = ss.accept() #接受客户端链接
    comm_loop:         #通讯循环
        cs.recv()/cs.send() #对话(接收与发送)
    cs.close()    #关闭客户端套接字
ss.close()        #关闭服务器套接字(可选)

2.tcp客户端

cs = socket()    # 创建客户套接字
cs.connect()    # 尝试连接服务器
comm_loop:        # 通讯循环
    cs.send()/cs.recv()    # 对话(发送/接收)
cs.close()            # 关闭客户套接字

(二)一步步实现TCP套接字

1.基于tcp协议实现简单套接字通信

##——————————————————————————————————————server端基础版本
import socket

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

# 2.插手机卡
phone.bind(('127.0.0.1', 8080))  # 本地回环,使用一个元组传参

# 3.开机
phone.listen(5)
print('starting %s:%s' % ('127.0.0.1', 8080))

# 4.等电话连接
conn, client_addr = phone.accept()

# 5.收/发消息
data = conn.recv(1024)  # 最大接收的字节数
print('收到的客户端数据:', data.decode('utf-8'))
conn.send(data.upper())

# 6.关闭
conn.close()
phone.close()

##——————————————————————————————————————client端基础版本
import socket

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

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

# 3.发/收消息
phone.send('hello'.encode('utf-8'))
data = phone.recv(1024)
print('服务的返回数据:', data.decode('utf-8'))

# 4.关闭
phone.close()

2.加上链接循环与通信循环

(1)为什么要加上加上链接循环与通信循环?

​ 因为服务端应满足的特性:

​ ① 一直对外提供服务;

​ ② 并发地提供服务;

(2)加上链接循环与通信循环
##——————————————————————————————————————server端基础版本加上链接循环与通信循环
import socket

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

# 2.插手机卡


phone.bind(('127.0.0.1', 8080))  # 本地回环,使用一个元组传参

# 3.开机
phone.listen(5)  # 半连接池数量,队列
print('starting %s:%s' % ('127.0.0.1', 8080))

# 4.等电话连接===>>>链接循环(D)  
# ##—————————————————此处的链接循环以后不可如此使用(需拆分开),这样只能一次处理一条服务,不能高并发
while True:
    conn, client_addr = phone.accept()
    print(client_addr)
    # 5.收/发消息===>>>通信循环(A)
    while True:
        try:
            data = conn.recv(1024)  # 最大接收的字节数
            # linux系统的一直收空信息的错误解决方法,判断是否为空,然后打破循环
            if len(data) == 0:
                break
            # (B) conn是一个双向连接,若客户端非正常断开,则服务端此处会报错,因为通信无法完成,需加入异常管理
            print('收到的客户端数据:', data.decode('utf-8'))
            conn.send(data.upper())
        except Exception:  # windows系统的解决异常方法
            break
            # (C) 异常捕捉完成后,服务端不能结束,需要重新进行服务,则需要重新建立连接
    # 6.关闭
    conn.close()  # 用于回收收无用地异常关闭地链接,然后进入连接循环,重新监听客户端地请求,暂无并发效果
phone.close()
# 用于软件正常关闭使用,暂时无用,正常写软件可使用关闭按钮实现

##——————————————————————————————————————client端基础版本加上链接循环与通信循环
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'))
    data = phone.recv(1024)
    print('服务的返回数据:', data.decode('utf-8'))

# 4.关闭
phone.close()
(3)会遇到的问题以及解决方案
① 问题:报错地址仍在使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5VwE3nbx-1597326413035)(D:\Onedrive\文档\01学习资料\全栈15博客笔记\python学习博客及作业\day031.socket编程之基本流程及基于TCP的套接字\003--问题.png)]

原因:由于服务端仍然存在第四次挥手的time_wait状态在占用地址(相关知识:1.tcp三次握手,四次挥手;2.syn洪水攻击;3.服务器高并发情况下会有大量的time_wait状态的优化方法)

② 解决方案
(A)Windows下,加入一条socket配置,重用ip和端口

(此方法不推荐,会导致套接字不知道去哪,换端口号相对更好一些)

# 在服务端的绑定信息前面添加一条配置信息
# 2.插手机卡

phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 就是它,在bind前加

phone.bind(('127.0.0.1', 8080))  # 本地回环,使用一个元组传参

(B)Linux下,增加内核相关的配置,解决根本问题
发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
vi /etc/sysctl.conf

编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
 
然后执行 /sbin/sysctl -p 让参数生效。
 
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间

3.半连接池

​ 未得到服务端连接的请求,都储存在半连接池中,当半连接池队列达到最大之后,后续的请求将无法得到响应,无法连接到服务器,除非半连接池队列数量减少,才能有新的请求进入半连接池。

​ 并发量大的情况下,应该扩大半连接池,写入配置文件,使可调整,但是半连接池容量不能无限大,不可超过物理内存。

4.远程执行命令

(1)改写成可以远程执行命令的版本

​ 使用subprocess模块,增加远程连接的功能

##——————————————————————————————————————server端基础版本加上链接循环与通信循环,再添加远程执行命令
import socket
import subprocess

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

# 2.插手机卡
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 就是它,在bind前加

phone.bind(('127.0.0.1', 8080))  # 本地回环,使用一个元组传参

# 3.开机
phone.listen(5)  # 半连接池数量,队列
print('starting %s:%s' % ('127.0.0.1', 8080))

# 4.等电话连接===>>>链接循环
while True:
    conn, client_addr = phone.accept()
    print(client_addr)
    # 5.收/发消息===>>>通信循环
    while True:
        try:
            cmd = conn.recv(1024)  # 最大接收的字节数
            # linux系统的一直收空信息的错误解决方法,判断是否为空,然后打破循环
            if len(cmd) == 0:
                break

            obj = subprocess.Popen(cmd.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            res = obj.stdout.read()+obj.stderr.read()  # 为何拼接?
            print(res)
            conn.send(res)
        except Exception:  # windows系统的解决异常方法
            break
            # (C) 异常捕捉完成后,服务端不能结束,需要重新进行服务,则需要重新建立连接
    # 6.关闭
    conn.close()  # 用于回收收无用地异常关闭地链接,然后进入连接循环,重新监听客户端地请求,暂无并发效果
phone.close()
# 用于软件正常关闭使用,暂时无用,正常写软件可使用关闭按钮实现

##——————————————————————————————————————client端基础版本加上链接循环与通信循环,再添加远程执行命令
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('[root@localhost]# ').strip()    # 用户输入消息进行通信
    phone.send(cmd.encode('utf-8'))
    data = phone.recv(1024)
    # windows系统下,应该使用gbk解码
    print(data.decode('gbk'))


# 4.关闭
phone.close()
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值