1、Socket介绍:
socket 套接字
Python中提供socket.py标准库,非常底层的接口库。
Socket 是一种通用的网络编程接口,和网络底层没有一一对应的关系
协议族:
AF:address family, 用于socket()第一个参数
Socket 类型
2、TCP编程:
Socket编程,需要两端,一般来说,需要一个服务器端,一个客户端,服务器端为server,客户端为client
2.1、TCP服务器端编程:
服务器端编程步骤:
- 创建Socket对象
- 绑定IP 地址 address, 和端口 port,bind() 方法,IPv4地址为一个二元组(‘IP地址字符串’,port)
- 开始监听,将在指定的ip 端口上监听,listen()方法
- 获取用于传递数据的socket 对象
socket.accept() ---> (socket.object, address info)
accept 方法 阻塞等待客户端建立连接,返回一个新的额socket对象和客户端地址的二元组
地址是远程客户端的地址,IPV4 中他是一个二元组(clientaddr,port)
- 接受数据:recv( bufsize [, flags])使用了缓冲区接受数据
- 发送数据:send(bytes)发送数据
只有一个client 请求的 样例:
1 import socket
2
3 # 1、创建套接字
4 socket = socket.socket()
5
6 # 2、绑定ip 和 port
7 ip = '127.0.0.1'
8 port = 9999
9 laddr = (ip, port)
10
11 socket.bind(laddr)
12
13 # 3、监听 套接字
14 socket.listen()
15
16 # 4\、等待接受 客户端数据,并生产新的socket,用于数据交互
17 data = socket.accept()
18 newsocket, raddr = data
19
20 # 5、接受数据
21
22 msg = newsocket.recv() # 收到的是字节类型
23
24 # 6、发送数据
25 newsocket.send(msg) # 发送也得是字节类型
26
27 # 7、当前socket 通信结束,可以关闭
28 newsocket.close()
29
30 # 8、关闭资源 ,这里关闭的是 创建连接时的 socket
31 socket.close()
服务器端基本样例
newsocket:
<socket.socket fd=212, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 50287)>
raddr:远程套接字
('127.0.0.1', 50287)
recv(1024):缓冲区大小一般是1024 的整数倍
socket.accept():阻塞直到 和客户端 成功建立连接,返回一个socket对象和客户端地址。
newsocket.recv(1024):连接成功后,没有收到数据,会阻塞在这里。
注:
TCP的服务端编程:
端口是给进程的,线程共享资源。
通过监听来获得端口,对端口进行暴露 ,同时监听
一个socket 是为了连接,创建新的socket进行资源通信
win cmd:
netstat -anp tcp | findstr 9999 (win的管道)
-b:显示进程
Linux:
一般使用ss
测试:
1、服务器端,主线程是用来做其他工作,所以,不能让accept占据主线程,处于阻塞状态
2、每个客户端应该都在自己的线程中工作,否则会recv阻塞,影响别人
3、为了方便的将信息通知给所有的客户端,所以某个人发出的信息,必须都发给其他人一份,所以这里通过字典,将所有newsocket收集起来
4、提供退出机制,因为使用 sindows上的调试工具,直接退出,会返回一个 b'',所以这里使用 b'quit' 或 b''退出客户端
5、主线程(server),关闭的时候,线程也依次结束,避免占用资源。
6、可以提供日志功能,将日志输出到文件
1 import socket
2 import logging
3 import threading
4
5 FORMAT = '%(asctime)s %(thread)s %(threadName)s %(message)s'
6 logging.basicConfig(format=FORMAT, level=logging.INFO)
7
8 class ChatServer:
9 def __init__(self, ip='127.0.0.1', port=9999):
10 self.socket = socket.socket()
11 self.laddr = (ip, port)
12 self.event = threading.Event()
13 self.clients = {}
14
15 def start(self):
16 self.socket.bind(self.laddr)
17 self.socket.listen()
18 threading.Thread(target=self.accept, name='accept').start()
19
20 def accept(self):
21 while not self.event.is_set():
22 newsocket, raddr = self.socket.accept()
23 self.clients[raddr] = newsocket
24 threading.Thread(target=self.recv,args=(newsocket, raddr)).start()
25
26 def recv(self, s, raddr):
27 while not self.event.is_set():
28 data = s.recv(1024)
29 logging.info(data)
30
31 if data.strip() == b'quit' or data == b'':
32 self.clients.pop(raddr)
33 s.close()
34 break
35 for s in self.clients.values():
36 logging.info(data)
37 msg = 'msgs is that {}'.format(data.decode())
38 s.send(msg.encode())
39
40
41 def stop(self):
42 for s in self.clients.values():
43 s.close()
44 self.socket.close()
45 self.event.set()
46
47
48 cs = ChatServer()
49 cs.start()
50
51 while True:
52 cmd = input('>>')
53 if cmd.strip() == 'quit':
54 cs.stop()
55 break
56 logging.info(threading.enumerate())
服务器端代码
socket常用方法:
2.2、MakeFile:
socket.makefile(mode='r', buffering=None, * , encoding=None, error=None, newline=None)
创建一个与该套接子相关联的文件对象,将recv 方法看做读方法,将send 方法看做是写方法。
测试:显示读和写
1 import socket
2 sock = socket.socket()
3
4 ip = '127.0.0.1'
5 port = 9999
6
7 addr = (ip, port)
8
9 sock.bind(addr)
10 sock.listen()
11
12
13 s, _ = sock.accept()
14 print(s)
15
16 # 这儿权限只能 r w a 不能 出现 +
17 # 创建一个 类文件对象
18 f = s.makefile(mode='rw')
19
20 line = f.read(10) # recv
21 print(line )
22
23 f.write(' -----{}-----'.format(line))
24 f.flush()# 写完之后 flush一下
简单测试 read
1 import socket
2 sock = socket.socket()
3
4 ip = '127.0.0.1'
5 port = 9999
6
7 addr = (ip, port)
8
9 sock.bind(addr)
10 sock.listen()
11
12
13 s, _ = sock.accept()
14 print(s)
15
16 # 这儿权限只能 r w a 不能 出现 +
17 # 创建一个 类文件对象
18 f = s.makefile(mode='rw')
19
20 line = f.readline(10) # recv
21 print(line )
22
23 f.write(' -----{}-----'.format(line))
24 f.flush()# 写完之后 flush一下
readline 是遇到回车换行才 输出,read 是 够了字符数,输出
s : <socket.socket fd=212, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 52129)>
f : <_io.TextIOWrapper mode='rw' encoding='cp936'>
测试:将群聊服务器端 改为makefile
1 import socket
2 import logging
3 import threading
4
5 FORMAT = '%(asctime)s %(thread)s %(threadName)s %(message)s'
6 logging.basicConfig(format=FORMAT, level=logging.INFO)
7
8 class ChatServer:
9 def __init__(self, ip='127.0.0.1', port=9999):
10 self.socket = socket.socket()
11 self.laddr = (ip, port)
12 self.event = threading.Event()
13 self.clients = {}
14
15 def start(self):
16 self.socket.bind(self.laddr)
17 self.socket.listen()
18 threading.Thread(target=self.accept, name='accept').start()
19
20 def accept(self):
21 while not self.event.is_set():
22 newsocket, raddr = self.socket.accept()
23
24 f = newsocket.makefile('rw')
25
26 self.clients[raddr] = f
27 threading.Thread(target=self.recv,args=(newsocket, f, raddr)).start()
28
29 def recv(self, s,f, raddr):
30 while not self.event.is_set():
31 data = f.readline(10)
32 logging.info(data)
33
34 if data.strip() == 'quit' or data == b'':
35 self.clients.pop(raddr)
36 f.close() # 只关文件描述符,可能完全断不开,所以把socket也关一下
37 s.close()
38 break
39 for f in self.clients.values():
40 logging.info(data)
41 f.write(data)
42 f.flush()
43
44
45 def stop(self):
46 for s in self.clients.values():
47 s.close()
48 self.socket.close()
49 self.event.set()
50
51
52 cs = ChatServer()
53 cs.start()
54
55 while True:
56 cmd = input('>>')
57 if cmd.strip() == 'quit':
58 cs.stop()
59 break
60 logging.info(threading.enumerate())
改为makefile
2.2、TCP客户端编程
客户端编程步骤:
- 创建socket对象
- 连接到远程服务端的IP 和port ,connect()方法
- 传输数据
- 使用send, recv方法,发送数据,接受数据
关闭连接,释放资源
2.3、一个群聊程序:注意:群聊,所以,不发信息,也能recv信息,所以recv放到一个线程中,一直执行
服务器端程序:
1 import socket
2 import logging
3 import threading
4
5 FORMAT = '%(asctime)s %(thread)s %(threadName)s %(message)s'
6 logging.basicConfig(format=FORMAT, level=logging.INFO)
7
8 class ChatServer:
9 def __init__(self, ip='127.0.0.1', port=9999):
10 self.socket = socket.socket()
11 self.laddr = (ip, port)
12 self.event = threading.Event()
13 self.clients = {}
14
15 def start(self):
16 self.socket.bind(self.laddr)
17 self.socket.listen()
18 threading.Thread(target=self.accept, name='accept').start()
19
20 def accept(self):
21 while not self.event.is_set():
22 newsocket, raddr = self.socket.accept()
23
24 f = newsocket.makefile('rw')
25
26 self.clients[raddr] = f
27 threading.Thread(target=self.recv,args=(newsocket, f, raddr)).start()
28
29 def recv(self, s,f, raddr):
30 while not self.event.is_set():
31 data = f.readline(10)
32 logging.info(data)
33
34 if data.strip() == 'quit' or data == b'':
35 self.clients.pop(raddr)
36 f.close() # 只关文件描述符,可能完全断不开,所以把socket也关一下
37 # s.close()
38 break
39 for f in self.clients.values():
40 logging.info(data)
41 f.write(data)
42 f.flush()
43
44
45 def stop(self):
46 for s in self.clients.values():
47 s.close()
48 self.socket.close()
49 self.event.set()
50
51
52 cs = ChatServer()
53 cs.start()
54
55 while True:
56 cmd = input('>>')
57 if cmd.strip() == 'quit':
58 cs.stop()
59 break
60 logging.info(threading.enumerate())
server
客户端程序:
1 import socket
2 import threading
3
4 class ChatClient:
5 def __init__(self, ip='127.0.0.1', port=9999):
6 self.socket = socket.socket()
7 self.event = threading.Event()
8 self.raddr = (ip, port)
9
10 def start(self):
11 self.socket.connect(self.raddr)
12
13 self.send('hello I am client')
14 # while not self.event.is_set():
15 # data = self.socket.recv(1024)
16 # print(data)
17 threading.Thread(target=self.recv).start()
18
19 def send(self, msg):
20 self.socket.send(msg.encode())
21
22 def recv(self):
23 while not self.event.is_set():
24 data = self.socket.recv(1024)
25 print(data)
26
27 def stop(self):
28 self.socket.close()
29 self.event.set()
30
31 cc = ChatClient()
32 cc.start()
33
34 while True:
35 cmd = input('>>')
36 if cmd.strip() == 'quit':
37 cc.stop()
38 break
39 cc.send(cmd)
client
server:
client 1
client 2: