可行性分析

(1)操作可行性

聊天室是提供给网民一个交友与娱乐的场所。在聊天室里,网民可选择自己的聊天对象,与其进行对话交流,是网民之间相互沟通、交流情感的最佳方式之一。

利用聊天室,用户可以通过网络在线与距离遥远的其他一位或多位用户进行信息交流。既可以一对一,又可以一对多,还可以形成小组进行多对多讨论,方便网民在线私聊与群聊,是现行网络最流行、最被广泛应用的通讯工具。

(2)技术可行性

  • 采用C/S模式,基于Socket编程的方式,使得各个用户通过服务器转发实现聊天的功能;

  • 分为两大模块:客户端模块和服务器端模块;

  • 客户端模块的主要功能

    • 登陆功能:用户可以注册,然后选择服务器登入聊天室;
    • 显示用户:将在线用户显示在列表中;
    • 接收信息:能接收其他用户发出的信息;
    • 发送信息:能发出用户要发出的信息;
    • 自定义颜色:可以个性化字体颜色;
    • 退出功能:用户离线,进行通知;
    • 连接设置:显示连接的IP地址和端口号。
  • 服务器端模块的主要功能

    • 检验登陆信息:检查登陆信息是否正确,并向客户端返回登陆信息,如信息正确,就允许用户登陆;
    • 显示在线状态:将该用户的状态发给各在线用户;
    • 转发聊天信息:将消息转发给所有在线的用户;
    • 端口服务:输入要侦听的端口,默认为9590;
    • 启动服务:启动服务器,并开始在设置的端口中侦听,客户端用户可以登录并开始聊天;
    • 停止服务:关闭服务器,侦听结束。客户端用户不能再聊天;
    • 服务器IP:输入要监听的IP;
    • 退出服务器:退出程序,并停止服务。

(3)经济可行性

  • 成本方面:本系统主要在实验室开发,相应资料已具备,开发成本和运行成本能够满足。

  • 效益方面:人们日常生活中越来越多地使用聊天室这项应用来为自己的工作和学习服务。一个操作简单,界面友好、运行稳定的聊天室,对于小型局域网的用户可以起到很好的交流作用。

设计原理

聊天协议的应答

  • 聊天状态:CLOSED和CONNECTED状态

  • 执行CONN命令后进入CONNECTED状态,执行下列命令:

    • CONN:连接聊天室服务器
    • JOIN:加入聊天(通知其他用户本人已经加入聊天室服务器)
    • LIST:列出所有的用户(向客户端发送全部的登录用户名字)
    • CHAT:发送聊天信息(公开的聊天信息)
    • PRIV:进行私聊(三个参数:私聊信息用户;接收私聊信息用户;发送信息)
    • EXIT:客户端向服务器发送离开请求;
    • QUIT:退出聊天,服务器向客户端发送退出命令(执行QUIT命令聊天状态变为CLOSED)。

服务器协议解析

当有客户端连接聊天室服务器后,服务器立刻为这个客户建立一个数据接收的线程(多用户程序必备)。在接收线程中,如果收到聊天命令,就对其进行解析处理,服务器可以处理五种命令:CONN\LIST\CHAT\PRIV\EXIT。

服务器接收到CONN命令,就向其他用户发送JOIN命令告诉有用户加入,然后把当前的全部用户信息返回给刚刚加入的用户,以便在界面上显示用户列表。当接收到EXIT命令后,就清除当前用户的信息,然后向其他用户发送QUIT命令,告诉其他用户退出了,这些用户的客户端把离开的用户从用户列表中删除。

客户端协议解析

当客户端连接到服务器后,服务器立刻建立一个数据接收的独立线程。在接收线程中,如果收到了聊天命令,就对其进行解析处理。客户端一共处理的命令有五种:OK\ERR\LIST\JOIN\QUIT命令。

具体实现

编写实体Bean

编写一个独立的类即Client类,封装了客户端的信息与连接,每一个客户进入聊天室,就创建一个Client对象,用于保存该用户的信息并接收用户数据和发送信息到客户端

Client.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
namespace Server
{public class Client{private string name;//保存用户名private Socket currentSocket = null;//保存与当前用户连接的Socket对象private string ipAddress;//保存用户的IP地址private FrmServer server;//保留当前连接的状态://closed --> connected --> closedprivate string state = "closed";public Client(FrmServer server, Socket clientSocket){this.server = server;this.currentSocket = clientSocket;ipAddress = getRemoteIPAddress();}//获得连接客户端的IP地址,并转换成一个字符串private string getRemoteIPAddress(){return ((IPEndPoint)currentSocket.RemoteEndPoint).Address.ToString();}//306.客户端线程方法,每连入一个客户端,会启动这个方法public string Name{get{return name;}set{name = value;}}public Socket CurrentSocket{get{return currentSocket;}set{currentSocket = value;}}//ServiceClient()函数为服务器接收客户数据的线程主体,主要用来接收用户发送来的数据,并处理聊天命令public void ServiceClient(){string[] tokens = null;byte[] buff = new byte[1024];bool KeepConnect = true;//用循环来不断的与客户端交互,直到客户端发出EXIT命令while (FrmServer.ServiceFlag && KeepConnect){tokens = null;try{//如果没有信息达到服务器端,则休息if (currentSocket == null || currentSocket.Available < 1){Thread.Sleep(200);continue;}//接收数据并存入buff数组中int len = currentSocket.Receive(buff);//将字符数组转化为字符串string clientCommand = System.Text.Encoding.Default.GetString(buff, 0, len);//tokens[0]中保存了命令标志符(CONN、CHAT、PRIV、LIST或EXIT)tokens = clientCommand.Split(new Char[] { '|' });if (tokens == null){Thread.Sleep(150);continue;}}catch (Exception ex){server.updateUI("发生异常:" + ex.Message);}if (tokens[0] == "CONN"){//此时接收到的命令格式为://命令标志符(CONN)|发送者的用户名|,//tokens[1]中保存了发送者的用户名this.name = tokens[1];if (FrmServer.clients.Contains(this.name)){SendToClient(this, "ERR|User " + this.name + " 已经存在");}else{Hashtable syncClients = Hashtable.Synchronized(FrmServer.clients);syncClients.Add(this.name, this);//更新界面server.AddUser(this.name);//对每一个当前在线的用户发送JOIN消息命令和LIST消息命令,//以此来更新客户端的当前在线用户列表System.Collections.IEnumerator myEnumerator =FrmServer.clients.Values.GetEnumerator();while (myEnumerator.MoveNext()){Client client = (Client)myEnumerator.Current;SendToClient(client, "JOIN|" + tokens[1] + "|");Thread.Sleep(100);}//更新状态state = "connected";SendToClient(this, "ok");//向客户端发送LIST命令,以此更新客户端的当前在线用户列表string msgUsers = "LIST|" + server.GetUserList();SendToClient(this, msgUsers);}}else if (tokens[0] == "LIST"){if (state == "connnected"){//向客户端发送LIST命令,以此更新客户端的当前在线用户列表string msgUsers = "LIST|" + server.GetUserList();SendToClient(this, msgUsers);}else{//send err to serverSendToClient(this, "ERR|state error,Please login first");}}else if (tokens[0] == "CHAT"){if (state == "connected"){//此时接收到的命令的格式为://命令标志符(CHAT)|发送者的用户名:发送内容|//向所有当前在线的用户转发此信息System.Collections.IEnumerator myEnumerator =FrmServer.clients.Values.GetEnumerator();while (myEnumerator.MoveNext()){Client client = (Client)myEnumerator.Current;//将“发送者的用户名:发送内容”转发给用户SendToClient(client, tokens[1]);}server.updateUI(tokens[1]);}else{//send err to serverSendToClient(this, "ERR|state error,Please login first");}}else if (tokens[0] == "PRIV"){if (state == "connected"){//此时接收到的命令格式为://命令标志符(PRIV)|发送者用户名|接收者用户名|发送内容|//tokens[1]中保存了发送者的用户名string sender = tokens[1];//tokens[2]中保存了接收者的用户名string receiver = tokens[2];//tokens[3]中保存了发送的内容string content = tokens[3];string message = sender + " ---> " + receiver + ":  " + content;//仅将信息转发给发送者和接收者if (FrmServer.clients.Contains(sender)){SendToClient((Client)FrmServer.clients[sender], message);}if (FrmServer.clients.Contains(receiver)){SendToClient((Client)FrmServer.clients[receiver], message);}server.updateUI(message);}else{//send err to serverSendToClient(this, "ERR|state error,Please login first");}}else if (tokens[0] == "EXIT"){//此时接收到的命令的格式为:命令标志符(EXIT)|发送者的用户名//向所有当前在线的用户发送该用户已离开的信息if (FrmServer.clients.Contains(tokens[1])){Client client = (Client)FrmServer.clients[tokens[1]];//将该用户对应的Client对象从clients中删除Hashtable syncClients = Hashtable.Synchronized(FrmServer.clients);syncClients.Remove(client.name);server.RemoveUser(client.name);//向客户端发送QUIT命令string message = "QUIT|" + tokens[1];System.Collections.IEnumerator myEnumerator =FrmServer.clients.Values.GetEnumerator();while (myEnumerator.MoveNext()){Client c = (Client)myEnumerator.Current;SendToClient(c, message);}}//退出当前线程break;}Thread.Sleep(200);}}//SendToClient()方法实现了向客户端发送命令请求的功能private void SendToClient(Client client, string msg){System.Byte[] message = System.Text.Encoding.Default.GetBytes(msg.ToCharArray());client.CurrentSocket.Send(message, message.Length, 0);}}
}

聊天室服务器端设计

使用Label,TextBox,Button,ListBox,RichTextBox等控件完成服务器端UI

启动服务之前,我们要先对服务端IP和端口号进行设置,设置完成后点击启动服务按钮,对已设置端口进行监听

当服务使用完成后,点击结束服务按钮,停止对端口的监听

界面和功能实现FrmServer.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Windows.Forms;namespace Server
{public partial class FrmServer : Form{internal static Hashtable clients = new Hashtable();//clients数组保存当前在线用户的Client对象private TcpListener listener;//该服务器默认的监听端口号static int MaxNum = 100;//服务器可以支持的客户端最大连接数internal static bool ServiceFlag = false;//开始服务的标志public FrmServer(){InitializeComponent();CheckForIllegalCrossThreadCalls = false;}//服务器监听的端口号通过getValidPort()函数获得private int getValidPort(string port){int lport;//测试端口号是否有效try{//是否为空if (port == ""){throw new ArgumentException("端口号为空,不能启动服务器");}lport = System.Convert.ToInt32(port);}catch (Exception e){Console.WriteLine("无效的端口号:" + e.ToString());this.rtbMessage.AppendText("无效的端口号:" + e.ToString() + "\n");return -1;}return lport;}private void FrmServer_Load(object sender, EventArgs e){string strHostName = Dns.GetHostName();IPAddress strAddress = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];this.txtIPAddress.Text = strAddress.ToString();}private void btnStart_Click(object sender, EventArgs e){//确认端口号是有效的,根据TCP协议,范围应该在-65535之间int port = getValidPort(txtPort.Text);if (port < 0){return;}string ip = txtIPAddress.Text;try{IPAddress ipAdd = IPAddress.Parse(ip);listener = new TcpListener(ipAdd, port);//创建服务器套接字listener.Start(); //开始监听服务器端口this.rtbMessage.Text = "";this.rtbMessage.AppendText("Socket服务器已经启动!\n正在监听"+ ip+ "\n端口号:" + this.txtPort.Text + "\n");//启动一个新的线程,执行方法this.StartSocketListen,//以便在一个独立的进程中执行确认与客户端Socket连接的操作FrmServer.ServiceFlag = true;Thread thread = new Thread(new ThreadStart(this.StartSocketListen));thread.Start();this.btnStart.Enabled = false;this.btnStop.Enabled = true;}catch (Exception ex){this.rtbMessage.AppendText(ex.Message.ToString() + "\n");}}//在新的线程中的操作,它主要用于当接收到一个客户端请求时,确认与客户端的链接//并且立刻启动一个新的线程来处理和该客户端的信息交互private void StartSocketListen(){while (FrmServer.ServiceFlag){try{if (listener.Pending()){Socket socket = listener.AcceptSocket();if (clients.Count >= MaxNum){this.rtbMessage.AppendText("已经达到了最大连接数:" + MaxNum + ",拒绝新的连接\n");socket.Close();}else{//启动一个新的线程,执行方法this.ServiceClient,处理用户相应的请求Client client = new Client(this, socket);Thread clientService = new Thread(new ThreadStart(client.ServiceClient));clientService.Start();}}//这句话能使系统性能大大提高Thread.Sleep(200);}catch (Exception ex){this.rtbMessage.AppendText(ex.Message.ToString() + "\n");}}}private void txtPort_TextChanged(object sender, EventArgs e){if (this.txtPort.Text != ""){this.btnStart.Enabled = true;}}private void btnStop_Click(object sender, EventArgs e){FrmServer.ServiceFlag = false;Thread.Sleep(300);rtbMessage.Text += txtIPAddress.Text + "的服务已经停止!" + "\r\n";//204. 控制按钮的可用性this.btnStart.Enabled = true;this.btnStop.Enabled = false;}public void AddUser(string username){this.rtbMessage.AppendText(username + "已经加入\n");//将刚连接的用户加入到当前在向用户列表中this.userlist.Items.Add(username);this.usernum.Text = Convert.ToString(clients.Count);}public void RemoveUser(string username){this.rtbMessage.AppendText(username + "已经离开\n");//将刚连接的用户加入到当前在向用户列表中this.userlist.Items.Remove(username);this.usernum.Text = Convert.ToString(clients.Count);}public string GetUserList(){string rtn = "";for (int i = 0; i < userlist.Items.Count; i++){rtn += userlist.Items[i].ToString() + "";}return rtn;}public void updateUI(string msg){this.rtbMessage.AppendText(msg + "\n");}private void FrmServer_FormClosing(object sender, FormClosingEventArgs e){FrmServer.ServiceFlag = false;}}
}

聊天室客户端设计

客户端主要完成界面、聊天和传输文件功能的设计,用到以下控件:Label,TextBox,Button,ListBox,RichTextBox,OpenFileDialog,CheckBox。

连接、登陆以前,要对服务器IP、端口号和用户名进行设置, 为调试方便,窗体的Load事件将服务器IP设置为本机IP并对端口6666进行监听,一旦有其他客户端向本客户端发送消息可以选择接收。

接下来就可以进行对话了,在下面的TextBox中输入要发送的信息,点击发送对所有在线人发送信息,若选择私聊CheckBox和在线用户的用户名,则是仅对该用户发送信息,为了体现个性化,系统增加了改变字体颜色的功能。

观察服务器端消息转发情况

界面和功能实现FrmClient.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace Client
{public partial class FrmClient : Form{TcpClient tcpClient;//与服务器的连接private NetworkStream Stream;//与服务器交互的流通道private static string CLOSED = "closed";private static string CONNECTED = "connected";private string state = "closed";private bool stopFlag;private Color color;//保存当前客户端显示的颜色public FrmClient(){InitializeComponent();CheckForIllegalCrossThreadCalls = false;}private void btnLogin_Click(object sender, EventArgs e){if (state == CONNECTED)return;if (this.username.TextLength == 0){MessageBox.Show(" 请输入你的昵称!", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);this.username.Focus();//为控件设置焦点return;}try{//创建一个客户端套接字,他是Login的一个公共属性tcpClient = new TcpClient();tcpClient.Connect(IPAddress.Parse(txtHost.Text), Int32.Parse(txtPort.Text));//向指定的IP地址服务器发出连接请求Stream = tcpClient.GetStream(); //获得与服务器数据交互的流通道 NetworksStream//启动一个新的线程,执行方法this.ServerResponse(),以便来响应从服务器发回的信息Thread thread1 = new Thread(new ThreadStart(this.ServerResponse));thread1.Start();//向服务器发送CONN请求命令//此命令的格式与服务器端的定义的格式一致//命令格式为:命令标志符CONN|发送者的用户名string cmd = "CONN|" + this.username.Text + "|";//将字符串转化为字符数组Byte[] outbytes = System.Text.Encoding.Default.GetBytes(cmd.ToCharArray());Stream.Write(outbytes, 0, outbytes.Length);}catch (Exception ex){MessageBox.Show(ex.Message);}}private void btnSend_Click(object sender, EventArgs e){try{if (!this.cbPrivate.Checked){//此时命令的格式是:命令标识符CHAT|发送者的用户名:发送内容|string message = "CHAT|" + this.username.Text + ":" + tbSendContent.Text;tbSendContent.Text = "";tbSendContent.Focus();byte[] outbytes = System.Text.Encoding.Default.GetBytes(message.ToCharArray());    //将字符串转化为字符数组Stream.Write(outbytes, 0, outbytes.Length);}else{if (lstUsers.SelectedIndex == -1){MessageBox.Show("请在列表中选择一个用户", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);return;}string receiver = lstUsers.SelectedItem.ToString();//消息的格式是:命令标识符PRIV|发送者的用户名|接收者的用户名|发送内容string message = "PRIV|{" + this.username.Text + "|" + receiver + "|" + tbSendContent.Text + "|";tbSendContent.Text = "";tbSendContent.Focus();byte[] outbytes = System.Text.Encoding.Default.GetBytes(message.ToCharArray());   //将字符串转化为字符数组Stream.Write(outbytes, 0, outbytes.Length);}}catch{this.rtbMsg.AppendText("网络发生错误!");}}//this.ServerResponse()方法用于接收从服务器发回的信息,根据不同的命令,执行相应的操作private void ServerResponse(){//定义一个byte数组,用于接收从服务器端发来的数据//每次所能接受的数据包的最大长度为1024个字节byte[] buff = new byte[1024];string msg;int len;try{if (Stream.CanRead == false){return;}stopFlag = false;while (!stopFlag){//从流中得到数据,并存入到buff字符数组中len = Stream.Read(buff, 0, buff.Length);if (len < 1){Thread.Sleep(500);continue;}//将字符数组转化为字符串msg = System.Text.Encoding.Default.GetString(buff, 0, len);msg.Trim();string[] tokens = msg.Split(new char[] { '|' });//tokens[0]中保存了命令标志符LIST JOIN QUITif (tokens[0].ToUpper() == "OK"){//处理响应add("命令执行成功!");}else if (tokens[0].ToUpper() == "ERR"){add("命令执行错误:" + tokens[1]);}else if (tokens[0] == "LIST"){//此时从服务器返回的消息格式:命令标志符LIST|用户名1|用户名2|。。(所有在线用户名)//add(“获得用户列表”),更新在线用户列表lstUsers.Items.Clear();for (int i = 1; i < tokens.Length - 1; i++){lstUsers.Items.Add(tokens[i].Trim());}}else if (tokens[0] == "JOIN"){//此时从服务器返回的消息格式:命令标志符JOIN| 刚刚登入的用户名add(tokens[1] + "已经进入了聊天室");this.lstUsers.Items.Add(tokens[1]);if (this.username.Text == tokens[1]){this.state = CONNECTED;}}else if (tokens[0] == "QUIT"){if (this.lstUsers.Items.IndexOf(tokens[1]) > -1){this.lstUsers.Items.Remove(tokens[1]);}add("用户:" + tokens[1] + "已经离开");}else{//如果从服务器返回的其他消息格式,则在ListBox控件中直接显示add(msg);}}//关闭连接tcpClient.Close();}catch{add("网络发生错误");}}//将“EXIT”命令发送给服务器,此命令格式要与服务器端的命令格式一致private void FrmClient_FormClosing(object sender, FormClosingEventArgs e){btnExit_Click(sender, e);}//设置字体颜色//向显示消息的rtbMsg中添加信息是通过add函数完成的private void add(string msg){if (!color.IsEmpty){this.rtbMsg.SelectionColor = color;}this.rtbMsg.SelectedText = msg + "\n";}private void btnExit_Click(object sender, EventArgs e){if (true){string message = "EXIT|" + this.username.Text + "|";//将字符串转化为字符数组byte[] outbytes = System.Text.Encoding.Default.GetBytes(message.ToCharArray());Stream.Write(outbytes, 0, outbytes.Length);this.state = CLOSED;this.stopFlag = true;this.lstUsers.Items.Clear();}}private void btnColor_Click(object sender, EventArgs e){ColorDialog colorDialog1 = new ColorDialog();colorDialog1.Color = this.rtbMsg.SelectionColor;if (colorDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK &&colorDialog1.Color != this.rtbMsg.SelectionColor){this.rtbMsg.SelectionColor = colorDialog1.Color;color = colorDialog1.Color;}}}
}

基于Socket编程的网络聊天室相关推荐

  1. 基于TCP协议的网络聊天室

    创建服务端程序 public class Server {public static void main(String[] args) {new Thread(new ServerTask()).st ...

  2. 基于socket.io的web聊天室

    基于socket.io的web聊天室 一. 项目介绍 该项目使用node.js作为后端服务器框架,并利用socket.io来实现web聊天室功能.socket.io是由 JavaScript 实现的基 ...

  3. 使用socket实现基于select模型的网络聊天室

    假期闲来无事,便写了个小小的网络聊天室程序.以前一直都是写MFC的有界面的程序,时间长了,便失去了兴趣,感觉有些东西经过MFC的封装,反而失去了意义,让人学不到东西,所以,丢开MFC那层的东西,直接使 ...

  4. Socket编程实现简易聊天室

    1.Socket基础知识 Socket(套接字)用于描述IP地址和端口,是通信链的句柄,应用程序可以通过Socket向网络发出请求或者应答网络请求. Socket是支持TCP/IP协议的网络通信的基本 ...

  5. Linux下基于socket和多线程的聊天室小程序

    转载:http://blog.csdn.net/robot__man/article/details/52460733 要求:基于TCP编写,一个聊天室最多100人.  客户端:  1.用户需要登录, ...

  6. 使用python基于socket的tcp服务器聊天室

    # coding=utf-8 import socket,threading,time '''代码说明:1.创建一个字典用于接受客户端的用户名和信息2.创建一个类对象client用于编写客户端套接字对 ...

  7. html5 red5,一个基于red5+flash的网络聊天室red5Chat

    前两天发现一个开源的基于red5+flash的聊天室软件red5Chat,于是想搭起来玩玩,没想到遇到一大堆问题.首先到http://www.red5chat.com/下载源码red5chatv2.1 ...

  8. 基于Socket通信的Android聊天室

    资源下载地址:https://download.csdn.net/download/sheziqiong/86763786 资源下载地址:https://download.csdn.net/downl ...

  9. python socket + tkinter实现网络聊天室

    个人博客文章链接:http://www.huqj.top/article?id=169 最近突然想用socket做个聊天室程序,之前用java写过一个文件传输的程序,这次就用python做一下,顺便也 ...

  10. 小成开发日记---利用Qt/C++实现基于Udp协议的网络聊天室(分服务端和客户端的开发【轻聊v1.0.1】)

    作者:小成Charles 原创作品 转载请标注原创文章地址:https://blog.csdn.net/weixin_42999453/article/details/112363393 一.引言 最 ...

最新文章

  1. DataGridView设置单元格的提示内容ToolTip详解
  2. char,Character,int,字符及编码日记 1
  3. python3 规则引擎_几个常见规则引擎的简单介绍和演示
  4. 【转】什么是ERP、SCM、CRM?
  5. 【UVA - 10815】 Andy's First Dictionary(STL+字符处理)
  6. python数据按照分组进行频率分布_python实现读取类别频数数据画水平条形图
  7. 软件测试黑马程序员课后答案_软件测试课后答案
  8. 华为荣耀9x怎么解账户锁_荣耀X10顶配与30S最低配形成200元的价差,我觉得这样是最好的...
  9. ASP.NET的Web Resources 设置教程
  10. 微信又干了件大好事 老司机们快看!
  11. Python 学习笔记——文件对象和操作
  12. 如何退出while(cinvalue)的循环
  13. Ajax技术复习---狂神笔记
  14. Springboot thymeleaf i18n国际化多语言选择
  15. Web前端大作业 HTML+CSS+JS 防天天生鲜官网 9页 (1)
  16. 开发板实战篇4 RGB565 LCD刷颜色数据
  17. 级联样式单与CSS选择器
  18. (成功解决)网页无法打开位于http://www.baidu.com/的网页无法加载,因为:net:ERR_NAME_NOT_RESOLVED
  19. S7-1200中时钟功能设定和读写调用的具体方法
  20. 谈 DevOps 自动化时,也应该考虑到 SOX 等法案

热门文章

  1. 华为云服务之弹性云服务器ECS的深度使用和云端实践【华为云至简致远】
  2. python冒号_python数组冒号取值操作
  3. Shiro框架Given final block not properly padded问题解决
  4. PSQLException: An I/O error occurred问题排查
  5. eclipse遇到“A java Exception has occurred”报错解决办法
  6. web统计:UV、IP、PV、VV
  7. ASML光刻机PK 原子弹,难度?
  8. 反常识—股票暴跌收益会更高
  9. Android开发之隐示意图跳转
  10. [渝粤教育] 广东-国家-开放大学 21秋期末考试土木工程施工10516k1