Python3 网络通信 网络聊天室 文件传输

功能描述

该项目将实现一个文字和文件传输的客户端和服务器程序通信应用程序。它将传输和接收视频文件。

文本消息必须通过TCP与服务器通信,而客户端自己用UDP传输视频文件。

程序将支持一下功能:

  • 用户身份验证
  • 向服务器发布消息
  • 编辑或删除消息、读取消息
  • 从服务器发送消息
  • 读取活动用户的信息
  • 上传视频文件从一个用户到另一个用户

简要流程

  • 该项目将实现各种应用程序协议来实现以上功能。
  • 服务器将监听指定为命令行参数的端口,并等待客户端监听连接。
  • 客户端程序将发起一个与服务器的TCP连接。
  • 在连接建立后,用户将启动身份验证过程。客户端将与用户交互通过命令行界面。
  • 身份验证成功后,用户可以执行一系列命令
  • 最终登出
  • 客户端和服务器都必须打印有意义的提示消息

简要设计

  • Server: 使用一个类来保存每个客户端信息,包括用户名和IP地址的映射,并多线程开始接收每个客户端的处理程序。
  • Client: 三个线程:主线程为获取用户输入和发送到服务器,和一个线程为接收服务器响应,一个线程UDP传输文件

功能协议

  • Login: flag("Login") + username + password
  • MSG: flag("MSG") + message
  • EDT: flag("EDT") + msg_index + msg_time + new_msg
  • DLT: flag("DLT") + msg_index + msg_time
  • RDM: flag("RDM") + msg_time
  • ATU: flag("ATU")
  • OUT: flag("OUT")
  • UPD: flag("UDP") + username + filename

Usage

  • Serverpython server.py 端口号 最大密码尝试次数
  • Clientpython client.py 服务器IP 服务器端口 客户端UDP端口

效果展示



代码

client 点击查看代码

import socket
import threading
import time
import sys
import os UDP_port = 6666 LoginStatus = False
TCP_running = True clientSocket = socket.socket() login_Event = threading.Event()
response_Event = threading.Event() user_list = []
USERNAME = "" # get IP address and port after ATU command
def get_address_port_by_username(name):
if len(user_list) == 0:
print("[error] need run ATU command first")
else:
for i in user_list:
user_info = i.split(", ")
username = user_info[0]
address = user_info[1]
port = user_info[2]
if username == name:
return address, port
return None # LOGIN
def log_in(clientSocket,udp_port):
global USERNAME
print("Username: ", end="")
name = input('')
print("Password: ", end="")
pwd = input('') FLAG = "LOGIN"
USERNAME = name
PWD = pwd login_msg = FLAG + "%%" + name + "%%" + PWD + "%%" + str(udp_port)
clientSocket.send(login_msg.encode('utf-8')) def reponse_to_client(recv_msg):
global TCP_running
global LoginStatus
global login_Event
send_msg = ""
recv_msg_list = []
recv_msg_list = recv_msg.split("%%")
res = True
if len(recv_msg_list) != 0:
flag = recv_msg_list[0] if (flag == "LOGIN"):
TYPE = recv_msg_list[1]
if(TYPE == "0"):
LoginStatus = True
else:
LoginStatus = False
# inform the main loop the result from server
login_Event.set()
print(recv_msg_list[2]) elif (flag == "MSG"):
print(recv_msg_list[1])
elif flag == "EDT":
TYPE = recv_msg_list[1]
if (TYPE == "0"):
edit_res = True
else:
edit_res = False
print(recv_msg_list[2])
elif (flag == "RDM"):
txt = recv_msg_list[1:]
for i in txt:
print(i)
pass
elif (flag == "DLT"):
TYPE = recv_msg_list[1]
if (TYPE == "0"):
del_res = True
else:
del_res = False
print(recv_msg_list[2])
pass
elif (flag == "ATU"):
NUM = recv_msg_list[1]
if NUM == "0":
print(recv_msg_list[2])
else:
txt = recv_msg_list[2:]
for i in txt:
print(i)
user_list.append(i)
elif (flag == "OUT"):
TCP_running = False
print(recv_msg_list[1]) def read_server(s):
global response_Event
global TCP_running
while TCP_running:
# the thread always receive from server
content = s.recv(2048).decode('utf-8')
reponse_to_client(content) # inform the mian loop, resume it
response_Event.set()
# s.close()
os._exit(1) def execute_command(clientSocket):
response_Event.clear()
while TCP_running:
print("Enter one of the following commands (MSG, DLT, EDT, RDM, ATU, OUT, UPD): ", end="")
msg = input('')
if not msg:
continue
command = msg.split(" ")[0] if command == "MSG":
if len(msg.split(" ")) < 2:
print("need arguments")
continue txt = msg.split(" ")[1:]
msg = command + "%%" + ' '.join(txt)
elif command == "EDT":
if len(msg.split(" ")) < 2:
print("[error] need arguments")
continue
args = msg.split(" ")[1:]
index = args[0][1:]
dt = ' '.join(args[1:5])
txt = ' '.join(args[5:])
msg = command + "%%" + index + "%%" + dt + "%%" + txt elif command == "DLT":
if len(msg.split(" ")) < 2:
print("[error] need arguments")
continue
args = msg.split(" ")[1:]
index = args[0][1:]
dt = ' '.join(args[1:])
msg = command + "%%" + index + "%%" + dt
pass
elif command == "RDM":
if len(msg.split(" ")) < 2:
print("[error] need arguments")
continue dt = ' '.join(msg.split(" ")[1:])
msg = command + "%%" + dt
elif command == "ATU":
if len(msg.split(" ")) < 2:
pass
else:
print("[error] too many arguments")
continue
pass
elif command == "OUT":
if len(msg.split(" ")) < 2:
pass
else:
print("[error] too many arguments")
continue
pass
elif command == "UPD":
if len(msg.split(" ")) < 3:
print("[error] need more arguments")
continue
else:
UDP_send(msg.split(" ")[1], msg.split(" ")[2])
continue
else:
print("[error] unknown command")
continue clientSocket.send(msg.encode('utf-8'))
# block, waiting for server response
time.sleep(0.3)
response_Event.wait(30)
response_Event.clear() # mian func
def tcp_start(server_IP, server_port, udp_port):
global clientSocket
clientSocket = socket.socket()
clientSocket.connect((server_IP, server_port)) TCP_t = threading.Thread(target=read_server, args=(clientSocket,)).start() # login
while LoginStatus == False:
log_in(clientSocket, udp_port)
# waiting for server ...
time.sleep(0.3)
# block, waiting for server response
login_Event.wait(30)
login_Event.clear()
if LoginStatus:
# print("Debug: log in success")
pass
else:
# print("Debug: log in fail")
pass # commands
execute_command(clientSocket) def UDP_send(username, filename):
global USERNAME
address, port = get_address_port_by_username(username)
BUF_SIZE = 1024
# print(address, port)
server_addr = (address, int(port)) client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
count = 0
f = open(filename, 'rb')
while True:
if count == 0:
# send the first message inculding filename
data = "UDP%%"+USERNAME+"_"+filename
client.sendto(data.encode('utf-8'), server_addr) data = f.read(BUF_SIZE)
if str(data) != "b''":
client.sendto(data, server_addr)
else:
client.sendto('end'.encode('utf-8'), server_addr) # end for the file and send a speical "end" flag
# lecture1.mp4 has been uploaded
print(filename + " has been uploaded.")
execute_command(clientSocket)
break
count += 1
time.sleep(0.001)
f.close()
client.close() def UDP_recv(udp_port):
BUF_SIZE = 1024
server_addr = ('127.0.0.1', udp_port) # UDP: socket.SOCK_DGRAM
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(server_addr) count = 0
f = None
filename = ""
while True:
data, addr = server.recvfrom(BUF_SIZE)
if count == 0:
# recive the starting message inculding filename
recv_data_array = data.decode('utf-8').split("%%")
if len(recv_data_array) > 1:
if recv_data_array[0] == "UDP":
filename = recv_data_array[1]
f = open(filename, 'wb')
count += 1
elif str(data) != "b'end'":
try:
f.write(data)
# print(count)
except:
print("[erroe] can not write")
count += 1
else:
f.close()
# print(count)
count = 0
print("\nReceived " + filename.split("_")[1] + " from " + filename.split("_")[0]) if __name__ == '__main__':
# python client.py server_IP server_port client_udp_server_port
if len(sys.argv) > 3:
server_IP = sys.argv[1]
server_port = int(sys.argv[2])
client_udp_server_port = int(sys.argv[3])
udp_recv_t = threading.Thread(target=UDP_recv, args=(client_udp_server_port,)).start()
tcp_start(server_IP, server_port, client_udp_server_port)
else:
print("[error] Usage: python client.py server_IP server_port client_udp_server_port ") # just for test
# server_IP = "127.0.0.1"
# server_port = 9000
# client_udp_server_port = 7777
# udp_recv_t = threading.Thread(target=UDP_recv, args=(client_udp_server_port,)).start()
# tcp_start(server_IP, server_port, client_udp_server_port)
Server 点击查看代码
# Python 3
# coding: utf-8 from socket import *
import time
import sys
import threading
import traceback # If true, print debug information
__DEBUG = False
# max number of consecutive failed attempts
MAX_ERR_COUNT = 0
# server IP
serverIP = '127.0.0.1' # record clients who try to connect to server
client_connect_list = [] # ClientInfo class
# manager client infomation
class ClientInfo():
m_IP=""
m_loginTimestamp = -1
m_preLoginTime = -1
m_username=""
m_UDPServerPort = -1
m_errorCount = 0 def __init__(self, IP):
self.m_IP = IP def add_logInfo(self, name, timestamp, UDPport):
self.m_loginTimestamp = timestamp
self.m_username = name
self.m_UDPServerPort = UDPport if self.m_errorCount < MAX_ERR_COUNT:
self.m_preLoginTime = self.m_loginTimestamp
# print("Debug: self.m_errorCount: " + str(self.m_errorCount))
return True
else:
if (int(self.m_loginTimestamp) - int(self.m_preLoginTime) > 30):
self.m_errorCount = 0
self.m_errorCount = self.m_errorCount + 1
return True
else:
return False
# record number_of_consecutive_failed_attempts
def error_password(self):
self.m_errorCount = self.m_errorCount + 1 # check name and password
def check_credential(username, password):
res = False
with open('credentials.txt', 'r') as file:
credential_list = file.readlines()
for credential in credential_list:
name = credential.strip().split(" ")[0]
pwd = credential.strip().split(" ")[1]
if (name==username and pwd == password):
res = True
break
if __DEBUG: print("check_credential: res: " , res)
return res # Get client infomation by IP
def get_client_from_connect_list(IP):
for c in client_connect_list:
if c.m_IP == IP:
return c
return None # Add a new client to cse_userlog.txt
def add_user_log(name, timestamp, IP, UDPport):
dt = time.strftime("%d %b %Y %H:%M:%S", time.localtime(int(timestamp)))
address = IP[0]
txt = dt + "; " + name + "; " + address + "; " + str(UDPport)
index = 0
# read old file to get log index
with open('cse_userlog.txt', mode='r', encoding="utf-8") as user_file:
log_list = user_file.readlines()
if log_list:
last_line = log_list[-1]
index = int(last_line.strip().split("; ")[0])
else:
index = 0
# write new log in file
with open('cse_userlog.txt', mode='a+', encoding="utf-8") as user_file:
msg = str(index+1) + '; ' + txt
user_file.write(msg + "\n") # OUT: delete a client from cse_userlog.txt and adjust other client's index
def del_user_log(name):
new_log_list=[]
index = 1
with open('cse_userlog.txt', mode='r', encoding="utf-8") as user_file:
log_list = user_file.readlines()
for log in log_list:
log_arr = log.split("; ")
log_txt = log_arr[1:]
log_prot = log_arr[4]
log_addr = log_arr[3]
log_name = log_arr[2]
if log_name == name:
if __DEBUG: print("Debug: find same IP need to delete: " + str(log_arr[0]))
pass
else:
txt = "; ".join(i for i in log_txt)
msg = str(index) + "; " + txt
index = index + 1
new_log_list.append(msg) with open('cse_userlog.txt', mode='w', encoding="utf-8") as user_file:
user_file.writelines(new_log_list) # ATU: read other user log from cse_userlog.txt and return
def show_user_log_exclude_self(name):
new_log_list=[] with open('cse_userlog.txt', mode='r', encoding="utf-8") as user_file:
log_list = user_file.readlines()
for log in log_list:
log_arr = log.split("; ")
log_txt = log_arr[1:]
log_name = log_arr[2]
if log_name == name :
if __DEBUG: print("Debug: find same name no need to show: " + str(log_arr[0]))
pass
else:
txt = "; ".join(i for i in log_txt)
msg = txt
new_log_list.append(msg)
return new_log_list # MSG: When someone posts a new message, add it to the messagelog.txt
def add_msg_log(name, timestamp, txt):
dt = time.strftime("%d %b %Y %H:%M:%S", time.localtime(int(timestamp)))
log_txt = dt + "; " + name + "; " + txt
index = 0
with open('messagelog.txt', mode='r', encoding="utf-8") as msg_file:
log_list = msg_file.readlines()
if log_list:
last_line = log_list[-1]
index = int(last_line.strip().split("; ")[0])
else:
index = 0
with open('messagelog.txt', mode='a+', encoding="utf-8") as msg_file:
msg = str(index + 1) + '; ' + log_txt + '; ' + 'no'
msg_file.write(msg + "\n")
return index + 1 # convert formatted date time to timestamp
def dt_to_timestamp(dt):
timeArray = time.strptime(dt, "%d %b %Y %H:%M:%S")
timeStamp = int(time.mktime(timeArray))
return timeStamp # RDM: read message log by different time
def read_msg_log(timestamp):
new_log_list=[]
with open('messagelog.txt', mode='r', encoding="utf-8") as msg_file:
log_list = msg_file.readlines()
for log in log_list:
log_arr = log.split("; ")
log_dt = log_arr[1]
log_timestamp = dt_to_timestamp(log_dt)
log_name = log_arr[2]
if log_timestamp < timestamp:
pass
else:
new_log_list.append(log)
return new_log_list # DLT: delete a client's own message by time
def del_msg_log(name, del_index, del_dt):
del_res = False
del_txt = ""
new_log_list=[]
index = 1
with open('messagelog.txt', mode='r', encoding="utf-8") as msg_file:
log_list = msg_file.readlines()
for log in log_list:
log_arr = log.split("; ")
log_txt = log_arr[1:]
log_dt = log_arr[1]
log_name = log_arr[2]
if log_dt == del_dt and log_name == name and index == del_index:
del_res = True
del_txt = log_arr[3]
if __DEBUG: print("Debug: find same name and index and time need to delete: " + str(log_arr[0]))
pass
else:
txt = "; ".join(i for i in log_txt)
msg = str(index) + "; " + txt
index = index + 1
new_log_list.append(msg) with open('messagelog.txt', mode='w', encoding="utf-8") as msg_file:
msg_file.writelines(new_log_list) return del_res, del_txt # EDT: edit a client's own message by time
def edit_msg_log(name, edit_index, edit_dt, edit_txt):
edit_res = False
new_log_list=[]
edit_time = ''
with open('messagelog.txt', mode='r', encoding="utf-8") as msg_file:
log_list = msg_file.readlines()
for log in log_list:
log_arr = log.split("; ")
log_index = log_arr[0]
log_txt = log_arr[3]
log_dt = log_arr[1]
log_name = log_arr[2]
if log_dt == edit_dt and log_name == name and log_index == str(edit_index):
edit_res = True
edit_time = time.strftime("%d %b %Y %H:%M:%S", time.localtime(int(time.time())))
log_arr[3] = edit_txt
log_arr[1] = edit_time
log_arr[4] = "yes\n"
if __DEBUG: print("Debug: find same name and index and time need to edit: " + str(log_arr[0]))
log = "; ".join(log_arr)
new_log_list.append(log)
if __DEBUG: print("Debug: new log ", log)
else:
new_log_list.append(log) with open('messagelog.txt', mode='w', encoding="utf-8") as msg_file:
msg_file.writelines(new_log_list) return edit_res, edit_time # parse the msg from client and make different responses based on different client commands
def reponse_to_client(clientSocket, IP, recv_msg):
socket_status = True
send_msg = ""
recv_msg_list = []
recv_msg_list = recv_msg.split("%%")
pwd_status = True
if len(recv_msg_list) != 0:
flag = recv_msg_list[0]
else:
print("[error] parse error")
pass if __DEBUG: print("Debug: flag is " + flag)
client = get_client_from_connect_list(IP)
# response to login
if (flag == "LOGIN"):
name = recv_msg_list[1]
pwd = recv_msg_list[2]
UDPPort = recv_msg_list[3]
pwd_status = check_credential(name, pwd)
# if wrong name and password, number of consecutive failed attempts adds 1
if not pwd_status:
client.error_password() if client is None:
client = ClientInfo(IP)
client_connect_list.append(client)
# save user information
add_status = client.add_logInfo(name, time.time(), UDPPort)
if add_status == True:
if pwd_status == False:
TYPE = "1"
if client.m_errorCount == MAX_ERR_COUNT:
DATA = "Invalid Password. Your account has been blocked. Please try again later"
else:
DATA = "Invalid Password. Please try again!"
elif pwd_status == True:
TYPE = "0"
DATA = "Log in sucessfully!"
add_user_log(name, client.m_loginTimestamp, IP, client.m_UDPServerPort)
server_output = client.m_username + " log in sucessfully"
print(server_output)
else:
TYPE = "2"
DATA = "Your account is blocked due to multiple login failures. Please try again later"
FLAG = flag
send_msg = FLAG + "%%" + TYPE + "%%" + DATA # response to "MSG" command
elif (flag == "MSG"):
txt = recv_msg_list[1]
if __DEBUG: print("Debug: " + txt)
timestamp = time.time()
msg_index = add_msg_log(client.m_username, timestamp, txt)
dt = time.strftime("%d %b %Y %H:%M:%S", time.localtime(int(timestamp)))
FLAG = flag
DATA = "Message #" + str(msg_index) + " posted at " + dt
send_msg = FLAG + "%%" + DATA
server_output = client.m_username + " posted MSG #" + str(msg_index) + " \"" + txt + "\" at " + dt + "."
print(server_output) # response to "RDM" command
elif (flag == "RDM"):
FLAG = flag
DATA = ""
dt = recv_msg_list[1]
try:
timestamp = dt_to_timestamp(dt)
except Exception as e:
print(e)
send_msg = FLAG + "%%" + "unknown time, please check time format"
clientSocket.send(send_msg.encode('utf-8'))
return
log_list = read_msg_log(timestamp)
if len(log_list) == 0:
DATA = "no new message"
else:
# print(log_list)
for log in log_list:
log_arr = log.split("; ")
log_edit_status = log_arr[4]
log_index = log_arr[0]
log_name = log_arr[2]
log_txt = log_arr[3]
log_dt = log_arr[1]
if log_edit_status.strip() == "no":
log_edit_status = "posted"
elif log_edit_status.strip() == "yes":
log_edit_status = "edited"
m = "#" + log_index + " " + log_name + ", " + log_txt + ", " + log_edit_status + " at " + log_dt + "%%"
DATA = DATA + m
send_msg = FLAG + "%%" + DATA
#Yoda issued RDM command
server_output = client.m_username + " issued RDM command."
print(server_output) # response to "EDT" command
elif (flag == "EDT"):
FLAG = flag
edit_index = int(recv_msg_list[1])
edit_dt = recv_msg_list[2]
edit_txt = recv_msg_list[3]
edit_res, edit_time = edit_msg_log(client.m_username, edit_index, edit_dt, edit_txt) if edit_res:
TYPE = "0" # del success
DATA = "Message #" + str(edit_index) + " edited at " + edit_time + "."
server_output = client.m_username + " edited MSG #" + str(edit_index) + " \"" + edit_txt + "\" at " + edit_time + "."
print(server_output)
else:
TYPE = "1" # del fail
DATA = "Edit message fail, can not find such message"
server_output = client.m_username + " " + DATA
print(server_output)
send_msg = FLAG + "%%" + TYPE + "%%" + DATA pass # response to "DLT" command
elif (flag == "DLT"):
FLAG = flag
del_index = int(recv_msg_list[1])
del_dt = recv_msg_list[2]
del_res, del_txt = del_msg_log(client.m_username, del_index, del_dt)
del_time = time.strftime("%d %b %Y %H:%M:%S", time.localtime(int(time.time())))
if del_res:
TYPE = "0" # del success
DATA = "Message #" + str(del_index) + " deleted at " + del_time + "."
server_output = client.m_username + " deleted MSG #" + str(
del_index) + " \"" + del_txt + "\" at " + del_time + "."
print(server_output)
else:
TYPE = "1" # del fail
DATA = "Delete message fail, can not find such message"
server_output = client.m_username + " " + DATA
print(server_output) send_msg = FLAG + "%%" + TYPE + "%%" + DATA # response to "ATU" command
elif (flag == "ATU"):
FLAG = flag
DATA = ""
user_list = show_user_log_exclude_self(client.m_username)
NUM = len(user_list)
if len(user_list) == 0:
DATA = "no other active user"
else:
for user in user_list:
user_arr = user.split("; ")
user_dt = user_arr[0]
user_name = user_arr[1]
user_address = user_arr[2]
user_port = user_arr[3]
txt = user_name + ", " + user_address + ", " + user_port.strip() + ", active since " + user_dt + "."
DATA = DATA + txt + '%%'
send_msg = FLAG + "%%" + str(NUM) + "%%" + DATA
server_output = client.m_username + " issued ATU command."
print(server_output) # response to "OUT" command
elif (flag == "OUT"):
del_user_log(client.m_username)
client = get_client_from_connect_list(IP)
FLAG = flag
send_msg = FLAG + "%%" + "Bye, " + client.m_username + "!"
socket_status = False
server_output = client.m_username + " logout"
print(server_output) if send_msg != "":
clientSocket.send(send_msg.encode('utf-8')) if not socket_status:
clientSocket.close() # Try to recvice msg from client
def read_client(socket, IP):
try:
return socket.recv(2048).decode('utf-8')
except:
# if exception, indicating that the connection failed, the client is deleted
print(str(IP) + ' Left!') # Always wait for a message from the client
def recv_handler(socket, clientAddress):
try:
while True:
content = read_client(socket, clientAddress)
if content is None:
break
else:
if __DEBUG: print(str(clientAddress) + " received: " + content) reponse_to_client(socket, clientAddress, content) except Exception as e:
# raise Exception
print("[error]: ", e)
exstr = traceback.format_exc()
if __DEBUG: print("debug: " + exstr)
pass def start(server_port):
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind((serverIP, server_port))
serverSocket.listen(1)
print("Server start ... ")
print("waiting client ...")
while True:
clientSocket, clientAddress = serverSocket.accept() # record each client when server accept successfully
client = ClientInfo(clientAddress)
client_connect_list.append(client)
if __DEBUG: print(str(clientAddress) + ' is trying to connect!') # start recv_handler for each client
threading.Thread(target=recv_handler, args=(clientSocket, clientAddress)).start() if __name__ == '__main__':
# create messagelog.txt
msg_file = open('messagelog.txt', 'w')
msg_file.close()
# create cse_userlog.txt
user_file = open('cse_userlog.txt', 'w')
user_file.close() # python server.py server_port number_of_consecutive_failed_attempts
if len(sys.argv) > 2:
server_port = int(sys.argv[1])
MAX_ERR_COUNT = int(sys.argv[2])
start(server_port)
else:
print("[error] Usage: python server.py server_port number_of_consecutive_failed_attempts ") # just for test
# server_port = 9000
# MAX_ERR_COUNT = 6
# start(server_port)

最新文章

  1. TypeScript为Zepto编写LazyLoad插件
  2. DBUtil数据库连接单例 —— 简单不简单
  3. mysql 编译安装
  4. Robot Framework 的安装和配置(转载)
  5. MySQL ODBC for Linux
  6. Android优化——UI检视利器:Hierarchy Viewer
  7. Marvelous Mazes
  8. Python的包管理工具--PIP安装使用
  9. virtualBox虚拟机安装与主机互访和实现上网配置
  10. unity3d中的Viewport
  11. PHP之ThinkPHP数据操作CURD
  12. LightOJ_1038 Race to 1 Again
  13. hdu2492 Ping pong
  14. 判断iPhone设备是carplay和iPod的方法
  15. 基本排序算法&lt;二&gt;
  16. Node.js UDP/Datagram
  17. PHP7 学习笔记(十六)Yaconf 一个高性能的配置管理扩展
  18. MUI class=&quot;mui-switch&quot; 开关监听
  19. help2man: can&#39;t get `--help&#39; info from automake-1.15 Try `--no-discard-stderr&#39; if option outputs to stderr Makefile:3687: recipe for target &#39;doc/automake-1.15.1&#39; failed
  20. 手机不弹toast解决方法

热门文章

  1. 【MIT6.S081/6.828】手把手教你搭建开发环境
  2. NCNN优化实时面部关键点检测
  3. GDAL 矢量裁剪栅格
  4. nRF52832蓝牙iBeacon广播
  5. Appium自动化(16) - 使用手机浏览器进行自动化测试
  6. 处理器核、Core、处理器、CPU区别&amp;&amp;指令集架构与微架构的区别&amp;&amp;32位与64位指令集架构说明
  7. SpringBoot自定义初始化Bean+HashMap优化策略模式实践
  8. DevExpress Silverlight DXChart特效总结
  9. Ts基本数据类型
  10. 一、java基础补充