[TOC]python
socket: Python的底层网络接口,通常状况程序员不须要接触到这个模块。有更多的高级模块,好比
requests
能够直接使用。本文章试图从Python的socket模块和linux socket api的角度来对Python实现网络通信方式进行分析,提升对TCP,UDP通信方式的理解。最后用Python实现一个hello/hi的简单的网络聊天程序。linux
在Python中若是想要使用一个本身的网络应用层协议,或者说想使用纯原生TCP,UDP来实现通信,就须要使用Python的socket
模块。git
import socket
socket
模块提供了访问BSD套接字的接口。在全部现代Unix系统、Windows、macOS和其余一些平台上可用。程序员
# 使用socket()方法返回一个socket对象 s = socket.socket([family[, type, proto, fileno]])
重要参数:github
参数 | 描述 |
---|---|
family | |
socket.AF_INET(默认) | IPv4 |
socket.AF_INET6 | IPv6 |
socket.AF_UNIX | Unix系统进程间通讯 |
type | |
socket.SOCK_STREAM | 流式套接字,TCP |
socket.SOCK_DGRAM | 数据报套接字,UDP |
socket.SOCK_RAW | 原始套接字 |
socket方法与Linux Socket的
socket
函数对应编程
// socket(协议域,套接字类型,协议) int socket(int domain, int type, int protocol);
经过s = socket.socket()
方法,获得了一个socket对象。api
Python中的socket对象的成员方法,是对套接字系统调用的高级实现,每每比C语言更高级。服务器
一般若是是服务器,须要绑定一个总所周知的地址用于提供服务,因此须要绑定一个(IP:PORT),客户端能够经过链接这个地址来得到服务。而客户端则直接经过链接,由系统随机分配一个端口号。网络
python中bind()方法传入一个地址和端口的元组dom
s.bind((host: str, port: int))
linux socket将套接字做为对象,传入一个套接字和地址结构体
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
开始监听,backlog指定在拒绝链接以前,操做系统能够挂起的最大链接数,默认为1
s.listen(backlog: int)
linux socket一样须要额外传入套接字参数
int listen(int sockfd, int backlog);
connect
方法是客户端用发起某个链接的,接受一个目标主机名和端口号的元组参数
# address -> (hostname, port) s.connect(address) # connect_ex是connect的扩展方法,不一样在于返回错误代码,而不是抛出错误 s.connect_ex(address)
linux socket中,参数分别为客户端套接字socket描述符,服务器socket地址,socket地址长度
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
服务器依次调用socket()
, bind()
, listen()
后就会监听指定地址,客户端经过connect()
向服务器发起链接请求。服务器监听到请求后会调用accept()
函数接受请求。这样端与端的链接就创建好了
python中,accept()
方法阻塞进程,等待链接,返回一个新的套接字对象和链接请求者地址信息。
# accept() -> (socket object, address info) s.accept()
linux socket中,第一个参数是服务器套接字描述符,第二个为一个地址指针,用于返回客户端协议地址,第三个参数是协议地址长度。若是链接成功,函数返回值为内核自动生成的一个全新描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
服务器的监听套接字通常只建立一个,而accept函数会返回一个链接套接字,服务器与客户端之间的通讯是在链接套接字上进行。每来一个服务请求新建一个链接套接字与请求者通讯,而监听套接字只有一个,完成服务后对应的链接套接字就会被关闭。(能够理解成,监听套接字是专门的接线员,只负责将电话转接给别的部门)
send(data[, flags]) ->count
发送数据到socket,发送前要将数据转换为utf-8的二进制格式。返回发送数据长度,由于网络可能繁忙,致使数据没有所有发送完毕,因此要再次对剩下的数据进行发送。
python还有一个sendall()
sendall(data[, flags])
做用是不停调用send()
函数,直到全部数据发送完毕
linux socket中的send()
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
send()
函数先检查协议是否正在发送缓冲区数据,等待协议发送完毕或则缓冲区已没有数据,那么send
比较sockfd缓冲区剩余空间大小和发送数据的len。python中,从已链接套接字读取数据的函数为recv()
s.recv(bufsize: int)
从套接字接受数据,若是没有数据到达套接字,将会阻塞直到来数据或则远程链接关闭。
若是远程链接关闭且数据已所有读取,则抛出一个错误。
linux socket也有读取数据函数recv()
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
recv()
等待s发送缓冲区发送完毕recv()
在无链接的状况下,端到端须要使用另外的数据发送和接受方式
python中发送UDP数据,将数据data发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.sendto(data,address)
linux socket: 因为本地socket并无与远端机器创建链接,因此在发送数据时应指明目的地址
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
该函数比send()函数多了两个参数,dest_addr表示目地机的IP地址和端口号信息,而addrlen是地址长度。
s.recvfrom() -> (data, address)
接收UDP数据,与recv()相似,但返回值是(data,address)。其中data是包含接收的数据,address是发送数据的套接字地址。
linux socket: recvfrom()的状况与sendto()相似,须要指针来存放发送数据的套接字地址
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
python和linux socket都须要对套接字关闭
python:
s.close()
linux socket:
int close(int socketfd)
#! /usr/bin/env python3 import socket from threading import Thread import traceback HOST = "127.0.0.1" PORT = 65432 def recv_from_client(conn): try: content = conn.recv(1024) return content except Exception: return None class ServiceThread(Thread): def __init__(self, conn, addr): super().__init__() self.conn = conn self.addr = addr def run(self): try: while True: content = recv_from_client(self.conn) if not content: break print(f"{self.addr}: {content.decode('utf-8')}") self.conn.sendall(content) self.conn.close() print(f"{self.addr[0]}:{self.addr[1]} leave.") except Exception: traceback.print_exc() if __name__ == "__main__": s = None try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen() print("Repeater server started successfully.") while True: conn, addr = s.accept() print(f"Connected from {addr}") service_thread = ServiceThread(conn, addr) service_thread.daemon = True service_thread.start() except Exception: traceback.print_exc() s.close()
#! /usr/bin/env python3 import socket from threading import Thread HOST = "127.0.0.1" PORT = 65432 class ReadFromConnThread(Thread): def __init__(self, conn): super().__init__() self.conn = conn def run(self): try: while True: content = self.conn.recv(1024) print(f"\n({HOST}:{PORT}): {content.decode('utf-8')}\nYOUR:", end="") except Exception: pass if __name__ == "__main__": s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) read_thread = ReadFromConnThread(s) read_thread.daemon = True read_thread.start() while True: content = input("YOUR:") if content == "quit": break s.sendall(content.encode("utf-8")) s.close()
做者:SA19225176,万有引力丶
参考资料来源:USTC Socket网络编程