目录
一、初始版
1.1 代码效果演示
先启动服务器端,再启动客户端。在聊天中输入’quit’,退出聊天。
大概的原理:就是服务器端和客户端都分别启动两个线程用来完成发送消息和接收消息。
1.2服务器端源码
# -*- coding: utf-8 -*-
"""
Created on Sun Apr 26 22:39:40 2020
@author: kenwanmao
"""
import socket
from threading import Thread
import psutil
#获取网卡名称和其ip地址,不包括回环
def get_netcard():
netcard_info = []
info = psutil.net_if_addrs()
for k,v in info.items():
for item in v:
if item[0] == 2 and not item[1]=='127.0.0.1':
netcard_info.append((k,item[1]))
return netcard_info
def recv():
while True:
recv_data= newSocket.recv(1024)
recv_msg = recv_data.decode('utf-8')
if recv_msg == 'quit' :
print('The client wants to close the connection')
newSocket.close()
break
print('客户端>:', recv_msg)
def send_msg():
while True:
msg = input('Server>')
if msg == 'quit' :
print('close the connection!')
newSocket.close()
break
newSocket.send(msg.encode('utf-8'))
# 创建TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
local_address_list = get_netcard()
wlan_ip = local_address_list[-1][1]
print('本机的局域网ip:{}'.format(wlan_ip))
address = input('请输入本机的局域网ip:(default 自动填写)') or wlan_ip
ip_port = (address, 23333) # 绑定本地信息.
s.bind(ip_port)
s.listen(1)
print('chat server 已启动...,正在监听{}'.format(s.getsockname()))
newSocket, clientAddr = s.accept()
print('与客户端 {} 连接成功!'.format(newSocket.getpeername()))
thread_recv = Thread(target=recv)
thread_send = Thread(target=send_msg)
thread_recv.start()
thread_send.start()
thread_recv.join()
thread_send.join()
# 关闭这个客户端服务的套接字,只要关闭了,就意味着不能再为这个客户端服务器了,如果还需要服务,只能再次重新连接
newSocket.close()
# 关闭监听套接字,只要这个套接字关闭了,就意味着整个程序不能再接收到任何新的客户端的连接
s.close()
1.3客户端源码
# -*- coding: utf-8 -*-
"""
Created on Sun Apr 26 22:39:41 2020
@author: kopstone
"""
import socket,sys
from threading import Thread
def recv():
while True:
data_recv = s.recv(1024)
text_recv = data_recv.decode('utf-8')
if text_recv == 'quit' :
print('The server wants to close the connection')
s.close()
break
print('服务端>:', text_recv)
def send_msg():
while True:
msg = input(name+'>')
if msg == 'quit' :
print('close the connection!')
s.close()
break
s.send(msg.encode('utf-8'))
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 1、创建套接字
dest_ip = input('请输入服务器端ip: (default localhost)')
if len(dest_ip)<=0 :
print('目的ip不能为空')
sys.exit(0)
#dest_port = int(input('请输入服务端端口:'))
#dest_ip = '192.168.2.158'
dest_port = 23333
name = input('input your nickname (default kenwanmao) :') or 'kenwanmao'
s.connect((dest_ip,dest_port)) # 2、创建链接
thread_recv = Thread(target=recv)
thread_send = Thread(target=send_msg)
thread_recv.start()
thread_send.start()
thread_recv.join()
thread_send.join()
# 4、关闭
s.close()
1.4 用pyinstaller打包成可执行的.exe文件
第一步:安装 PyInstaller 模块与安装其他 Python 模块一样,使用 pip 命令安装即可。在命令行输入如下命令:
pip install pyinstaller
接下来使用命令行工具进入到目录下,执行如下命令:
pyinstaller -F D:\PythonWorkPlace\serverchat.py
最后在下面的位置找到。这种可执行的.exe文件可以在未安装python环境的电脑中直接运行。
打包结果:
二、将初始版的服务器端和客户端整合成一个文件
2.1 代码效果演示
启动方式:python localchat.py,进入后选s
python localchat.py,进入后选c
2.2 源代码
# -*- coding: utf-8 -*-
"""
Created on 2020/9/20
@author: kenwanmao
"""
import socket, argparse
from threading import Thread
import psutil
#获取网卡名称和其ip地址,不包括回环
def get_netcard():
netcard_info = []
info = psutil.net_if_addrs()
for k,v in info.items():
for item in v:
if item[0] == 2 and not item[1]=='127.0.0.1':
netcard_info.append((k,item[1]))
return netcard_info
def server_recv(newSocket):
while True:
recv_data= newSocket.recv(1024)
recv_msg = recv_data.decode('utf-8')
if recv_msg == 'quit' :
print('The client wants to close the connection')
newSocket.close()
break
print('客户端>:', recv_msg)
def server_send_msg(newSocket):
while True:
msg = input('Server>')
if msg == 'quit' :
print('close the connection!')
newSocket.close()
break
newSocket.send(msg.encode('utf-8'))
def client_recv(s):
while True:
data_recv = s.recv(1024)
text_recv = data_recv.decode('utf-8')
if text_recv == 'quit' :
print('The server wants to close the connection')
s.close()
break
print('服务端>:', text_recv)
def client_send_msg(s,name):
while True:
msg = input(name+'>')
if msg=='quit' :
print('close the connection!')
s.close()
break
s.send(msg.encode('utf-8'))
def server(address, port):
# 创建TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ip_port = (address, port)
s.bind(ip_port)
s.listen(1)
print('chat server 已启动...,正在监听{}'.format(s.getsockname()))
newSocket, clientAddr = s.accept()
print('与客户端 {} 连接成功!'.format(newSocket.getpeername()))
thread_recv = Thread(target=server_recv, args=(newSocket,))
thread_send = Thread(target=server_send_msg, args=(newSocket,))
thread_recv.start()
thread_send.start()
thread_recv.join()
thread_send.join()
# 关闭这个客户端服务的套接字,只要关闭了,就意味着不能再为这个客户端服务器了,如果还需要服务,只能再次重新连接
newSocket.close()
# 关闭监听套接字,只要这个套接字关闭了,就意味着整个程序不能再接收到任何新的客户端的连接
s.close()
def client(address, port):
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 1、创建套接字
name = input('input your nickname (default kenwanmao) :') or 'kenwanmao'
s.connect((address, port)) # 2、创建链接
thread_recv = Thread(target=client_recv, args=(s,))
thread_send = Thread(target=client_send_msg, args=(s,name))
thread_recv.start()
thread_send.start()
thread_recv.join()
thread_send.join()
# 4、关闭
s.close()
if __name__ == '__main__':
local_address_list = get_netcard() #获取本机上的ip地址,包括虚拟机的
wlan_ip = local_address_list[-1][1] #获取本机上wlan网卡的ip地址,这才是局域网的地址。
print('本机的局域网ip:{}'.format(wlan_ip))
while True:
getstart = input('请选择启动端,s代表服务器端、c代表客户端,输入其他无效会重复,q代表退出:')
if getstart == 's':
server(wlan_ip,23333) #启动服务器端
elif getstart == 'c':
dest_ip = input('请输入服务器端ip (必填):')
if len(dest_ip)<=0 :
print('警告,服务器端ip不能为空!')
continue
client(dest_ip,23333) #启动客户端
elif getstart == 'q':
break
else :
continue
三、TLS加密加密版
尚未完成,仍存在一些问题。但留代码在这里,可能将来再解决。
# -*- coding: utf-8 -*-
"""
Created on 2020/9/20
@author: kenwanmao
"""
import socket, argparse, ssl
from threading import Thread
def server_recv(newSocket):
while True:
recv_data= newSocket.recv(1024)
recv_msg = recv_data.decode('utf-8')
if recv_msg == 'quit' :
print('The client wants to close the connection')
newSocket.close()
break
print('客户端>:', recv_msg)
def server_send_msg(newSocket):
while True:
msg = input('Server>')
if 'quit' in msg :
print('close the connection!')
newSocket.close()
break
newSocket.send(msg.encode('utf-8'))
def client_recv(ssl_sock):
while True:
data_recv = ssl_sock.recv(1024)
text_recv = data_recv.decode('utf-8')
if text_recv == 'quit' :
print('The server wants to close the connection')
ssl_sock.close()
break
print('服务端>:', text_recv)
def client_send_msg(ssl_sock,name):
while True:
msg = input(name+'>')
if 'quit' in msg :
print('close the connection!')
ssl_sock.close()
break
ssl_sock.send(msg.encode('utf-8'))
def server(address, port, cafile=None):
purpose = ssl.Purpose.CLIENT_AUTH
context = ssl.create_default_context(purpose, cafile=cafile)
#context.load_cert_chain(certfile)
# 创建TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ip_port = (address, port)
s.bind(ip_port)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.listen(1)
print('chat server 已启动...,正在监听{}'.format(s.getsockname()))
newSocket, clientAddr = s.accept()
print('与客户端 {} 连接成功!'.format(newSocket.getpeername()))
ssl_sock = context.wrap_socket(newSocket,server_side=True)
thread_recv = Thread(target=server_recv, args=(ssl_sock,))
thread_send = Thread(target=server_send_msg, args=(ssl_sock,))
thread_recv.start()
thread_send.start()
thread_recv.join()
thread_send.join()
# 关闭这个客户端服务的套接字,只要关闭了,就意味着不能再为这个客户端服务器了,如果还需要服务,只能再次重新连接
newSocket.close()
# 关闭监听套接字,只要这个套接字关闭了,就意味着整个程序不能再接收到任何新的客户端的连接
s.close()
def client(address, port, cafile = None):
purpose = ssl.Purpose.SERVER_AUTH
context = ssl.create_default_context(purpose, cafile=cafile)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 1、创建套接字
name = input('input your nickname (default kenwanmao) :') or 'kenwanmao'
s.connect((address, port)) # 2、创建链接
ssl_sock = context.wrap_socket(s,server_hostname=address)
thread_recv = Thread(target=client_recv, args=(ssl_sock,))
thread_send = Thread(target=client_send_msg, args=(ssl_sock,name))
thread_recv.start()
thread_send.start()
thread_recv.join()
thread_send.join()
# 4、关闭
s.close()
if __name__ == '__main__':
choices = {'client': client, 'server': server}
parser = argparse.ArgumentParser(description='局域网一对一聊天')
parser.add_argument('role', choices=choices, help='选择服务器(聊天发起者)或客户端启动')
parser.add_argument('-host',default='127.0.0.1',help='the server listens at (default 127.0.0.1);'
' host the client sends to ')
parser.add_argument('-p', metavar='PORT', type=int, default=6666,help='UDP port (default 6666)')
#parser.add_argument('-s',metavar='certfile',default=None)
args = parser.parse_args()
function = choices[args.role]
function(args.host, args.p)
参考博客
Python3 socket编程,利用多线程完成最简单的聊天室 TCP和UDP
【Python3之socket编程】
Python3+ssl实现加密通信
argument after * must be an iterable解决方法
Python PyInstaller安装和使用教程(详解版)
python 获取网卡名称及其IP地址