• 主页
  • 相册
  • 随笔
  • 目录
  • 存档
Total 244
Search AboutMe

  • 主页
  • 相册
  • 随笔
  • 目录
  • 存档

python socket编程笔记

2020-09-23

1. UDP

1.1. 创建套接字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
udp_socket=socket.socket(AddressFamily,Type)
# 1. 创建套接字
# AddressFamily:AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6
# Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)


udp_socket.sendto("我是发送的消息".encode("utf-8"),("10.200.202.119",8888))
# 2. 指定服务器ip和端口,并发送消息


receive_data, from_addr = udp_socket.recvfrom(buffersize)
# 3. 接受消息
# buffersize:指定一次接受的字节大小
# return:返回为一个元祖,第一个参数表示接受的数据,第二个参数表示消息来源地址

udp_socket.close()
# 4. 关闭套接字

1.2. UDP多线程聊天室

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import socket
import threading

is_exit_send_msg = False


def send_msg(udp_socket, dest_addr):
global is_exit_send_msg
while True:
send_data = input("请输入要发送的内容:\n")
if send_data == "exit":
is_exit_send_msg = True
print("退出发送消息")
break
udp_socket.sendto(send_data.encode("GBK"), dest_addr)


def receive_msg(udp_socket):
global is_exit_send_msg
while True:
receive_data, from_addr = udp_socket.recvfrom(1024)
if receive_data.decode("GBK") == "exit":
print("退出接收消息")
break
print(str(from_addr)+" 发来消息:\n"+receive_data.decode("GBK"))

if not is_exit_send_msg:
print("请输入要发送的内容:")


def main():
# 1.创建udp socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2.输入要连接的ip和端口
dest_ip = input("输入要连接的ip:\n")
dest_port = int(input("输入端口号:\n"))
dest_addr = (dest_ip, dest_port)
# 3.创建发送消息线程
thread_send = threading.Thread(target=send_msg, args=(udp_socket, dest_addr))
# 4.创建接收消息线程
thread_receive = threading.Thread(target=receive_msg, args=(udp_socket,))

# 5.开启发送消息线程
thread_send.start()
# 6.开启接收消息线程
thread_receive.start()

if __name__ == "__main__":
main()

2. TCP

2.1. TCP客户端

1
2
3
4
5
6
7
8
9
10
11
12
#创建套接字
tcp_socket = socket.socket(AF_INET, socket.SOCK_STREAM)

#连接服务器,需指定服务器ip和端口
tcp_socket.connet((ip,port))

#发送数据/接受数据
tcp_socket.send("我是发送的消息".encode("utf-8"))
receive_data = tcp_socket.recv(1024)

#关闭套接字
tcp_socket.close()

2.2. TCP服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建套接字
tcp_server_socket = socket.socket(AF_INET,socket.SOCK_STREAM)
# 绑定服务端ip和端口
tcp_server_socket.bind((ip,port))
# 开启监听,监听客户端连接
tcp_server_socket.listen(max)
# max:表示同时客户端最大的连接数

# accept:接受一个客户端连接并返回一个处理这个连接的socket
new_client_socket,client_addr = tcp_server_socket.accept()
# new_client_socket:处理这个连接的socket
# client_addr:连接客户端的地址(ip和端口号)

# 接受/发送消息
receive_data=new_client_socket.recv(1024)
new_client_socket.send("我是发送的消息".encode("utf-8"))

3. select poll epoll

network IO

3.1. 前言

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。

  • 针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间
  • 而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间

3.2. 进程切换

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。

可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的

原语大概就是原子语句吧

3.3. IO模式

  • 阻塞 I/O(blocking IO)
    • 准备数据阶段(数据被拷贝到操作系统内核的缓冲区中是需要一个过程的)
    • 而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来
  • 非阻塞 I/O(nonblocking IO)
    • 轮询
    • 当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果
    • 相比阻塞I/O少一次阻塞
  • I/O 多路复用( IO multiplexing)
    • select,poll,epoll
    • I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作
    • 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接
  • 信号驱动 I/O( signal driven IO)
  • 异步 I/O(asynchronous IO)
    • 用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了

3.3.1. 文件描述符

文件描述符是Unix系统标识文件的int,Unix的哲学一切皆文件,所以各自资源(包括常规意义的文件、目录、管道、POSIX IPC、socket)都可以看成文件。文件描述符是内核提供给用户来安全地操作文件的标识,不像指针,拥有了指针后你能瞎JB改。拥有了描述符后,你只能传入描述符给特定的接口,实际操作由内核读取用户输入的参数后来安全地执行

  • select
    • 查询 fd_set 中,是否有就绪的 fd,可以设定一个超时时间,当有 fd (File descripter) 就绪或超时返回
  • poll
    • poll 中将 select 中的 fd_set 替换成了一个 pollfd 数组
    • 解决 fd 数量过小的问题
  • epoll
    • 事件(event)驱动
    • 为每个 fd,注册一个监听事件
    • fd 变更为就绪时,将 fd 添加到就绪链表

3.3.2. 内核缓冲与用户缓冲

内核态可以访问系统资源

  • 处理器cpu
  • 输入输出IO
  • …
    而上面所说的这些系统资源,在用户进程中是无法被直接访问的,只能通过操作系统来访问,所以也把操作系统提供的这些功能成为:“系统调用”

用户缓冲

一些程序在读取文件时,会先申请一块内存数组,称为buffer,然后每次调用read,读取设定字节长度的数据,写入buffer。(用较小的次数填满buffer)。之后的程序都是从buffer中获取数据,当buffer使用完后,在进行下一次调用,填充buffer。

所以说:用户缓冲区的目的是为了减少系统调用次数,从而降低操作系统在用户态与核心态切换所耗费的时间

内核缓冲

内核缓冲区,是为了在OS级别,提高磁盘IO效率,优化磁盘写操作

4. 参考

  • 01-网络编程 - python手册
  • Linux IO模式及 select、poll、epoll详解_人云思云 - SegmentFault 思否
  • 用户进程缓冲区和内核缓冲区
  • Program Language
  • Python
  • Advanced
渗透测试与主机加固
python小知识-2
  1. 1. 1. UDP
    1. 1.1. 1.1. 创建套接字
    2. 1.2. 1.2. UDP多线程聊天室
  2. 2. 2. TCP
    1. 2.1. 2.1. TCP客户端
    2. 2.2. 2.2. TCP服务端
  3. 3. 3. select poll epoll
    1. 3.1. 3.1. 前言
    2. 3.2. 3.2. 进程切换
    3. 3.3. 3.3. IO模式
      1. 3.3.1. 3.3.1. 文件描述符
      2. 3.3.2. 3.3.2. 内核缓冲与用户缓冲
  4. 4. 4. 参考
© 2024 何决云 载入天数...