Socket介绍
协议族
AF表示Address Family,用于socket()第一个参数
名称含义
AF_INETIPV4AF_INET6IPV6AF_UNIXUnix Domain Socket, windows没有
Socket类型
名称含义
SOCK_STREAM面向连接的流套接字。默认值,TCP协议SOCK_DGRAM无连接的数据报文套接字。UDP协议
TCP编程
Socket编程,需要两端,一般来说需要一个服务端、一个客户端,服务端称为Server,客户端称为Client。这种编程模式也称为CS编程。
TCP服务端编程
服务器端编程步骤
创建Socket对象绑定IP地址Address和端口Port。bind()方法 IPV4地址为一个二元组(‘IP地址字符串’, port)开始监听,将在指定的IP端口上监听。listen()方法获取用于传送数据的Socket对象 socket.accept() -> (socket object, address info) accept方法阻塞等待客户端建立连接,返回一个新的Socket对象和客户端地址的二元组 地址是远程客户端的地址,IPV4它是一个二元组(clientadddr, port)
接收数据 recv(bufsize[, flags])使用缓冲区接收数据发送数据 send(bytes)发送数据
import socket
s
= socket
.socket
()
s
.bind
(('127.0.0.1', 9988))
s
.listen
()
s1
, info
= s
.accept
()
print(s1
, info
)
data
= s1
.recv
(1024)
print(info
, data
)
s1
.send
(b
'it is s1 send test!!!')
s1
.close
()
s2
, info
= s
.accept
()
data
= s2
.recv
(1024)
print(info
, data
)
s2
.send
(b
'it is s2 send test!!!')
s2
.close
()
print('will close!')
s
.close
()
<socket.socket fd=1324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9988), raddr=('127.0.0.1', 10217)> ('127.0.0.1', 10217)
('127.0.0.1', 10217) b'client1 send message'
('127.0.0.1', 10225) b'client2 send message'
will close!
查看监听端口
Windows命令
netstat -anp tcp
| findstr 9988
linux命令
netstate -tanl
| grep 9988
ss -tanl
| grep 9988
群聊程序
需求分析
聊天工具是CS程序,C是每一个客户端client,S是服务器端server。
服务器应该具有的功能:
启动服务,包括绑定地址和端口,并监听。建立连接,能和多个客户端建立连接接收不同用户的信息分发,将接收的某个用户的信息转发到已连接的所有客户端停止服务记录连接的客户端
代码实现
class ChatServer:
def __init__(self
, ip
, port
):
self
.sock
= socket
.socket
()
self
.addr
= (ip
, port
)
def start(self
):
pass
def accept(self
):
pass
def recv(self
):
pass
def stop(self
):
pass
import socket
import logging
import threading
import datetime
Format
= '%(asctime)s - %(threadName)s - %(message)s'
logging
.basicConfig
(format=Format
, level
=logging
.INFO
)
class ChatServer:
def __init__(self
, ip
='127.0.0.1', port
=9988):
self
.sock
= socket
.socket
()
self
.addr
= (ip
, port
)
self
.clients
= {}
def start(self
):
self
.sock
.bind
(self
.addr
)
self
.sock
.listen
()
threading
.Thread
(target
=self
.accept
).start
()
def accept(self
):
while True:
sock
, client
= self
.sock
.accept
()
self
.clients
[client
] = sock
threading
.Thread
(target
=self
.recv
, args
=(sock
, client
)).start
()
def recv(self
, sock
:socket
.socket
, client
):
while True:
data
= sock
.recv
(1024)
msg
= '{:%Y.%m.%d %H:%M:%S} {}:{}-->{}\n'.format(datetime
.datetime
.now
(), *client
, data
.decode
())
logging
.info
(msg
)
msg
= msg
.encode
()
for s
in self
.clients
.values
():
s
.send
(msg
)
def stop(self
):
for s
in self
.clients
.values
():
s
.close
()
self
.sock
.close
()
cs
= ChatServer
()
cs
.start
()
2019-10-12 22:15:28,617 - Thread-7 - 2019.10.12 22:15:28 127.0.0.1:12693-->234
2019-10-12 22:16:06,117 - Thread-8 - 2019.10.12 22:16:06 127.0.0.1:12705-->12334545
2019-10-12 22:16:17,581 - Thread-7 - 2019.10.12 22:16:17 127.0.0.1:12693-->123222222222
2019-10-12 22:16:27,022 - Thread-7 - 2019.10.12 22:16:27 127.0.0.1:12693-->
Exception in thread Thread-7:
Traceback (most recent call last):
File "c:\programdata\miniconda3\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "c:\programdata\miniconda3\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-1-0f4ba791ec58>", line 31, in recv
data = sock.recv(1024)
ConnectionAbortedError: [WinError 10053] 你的主机中的软件中止了一个已建立的连接。
2019-10-12 22:16:46,551 - Thread-8 - 2019.10.12 22:16:46 127.0.0.1:12705-->quit
Exception in thread Thread-8:
Traceback (most recent call last):
File "c:\programdata\miniconda3\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "c:\programdata\miniconda3\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-1-0f4ba791ec58>", line 36, in recv
s.send(msg)
ConnectionAbortedError: [WinError 10053] 你的主机中的软件中止了一个已建立的连接。
import socket
import logging
import threading
import datetime
Format
= '%(asctime)s - %(threadName)s - %(message)s'
logging
.basicConfig
(format=Format
, level
=logging
.INFO
)
class ChatServer:
def __init__(self
, ip
='127.0.0.1', port
=9977):
self
.sock
= socket
.socket
()
self
.addr
= ip
, port
self
.clients
= {}
self
.event
= threading
.Event
()
def start(self
):
self
.sock
.bind
(self
.addr
)
self
.sock
.listen
()
threading
.Thread
(target
=self
.accept
).start
()
def accept(self
):
while not self
.event
.is_set
():
sock
, client
= self
.sock
.accept
()
self
.clients
[client
] = sock
threading
.Thread
(target
=self
.recv
, args
=(sock
, client
)).start
()
def recv(self
, sock
:socket
.socket
, client
):
while not self
.event
.is_set
():
data
= sock
.recv
(1024)
msg
= '{:%Y.%m.%d %H:%M:%S} {}:{}-->{}\n'.format(datetime
.datetime
.now
(), *client
, data
.decode
())
logging
.info
(msg
)
msg
= msg
.encode
()
for s
in self
.clients
.values
():
s
.send
(msg
)
def stop(self
):
self
.event
.set()
for s
in self
.clients
.values
():
s
.close
()
self
.sock
.close
()
cs
= ChatServer
()
cs
.start
()
while True:
cmd
= input('>>>').strip
()
if cmd
== 'quit' or cmd
== '':
cs
.stop
()
threading
.Event
().wait
(3)
break
File "<ipython-input-2-23af79808cf7>", line 35
for s in self.clients.values():
^
SyntaxError: unexpected EOF while parsing
客户端主动断开带来的问题
服务端知道自己何时断开,如果客户端断开,服务器不知道。(客户端主动断开,服务端recv会得到一个空串) 所以,好的做法是,客户端断开发出特殊消息通知服务器端断开连接。但是,如果客户端主动断开,服务端主动发送一个空消息,超时返回异常,捕获异常并清理连接。 即使为客户端提供了断开命令,也不能保证客户端使用它断开连接。但是还是要增加这个退出功能。
注意:
由于GIL和内置数据结构的读写原子性,单独操作字典的某一项item是安全的。但是遍历过程是线程不安全的,遍历中有可能被打断,其他线程如果对字典元素进行增加、弹出,都会影响字典的size,就会抛出异常。所以还是要加锁Lock。
import logging
import threading
import datetime
import socket
Format
= '%(asctime)s - %(threadName)s - %(message)s'
logging
.basicConfig
(format=Format
, level
=logging
.INFO
)
class ChatServer:
def __init__(self
, ip
='127.0.0.1', port
=9911)
self
.sock
= socket
.socket
()
self
.addr
= ip
, port
self
.clients
= {}
self
.event
= threading
.Event
()
self
.lock
= threading
.Lock
()
def start(self
):
self
.sock
.bind
(self
.addr
)
self
.sock
.listen
()
threading
.Thread
(target
=self
.accept
).start
()
def accept(self
):
while not self
.event
.is_set
():
sock
, client
= self
.sock
.accept
()
with self
.lock
:
self
.clients
[client
] = sock
threading
.Thread
(target
=self
.recv
, args
=(sock
, client
)).start
()
def recv(self
, sock
:socket
.socket
, client
):
while not self
.event
.is_set
():
data
= sock
.recv
(1024)
msg
= data
.decode
().strip
()
if msg
== 'quit' or msg
== '':
with self
.lock
:
self
.clients
.pop
(client
)
sock
.close
()
logging
.info
('{} quits}'.format(client
))
break
msg
= '{:%Y.%m.%d %H:%M:%S} {}:{}-->{}\n'.format(datetime
.datetime
.now
(), *client
, data
.decode
())
logging
.info
(msg
)
msg
= msg
.encode
()
with self
.lock
:
for s
in self
.clients
.values
():
s
.send
(msg
)
def stop(self
):
self
.event
.set()
with self
.lock
:
for s
in self
.clients
.values
():
s
.close
()
self
.sock
.close
()
cs
= ChatServer
()
cs
.start
()
while True:
cmd
= input('>>>').strip
()
if cmd
== 'quit' or cmd
== '':
cs
.stop
()
threading
.Event
().wait
(3)
break
logging
.info
(threading
,enumerate())
logging
.info
(cs
.clients
)
TCP客户端编程
客户端编程步骤
创建socket对象连接到远端服务端的ip和port,connect()方法传输数据
使用send、recv方法发送、接收数据 关闭连接,释放资源
import socket
client
= socket
.socket
()
addr
= ('127.0.0.1', 9999)
client
.connect
(addr
)
client
.send
(b
'123123123')
data
= client
.recv
(1024)
print(data
)
client
.close
()
客户端类
import socket
import threading
import logging
import datetime
Format
= '%(asctime)s - %(threadName)s - %(message)s'
logging
.basicConfig
(format=Format
, level
=logging
.INFO
)
class ChatClient:
def __init__(self
, ip
='127.0.0.1', port
=9911):
self
.sock
= socket
.socket
()
self
.addr
= ip
, port
self
.event
= threading
.Event
()
def start(self
):
self
.sock
.connect
(self
.addr
)
self
.sock
.send
('i am ready')
threading
.Thread
(target
=self
.recv
, name
='recv').start
()
def recv(self
):
while not self
.event
.is_set
():
try:
data
= self
.sock
.recv
(1024)
except Exception
as e
:
logging
.error
(e
)
break
msg
= '{:%Y.%m.%d %H:%M:%S} {}:{}-->{}\n'.format(datetime
.datetime
.now
(), *client
, data
.decode
())
logging
.info
(msg
)
def send(self
, msg
:str):
data
= '{}\n'.format(msg
.strip
()).encode
()
self
.sock
.send
(data
)
def stop(self
):
self
.sock
.close
()
self
.event
.wait
(3)
self
.event
.set()
logging
.info
('Client stops')
def main():
cc
= ChatClient
()
cc
.start
()
while True:
cmd
= input('>>>')
if cmd
.strip
() == 'quit':
cc
.stop
()
break
File "<ipython-input-3-7182a4a85f47>", line 17
def start(self):
^
SyntaxError: unexpected EOF while parsing
import socket
import logging
ip
= '127.0.0.1'
port
= 9966
Format
= '%(asctime)s - %(threadName)s - %(message)s'
logging
.basicConfig
(format=Format
, level
=logging
.INFO
)
addr
= ip
, port
s
= socket
.socket
()
s
.bind
(addr
)
s
.listen
()
sock
, info
= s
.accept
()
data
= sock
.recv
(1024)
logging
.info
(data
)
2019-10-13 17:22:12,913 - MainThread - b'123qwe'