摘要

本文阐述了基于Linux环境,Java语言实现的基本聊天室功能,涉及Linux下的Java 语言的Socket编程。以及Java语言的多线程编程。

关键字
Linux         Java                  Thread              Socket              Tcp
简介
开发背景
《 操作系统》和《计算机网络》的学习,使我能够有机会选择“基于Linux的网络聊天室的实现”这个课题项目,使自己课堂所学的理论能够联系实际,并且能够学习自己没有涉及过网络方面以及面向对象的基本思想,而且在编写聊天程序的过程中,也涉及到多线程的程序设计问题,这个概念在《操作系统》中已经学到,但是对于真正的语言应用却是第一次。Java语言的多线程编程提供了很好的基础类,可以很容易实现多线程的调用。以及Java语言的跨平台,使我有机会在Linux下编写程序,当然对于Linux下的Java编程和Windows下的Java编程,没有多少的不同,这些都是Java语言没有平台特有的特征带来的。
 
系统开发环境
Linux正以自由的精神席卷全球网络操作系统市场,而Java凭借其开放、先进的架构正迅速占领着高端软件领域。将这二者结合,便可通过Linux低廉的成本实现Java高级应用,在自由、高效的环境下充分发挥出Java的优势。因此,无论从成本还是性能上考虑,二者的结合都可谓是相得益彰。
例如,现在热门的服务器端脚本JSP的推荐实现就是Linux上的Tomcat,而与Jboss结合更是极佳的EJB平台。但是,Linux之所以未能在桌面应用等领域迅速普及,软件安装和设置复杂是一个重要原因。要在Linux下实现Java编程,其普通的环境设置可能令习惯了Windows的用户望而却步。其实,很多问题只需要简单的设置就能解决。
而对于本次课程项目的开发也正是两者的结合,尽管结合没有发挥各自的精髓,但是也能体会和感受到Java+Linux 的魅力。
技术概要
网络通信基本原理

Ø         TCP (Transmission Control Protocol)基础

数据传输协议允许创建和维护与远程计算机的连接。连接两台计算机就可彼此进行数据传输。如果创建客户应用程序,就必须知道服务器计算机名或者  IP  地址(RemoteHost  属性),还要知道进行 “ 侦听 ” 的端口(RemotePort  属性),然后调用  Connect  方法。如果创建服务器应用程序,就应设置一个收听端口(LocalPort  属性)并调用  Listen  方法。当客户计算机需要连接时就会发生  ConnectionRequest  事件。为了完成连接,可调用  ConnectionRequest  事件内的  Accept  方法。建立连接后,任何一方计算机都可以收发数据。为了发送数据,可调用  SendData  方法。当接收数据时会发生  DataArrival  事件。调用  DataArrival  事件内的  GetData  方法就可获取数据。

Ø         UDP(User Datagram Protocol) 基础

用户数据文报协议  (UDP)  是一个无连接协议。跟  TCP  的操作不同,计算机并不建立连接。另外  UDP  应用程序可以是客户机,也可以是服务器。
为了传输数据,首先要设置客户计算机的  LocalPort  属性。然后,服务器计算机只需将 
RemoteHost  设置为客户计算机的  Internet  地址,并将  RemotePort  属性设置为跟客户计算机的  LocalPort  属性相同的端口,并调用  SendData  方法来着手发送信息。于是,客户计算机使用DataArrival  事件内的  GetData  方法来获取已发送的信息。

Ø         Socket(Java)

套接字方式通信 (socket-based communication) 通过指派套接字实现程序自己的通信。套接字(Socket) 是一种抽象,为服务器和客户之间的通信提供方便。Java处理套接字通信的方式很像处理I/O操作,这样,程序对套接字进行读写就像读写文件一样容易。

Java支持流套接字(steam socket)和数据报套接字(datagram socket)。流套接字使用TCP协议(Transmission Control Protocol, 传输控制协议)进行数据的传输,而数据报套接字使用UDP协议(User Datagram Protocol, 用户数据报协议)。因为TCP能够探测丢失的数据传输并重新提交它们,因此传输的数据不会丢失,是可靠的。相比之下,UDP协议不能保证无损失传输。所以,采用TCP协议通信可以保证数据的正确传输。

Ø         客户/服务器模式

网络聊天室涉及的一个服务器端和N个客户端。客户向服务器发送请求,服务器对请求作出响应。客户尝试与服务器建立连接,服务器可以接受连接也可以拒绝连接。一旦连接建立起来,客户和服务器就可以通过套节字进行通信。
   客户开始工作时,服务器必须正在运行,等待客户的连接请求。创建服务器和客户所需要的语句如图1-1所示。

图1-1 服务器创建一个服务器套接字,与用户的连接一旦建立,就用客户套接字也客户保持连接
要建立服务器,需要创建一个服务器套接字,并把它附加到一个端口上,服务器通过这个端口监听连接请求。端口标识套接字上的TCP服务。编号在0到1023之间的端口用来为特权进程服务。
下面的语句创建一个服务器套接字server:

ServerSocket server = new ServerSocket(port);1

  [1] 创建了一个服务器套接字之后,服务器就能够使用下面的语句监听连接请求:

Socket connectToClient = server.accept();

 这条语句一直等待,直到客户连接到服务器套接字。客户发送下面的语句与服务器建立连接:

Socket connectToServer = new Socket(ServerName, port);

该语句用来打开一个套接字,以便客户程序能够与服务器进行通信。 ServerName是服务气的Internet主机名或IP地址。
服务器接受连接之后,就用与处理 I/O数据流相同的方式建立服务器与客户之间的通信。
要获得输入数据流和输出数据流,可以使用套接字对象中的 getInputStream()和getOutputstream()方法:

InputStream isFromServer = connectToServer.getInputStream();

OutputStream osToServer = connectToServer.getOutputSteam();

InputStream 和 OutputSteam是用户读写字节。可以使用DataInputStream、DataOutputSteam、BufferReader 和 PrintWriter包装InputStream和OutputSteam,读取double、int、String之类的数据值。可以使用readLine方法读入一行数据,使用println向端口写入一行。

多线程技术编程基本原理
一个线程 (Thread) 是指程序中完成一个任务的有始有终的执行流。 Java 语言支持多个线程同时运行。如下图 1-2 :
图1-2 多个线程分享一个CPU
 
多线程可以使程序反应更快、交互性更强,并提高执行效率。 Java 对多线程程序设计提供更好的支持,包括内在地支持创建线程和锁定资源以避免冲突,解决了资源的共享冲突问题。
当程序运行时, Java 解释器为 main 方法开始一个线程。此时可以创建另外的线程。每一个线程都是一个对象,它的类实现 Runnable 接口或者扩展实现了 Runnable 接口的类。也可以通过扩展 Thread 类来实现 Runnable 接口来创建线程。 Thread 事实实现了 Runnable 接口。

Ø         线程有五种状态:新建、就绪、运行、阻塞、结束。

如图 1-3 所示:

图1-3一个线程处于其中的一个状态
à       新创建一个线程的时候,它进入“新建状态”。调用start方法启动线程后,它进入“就绪状态”。就绪态可以通过调用run方法实现到“运行状态”的转移。
à       如果给定的CPU时间用完,或者调用yield()方法可以使线程处于“就绪状态”
à       当线程执行结束,然后其自然应该进入“结束状态”。
à       当线程因为调用sleep()等方法,将进入“阻塞状态”。此状态还可以重新进入“就绪状态”,接着重新得到运行。

Ø         Thread类包括以下的几种控制方法:

public void run()方法,用来执行线程。用户线程类中必须覆盖该方法。
public void start()方法,它引起对run方法的调用。
public void stop()方法,结束一个线程
public void suspend()方法,挂起一个线程 [2]
public void resume()方法,唤醒一个线程 [3]

public static void sleep(long millis) throws InterruptedException 方法,可以将在运行的线程置为休眠状态,休眠时间为指定的毫秒。

public void interrupt()方法,中断正在运行的线程。
public static boolean isInterrupted()方法,测试线程是否被中断。
public boolean isAlive()方法,检查线程是不是处于运行态。
public void setPriority(int p)方法,设置方法的优先级。从1~10
public final void wait() throws InterruptedException 方法,将该线程置为暂停状态,等待另外一个线程的通知。
public final void notify()方法,唤醒一个等待的线程。
Linux下Java编程
本次课题项目采用的环境是:

¨         Linux Platform - J2SE(TM) and NetBeans(TM) IDE Bundle NB 4.1 / J2SE 5.0 Update 4 [4]

¨         Rat Hat Linux 9.0

在Linux采用J2SE和NetBeans可以很容易的开发面向Linux的应用程序,可以移植到windows平台下运行。其中NetBean 是Sun公司开发的免费Java图形用户界面编辑器。(如图1-4)可以很轻松的实现界面的设计,它将控件以Swing, awt, JavaBean分类放置。

集成 Tomcat 5.0 加入对 XML,Structs 的支持。这其中感触最深的地方是,不能修改自动生成的组件初始代码。假如要用 ButtonGroup 就得自己去另写一个函数来初始化。感觉这样做,因为不能随意修改代码,能避免随意修改所导致的错误。但是,很多时候,我们真的要修改那部分代码反倒是件很麻烦的事了。
对于 window 平台有个比较不当的地方就是内存消耗太大。硬盘频繁访问。在 Linux 环境可以明显感觉到速度快了不少!

图1-4 windows 平台下的NetBeans运行界面 [5]
基于Linux的网络聊天室的具体实现
服务器端和客户端体系结构
根据通信的基本原理,不难分析服务器端与客户端的通信实现,以下是客户端和服务器端的交互流程,如图 1-5

图1-5 客户端和服务器端的基本流程
流程图的简要描述
Server 和 Client 端通信主要是服务器端创建多个线程,生成多个 Socket 对不同的用户进行通信。服务器端和客户端通过消息命令字的方式进行消息确认。方式是在消息头加入“命令字”。
自定义命令字含义如下:
[MESSAGE] :表示接下来的一句话是消息
[NAME]:   表示接下来的一句话是名字
[FIRSTNAME] :用于程序逻辑控制,表示第一个
[SYSTEM]: 系统消息                                                
[Server exit!] :   服务器退出
[WHISPERMESSAGE] :私聊控制字
[QUIT] :      表示客户端退出聊天室
客户端:
客户端由两个类实现,一个是主类 ClientJFrame 另外一个是用于播放声音的 PlaySound 类。一下是客户端的类关系图,图 1-6 :

图1-6 Client端两个类,ClientJFrame中创建PlaySound实例来播放声音

Ø

public void startConnect()
{
try
{

sock = new Socket(ipAddress, DEFAULT_PORT); //新建一个socket

if(sock!=null)                                                                                            //连接成功

{

processMsg();

}

isFromServer=new BufferedReader();              //新建一个接收变量

osToServer = new PrintWriter();                                              //新建一个输出变量

osToServer.println("[NAME]" + name);                       //发名字

osToServer.flush();                                                                               //刷新数据缓冲
}

catch(IOException ex)

{
}

readThread=new Thread(this);                                                 //通过Runnable实现

readThread.start();

}

Client端通过方法startConnect(),尝试连接服务器端,其函数原型如下:

Ø         通过SendInformation()方法实现数据的发送,实现函数原型如下:

public void sendInformation()
{
if(私聊)
{

osToServer.println("[WHISPERMESSAGE]" + message);//发送私聊信息

osToServer.flush();
}
else
{

osToServer.println("[MESSAGE]" + message);                   //发送群聊信息

osToServer.flush();

}
}

通过调用prinln()方法可以向端口写一句消息。

Ø             当程序退出,或者服务器退出,线程应该结束运行。客户端,通过重载Thread的run方法实现,函数的原型如下:

public void exit()
{
try
{
osToServer.println("[QUIT]");   //向服务器发送退出命令字

osToServer.flush();

}

catch(Exception exc){}

try
{
sock.close();                                                 //关闭socket

isFromServer.close();          //输入缓冲关闭

osToServer.close();            //输出缓冲关闭

}
catch(IOException ioe){}
finally                                                                          //不管异常是否发生都将执行
{

System.exit(0);

}
}  

à       在退出应用程序之前,向服务器端发送[QUIT]命令字,实现聊天室的更新。

à       客户端退出要将socket关闭,要将输入和输出的数据流关闭。

à       最后将执行finally块程序,最后调用System的exit的函数退出应用程序。

服务器端
服务器端主要创建了 5 个类,其中 ServerJFrame 是主类, CommunicateThread 用于连接客户端, BroadcastThread 用与对消息的广播, WhisperThread 用于处理悄悄话, BroadcastName 用户广播当前在线的用户。图 1-7 显示了类中的方法和类的属性。

图1-7 Server端的类视图

Ø             Server相对与客户端更加的复杂,要主动监听客户端发送的连接请求,创建不同的线程,来应答客户的请求。创建的线程,接受客户发送的数据的处理。

Server创建了连接线程后,还必须创建广播线程,将每一个客户发送的消息广播出去,到每一个客户端。对于广播线程和数据接受和处理线程之间的资源共享问题,我采用了,有序运行的方法来消除死锁。即在用户发送来数据时,开启广播线程,对刚才的数据进行广播,在信息广播结束后,关闭广播线程。这样一前一后,就可以保证数据广播的正确性。
在server还需要处理的一件事,就是如何将私聊信息发送给指定的客户端。我采用的方式是,“用户名查找发送法”,可以比较快而准确的发送数据 [6] ,为了和群聊消息区分,我采用WhisperThread类单独给予处理。这样可以清晰的区分。
在客户端,还需要处理的一件事情就是,管理当前的在线用户。我使用一个堆栈 [7] 来管理用户,当用户来到时,就将用户名压入堆栈。当用户退出时,将用户的名字从中去除 [8] 。
      

Ø             服务端使用serverListen()函数开始监听端口,其函数原型如下:

private void serverListen()
{

chatAcceptThread = new Thread(this);                    //创建一个监听线程

chatAcceptThread.start();

broadcastThread = new BroadcastThread(this); //创建广播线程

broadcastThread.start();

}

      
      
      
      
      
      
      
      
      
      

Ø             ServerJFrame是通过Runnable接口来创建的线程。其run函数实现客户端的接受并创建一个新的线程:

public void run()
{

clients = new java.util.Vector();                //分配一个栈,用于存储用户线程

clientsInfor = new java.util.Vector();  //用于存储用户名
try
{

serverSock=new ServerSocket(DEFAULT_PORT); //新建一个Socket

}

catch(IOException e){}

try
{

while(true)

{

Socket clientSock = serverSock.accept();

CommunicateThread ct = new CommunicateThread(); //实例化通信类

boolean addSucOrNot = clients.add(ct);            //将当前的进程压入堆栈

}
}

catch(IOException e){}

}

      
      
      
      
      
在run函数中有一个while(true)程序块,表示这个进程将随应用进程一起存在,所以服务器端将不停的监听端口,当一个连接进入的时候,就通过CommunicateThread来处理当前连接的进程(客户端),然后,服务器主线程将监听下一个连接。

为了后面能够将数据广播出去,和实现私聊,必须要得到响应的线程,所以在向堆栈压入线程的时候,需要有一个变量(index)来指示线程, index 不会随着客户的退出而删除 [9] ,而是逐次累加,那么当客户退出时,要将此进程在堆栈中的位置设置为[EMPTY],来表示一个客户端已经退出,此时,服务器端要结束和这个客户端连接的线程。

Ø             那么当用户发来数据的时候,并且命令字为[MESSAGE]的时候,服务器需要将这条信息广播出去,这个由Broadcast来处理,其中的run函数原型如下:

public void run()
{
try
{
while(true)
{               
//若消息堆栈为空,或者没有当前信息需要发送

boolean startBroadcast = chatFrame2.getBroadcastStart();

if (!startBroadcast)

{
try

Thread.sleep(500);                // 线程睡眠500毫秒后重新检测

catch(InterruptedException ex){}

continue;

}

int lengthOfChatClients = chatClients.size();          //线程个数

for(int i=0; i < lengthOfChatClients; i++)   //对每个线程进行操作

{

if(chatClients.get(i).equals("[EMPTY]")) //若是退出进程

continue;

comThread1 = (CommunicateThread)chatClients.get(i);

msgStack = comThread1.inforStack;

int lengthOfMsgStack = msgStack.size(); //对消息堆栈进行广播

for(int j=0; j<lengthOfMsgStack; j++)

{

string = (String)msgStack.get(j);

broadcastInfor = string;

broadcast("[MESSAGE]" + broadcastInfor);

boolean temp = msgStack.removeElement(string);

}
}
try
{

chatFrame2.stopBroadcast();                        //停止广播

Thread.sleep(1000);

}

catch(InterruptedException ex) {}

}
}

catch(Exception e){}

}

广播线程,主要是和接受消息线程达到同步,通过共享内存堆栈来实现数据的交互。然后广播线程需要解决的问题就是如何实现线程的数据广播,这是使用的是,调用创建的线程,然后调用CommunicateThread的通信进行数据的广播。其中名字的广播也是同样的一个道理。
执行效果演示
演示效果如下图所示 ( 图 1-8)

图1-8是windows下的运行效果,充分说明在Linux下开发的应用程序的可移植性,在Windows下运行无阻

Ø         程序可以实现公聊和私聊 [10] ,公聊在服务器端将加入聊天记录,私聊则只是发给指定用户,服务器端不保留聊天信息。

Ø         收到系统消息,和用户变化都会有声音提示。

Ø         完全可以单机来调试信息,也试过在Linux下运行服务器端,在Windows下使用客户端进行访问,访问方式没有区别,通信也没有故障。

Ø         当服务器退出时,或者说用户端失去服务器连接时,用户将需要重新连接,当然也可以实现超时退出的方式,这样可以实现重新连接。

Ø         可扩展功能:系统可以选择需要发送的系统消息的对象,这样可以使系统消息发送更加灵活。

Ø         用户可以通过右边的list得到当前的在线用户的状况

Ø         用户可以通过左边的textArea得到当前群中用户所发送的消息的记录 [11] 。

Ø         当用户连接失败,可以选择重新登陆,重新登陆就不需要重新输入用户名。

Ø         假如用户登陆时,没有指定连接地址,将默认为localhost地址 [12] 。

Ø         用户可以通过直接按Enter键发送消息 [13] 。

总结
经过一个星期的编码,基本完成了课题任务。从中也学到了不少的东西,锻炼了自己的独立开发能力。其中,对 Java 语言也有了一定的了解,也被 Java 语言的强大类库所折服,以及 Java 环境提供的规范语言所欣喜。正因为有这样优秀的语言,和优秀的类库使得这次的任务能顺利的完成。
从中让我深有体会的是, Java 的多线程编程。让我真正有机会接触多线程的编程,而 Java 语言的强大也使得这样的一个过程,不是非常的艰难。 Java 多线程编程,一般采用继承 Thread 类或者采用 Runnable 接口来实现。
Windows 平台和 Linux 平台对于 Java 语言,不同的只是虚拟机,对于程序,对于编码没有区别,这也是能让我顺利完成 Linux 平台的应用程序的一个保证。
通过书写这篇文档,我也从中琢磨了许多的东西,如 Rose , UML 等面向对象实现概念,通过尝试也学习了其中工具带来的方便。
参考资料
以下是开发过程中参考过的资料,其中有网页模式,其中有课本,以及有用的信息。

m        Ineroduction to Java Programming Third Editon :     Y.Daniel Liang

m        Linux Platform - J2SE(TM) and NetBeans(TM) IDE : http://java.sun.com

m        Java sockets 101:             http://www.ibm.com/developerWorks

m        Building a Java chat server:     http://www.ibm.com/developerWorks

m        Beej网络socket编程指南:    http://www.ecst.csuchico.edu/%7Ebeej/guide/net/

m        UML参考手册 :             James Rumbaugh


[1] 如果试图在被占用的端口上创建一个服务器套接字,将引起java.net.BindException 实时错误

[2] 该方法可以引起死锁。
[3] 唤醒线程一般不使用该方法,而是采用notify()方法加布尔变量来指明线程是否唤醒。

[4] Website: https://jsecom15d.sun.com/ECom/EComActionServlet;jsessionid=6596D10F28E751B8FD7981BCCB5E02DA#http://192.18.97.252/ECom/EComTicketServlet/BEGIN6596D10F28E751B8FD7981BCCB5E02DA/-2147483648/957453423/1/626894/626858/957453423/2ts+/westCoastFSEND/jdk-1.5.0_04-nb-4.1-oth-JPR/jdk-1.5.0_04-nb-4.1-oth-JPR:2/jdk-1_5_0_04-nb-4_1-linux.bin

[5] 图中所示的界面是Windows平台的界面效果,Linux(Rad hat Linux 9.0)下的执行界面的布局方式是大致相同的。

[6] 这里有个约定,就是用户名应该是唯一的。
[7] 其实是Java的向量类(Vector),可以动态的调整大小,使用Java提供的函数可以很好实现数据的输入保存和输出得到

[8] 在实现用,我并没有这样做,而是将其赋离线常量(String), 这样的目的,在后面将叙述到

[9] 这样做的目的,是为了处理简单,当然在某些时候也是需要这样处理的
[10] 可以实现多人私聊,可以将你的信息,发给你要想让看到的人。而不用发给全部
[11] 若功能再扩展,可以实现将聊天的历史记录实时保存
[12] 本地的调试地址
[13] 这是通过调用textArea的键盘按键事件来实现的

网络聊天室(Java)相关推荐

  1. 视频教程-网络聊天室Java基础版(Socket_Swing编程)仿QQ聊天-Java

    网络聊天室Java基础版(Socket_Swing编程)仿QQ聊天 IT行业资深从业者,7年资深Java高级开发,Java架构师.曾就职银行.电信等行业多家上市公司.担任项目负责人,软件架构师.有丰富 ...

  2. 基于Java socket的网络聊天室的设计与实现

    目 录 摘要 I Abstract II 目 录 III 1 引言 1 2 网络聊天室的简介 2 2.1网络聊天室现状和发展 2 3 完成网络聊天室的技术以及环境 4 3.1 Java的介绍 4 3. ...

  3. 网络聊天室(linux,java,Android)

    如果追忆会荡起涟漪,那么今天的秋红落叶和晴空万里都归你 艾恩凝 个人博客 https://aeneag.xyz/ 前几天在他人那里看到了网络聊天室的文章,想起了自己几年前也认认真真写过相关编程,实现了 ...

  4. java聊天室测试_Java网络聊天室实训能力测试

    1填空题(2*12=24,每空2分).1)C/S结构是指___客户机/服务器_____,请例举除网络聊天室之外的基于C/S结构的应用程序____________________.2)套接字(socke ...

  5. 【完整代码及文档】基于Java的网络聊天室系统的设计与实现

    摘 要 计算机从出现到现在有了飞速的发展,现阶段的计算机已经不单单是用于进行运算的独立的个体了,跟随计算机一同发展的还有互联网技术,经过了长久的发展,互联网技术有了日新月异的发展,它的发展速度和计算机 ...

  6. Java网络聊天室---个人博客

    Java网络聊天室 ---个人博客 一.项目简介 功能描述: 使用图形用户界面和socket通信,能实现一个聊天室中多人聊天,可以两人私聊,可以发送文件. 实现类似QQ用户注册.登录.聊天等功能. 参 ...

  7. 视频教程-Java基础中国象棋和网络聊天室Swing开发-Java

    Java基础中国象棋和网络聊天室Swing开发 IT行业资深从业者,7年资深Java高级开发,Java架构师.曾就职银行.电信等行业多家上市公司.担任项目负责人,软件架构师.有丰富的高并发.分布式系统 ...

  8. [源码和文档分享]基于java语言的C/S模式网络聊天室软件

    一 需求分析 采用C/S模式,基于TCP协议编程的方式,使得各个用户通过服务器转发实现聊天的功能 分为三大模块:客户端模块.服务器端模块和公共辅助类模块 客户端模块的主要功能: 登陆功能:用户可以注册 ...

  9. 小浩的JAVA网络聊天室

    案例:在线聊天室 需求:使用TCP的Socket实现一个聊天室 服务器端:一个线程专门发送消息,一个线程专门接收消息 客户端:一个线程专门发送消息,一个线程专门接收消息 实现:具有 注册 登录 功能的 ...

最新文章

  1. css的几种使用方式有哪些,引入CSS的方式有几种?
  2. 发送Request Payload数据演示
  3. pandas使用query函数查询dataframe指定数据列的内容(数值)不包含在特定列表中的数据行(not contain in list)
  4. php directoryiterator,PHP DirectoryIterator getBasename()用法及代码示例
  5. 进阶的“车厘子自由”,进化的“淘宝特价版”
  6. Effective C# 第二章:.Net资源管理(翻译)
  7. 高通量数据中批次效应的鉴定和处理(二)
  8. 新手学Python, 如何从入门到入土变为从入门到快速上车?
  9. python2 使用matplotlib
  10. 《UNIX网络编程 卷2:进程间通信(第2版)》——1.4 名字空间
  11. 2021东华杯misc详解
  12. Python代码缩进
  13. java实现一个录像大师
  14. 离散数学耿素云计算机,离散数学,屈婉玲,耿素云,张立昂编著_考研教材_考试点...
  15. 无法获得下列许可solidworks standard无效的(不一致的)使用许可号码(-8,544,0) solidworks2020 (亲测有效)
  16. 学习继电器的工作原理及作用
  17. TrustedInstaller
  18. js身份证号校验方法(转载我的请注明哈哈)
  19. 1905. 统计子岛屿
  20. C语言C++编程学习:排序原理分析

热门文章

  1. Java线程池的知识
  2. 假面舞会狂欢节·圆桌 | 当Thinker遇上Artist
  3. BUUCTF——rsa系列(2)
  4. 输入身份证号和年份计算年龄
  5. 夸克链创始人周期:把区块链的好处带给千家万户
  6. Environment Variables
  7. 云服务器搭建配置以及服务器开发相关
  8. 计算机网络三级考的是什么,三级网络技术都考什么东西?
  9. 在嵌入式板子ARMv7 上利用neon对彩色图转换为灰度图进行加速
  10. chatbot使用_如何使用Python构建Chatbot项目