Socket介绍
Socket套接字
Python中提供socket.py标准库,非常底层的接口库
Socket是一种通用的网络编程接口,和网络层次没有一一对应的关系
协议族
AF表示Address Family,用于socket()第一个参数。
名称 | 含义 |
---|---|
AF_INET | IPV4 |
AF_INET6 | IPV6 |
AF_UNIX | Unix Domain Socket ,windows没有 |
Socket类型
名称 | 含义 |
---|---|
SOCK_STREAM | 面向连接的流套接字。默认值,TCP协议 |
SOCK_DGRAM | 无连接的数据报文套接字。UDP协议 |
TCP编程
Socket编程,需要两端,一般来说需要一个服务端、一个客户端
,服务端成为Server,客户端成为Clent。这种编程被成为CS编程。
TCP服务端编程
服务器端编程步骤
- 创建Socket对象
- 绑定ip地址Address和端口Port。bind()方法,IPV4地址为一个二元组(‘ip地址字符串’,Port)
- 开始监听,将在指定的IP的端口上监听,listen()方法
- 获取用于传送数据的Socket.accept()–>(socket object , address info),accept方法阻塞等待客户端建立连接,返回一个新的Socket对象和客户端地址的二元组,地址是远程客户端的地址,IPV4中它是一个二元组(clientaddr,port)
- 接受数据,recv(bufsize[,flags])使用缓冲区接受数据
- 发送数据,send(bytes)发送数据
import socket
sock=socket.socket()
ip='127.0.0.1'
port=9999
#绑定IPV4的地址是一个二元组
sock.bind((ip,port))
#先监听后获取
sock.listen()
sock.accept()
newsock,clientinfo=sock.accept()
print(sock)
print(newsock)
print(clientinfo)
import socket
import time
server=socket.socket()
ip='127.0.0.1'
port=9999
server.bind((ip,port))
server.listen()
print(server)
newsock,clientinfo=server.accept()
data=newsock.recv(1024)
print(data)
newsock.send('ok good'.encode())
查看监听端口
#windows命令
netstat -anp tcp | findstr 9999
# linux命令
# netstat -tanl |grep 9999
# ss -tanl | grep 9999
import logging
import socket
import threading
import datetime
FORMAT="%(asctime)s %(threadName)s %(thread)s %(message)s "
logging.basicConfig(format=FORMAT,level=logging.INFO)
class Chat:
def __init__(self,ip='127.0.0.1',port=9999):
self.sock=socket.socket()
self.addr=(ip,port)
self.client={}
def start(self):
self.sock.bind(self.addr)
self.sock.listen()
threading.Thread(target=self.accept,name="accept").start()
def accept(self):
while True:
newsock,clientinfo=self.sock.accept()
self.client[clientinfo]=newsock
threading.Thread(target=self.recv,args=(newsock,clientinfo)).start()
def recv(self,newsock:socket.socket,clientinfo):
while True:
data=newsock.recv(1024)
print(data)
mid="{}{}{}{}".format(data.decode(), newsock, *clientinfo)
logging.info(mid.encode())
for i in self.client.values():
i.send(mid.encode())
def stop(self):
for i in self.client.values():
i.close()
self.sock.close()
chat=Chat()
chat.start()
b'11111111\r\n'
2019-06-13 11:01:42,726 Thread-1 12436 b"11111111\r\n<socket.socket fd=592, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 52902)>127.0.0.152902"
注意:
由于GIL和内置数据结构的读写原子性,单独操作字典的某一项item是安全的,但是便利过程是线程不安全,遍历中有可能会被打断,其他线程如果对字典元素进行增加、弹出,都会影响字典的size,就会抛出异常。
import logging
import socket
import threading
import datetime
FORMAT="%(asctime)s %(threadName)s %(thread)s %(message)s "
logging.basicConfig(format=FORMAT,level=logging.INFO)
class Chat:
def __init__(self,ip='127.0.0.1',port=9999):
self.sock=socket.socket()
self.addr=(ip,port)
self.client={}
self.event=threading.Event()
self.lock=threading.Lock()
def start(self):
self.sock.bind(self.addr)
self.sock.listen()
threading.Thread(target=self.accept,name="accept").start()
def accept(self):
while not self.event.is_set():
newsock,clientinfo=self.sock.accept()
with self.lock:
self.client[clientinfo]=newsock
threading.Thread(target=self.recv,args=(newsock,clientinfo)).start()
def recv(self,newsock:socket.socket,clientinfo):
while not self.event.is_set():
data=newsock.recv(1024)
print(data)
if data==b'quit' or data==b" ":
with self.lock:
self.client.pop(clientinfo)
newsock.close()
break
mid="{}{}{}{}".format(data.decode(), newsock, *clientinfo)
logging.info(mid.encode())
with self.lock:
for i in self.client.values():
i.send(mid.encode())
def stop(self):
self.event.set()
with self.lock:
for i in self.client.values():
i.close()
self.sock.close()
chat=Chat()
chat.start()
while True:
cmd=input(">>>>>").strip()
if cmd=="quit":
chat.stop()
break
print(threading.enumerate())
socket常用方法
名称 | 含义 |
---|---|
socket.recv(bufsize[,flags]) | 获取数据,默认是阻塞的方式 |
socket.recvfrom(bufsize[,flags]) | 获取数据,返回二元组 |
socket,recv_into(buff[,nbytes[,flags]]) | 获取到nbytes的数据后,存储到buffer中。如果nbytes没有指定或0,将buffer大小的数据存入buffer中,返回接受的字节数 |
socket.recvfrom_into(buffer[,nbytes[,flags]]) | 获取数据,返回一个二元组(bytes,address)中 |
socker.send(bytes[,flags]) | TCP发送数据 |
socket,sendall(bytes[flags]) | TCP发送全部数据,成功返回None |
socket.sendto(string[flag],address) | UDP发送数据 |
socket.sendfile(file,offset=0,coun=None) | 发送一个文件直到OFF,使用高性能的os.sendfile机制,返回发送的字节数,如果win下不支持sendfile,或者不是普通文件,使用send()发送文件,offset告诉起始位置 |
名称 | 含义 |
– | – |
socket.getpeername() | 返回连接套接字的远程地址,返回值通常是元组(ipaddr,port) |
socket.getsockname() | 返回套接字自己的地址,通常是一个元组(ipaddr,port) |
MakeFile
sock.makefile( mode="r", buffering=None, *,
encoding=None, errors=None, newline=None)
创建一个与该套接字相关联的文件对象,将recv方法看做读方法,将send方法看做写方法
import socket
server=socket.socket()
server.bind(('127.0.0.1',9999))
server.listen()
print('~~~~~~~~~~`')
s,_=server.accept()
f=s.makefile(mode='rw')
line=f.read(10)
print(line)
f.write(line)
f.flush()
f.close()
print(f.closed,s._closed)
s.close()
print(f.closed,s._closed)
server.close()
TCP客户端编程
- 创建Socket对象
- 连接到远端服务端的ip和port,connect()方法
- 传输数据:使用send、recv方法发送、接受数据
- 关闭连接释放资源
import socket
import threading
class ChatClient:
def __init__(self,ip='172.22.141.176',port=9999):
self.sock=socket.socket()
self.ipaddr=ip,port
def start(self):
self.sock.connect(self.ipaddr)
threading.Thread(target=self.recv).start()
threading.Thread(target=slice)
def send(self):
msg=input(">>>").encode()
self.sock.send(msg)
def recv(self):
while True:
self.send()
data=self.sock.recv(1024)
print(data)
chat=ChatClient()
chat.start()