分享WCF聊天程序--WCFChat

无意中在一个国外的站点下到了一个利用WCF实现聊天的程序,作者是:Nikola Paljetak。研究了一下,自己做了测试和部分修改,感觉还不错,分享给大家。
先来看下运行效果:
开启服务:

客户端程序:


程序分为客户端和服务器端:
------------服务器端:

IChatService.cs:

Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Collections;

namespace WCFChatService
{
    
// SessionMode.Required  允许Session会话。双工协定时的回调协定类型为IChatCallback接口
    [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))]
    
public interface IChatService
    {
        [OperationContract(IsOneWay 
= false, IsInitiating = true, IsTerminating = false)]//----->IsOneWay = false等待服务器完成对方法处理;IsInitiating = true启动Session会话,IsTerminating = false 设置服务器发送回复后不关闭会话
        string[] Join(string name);//用户加入

        [OperationContract(IsOneWay 
= true, IsInitiating = false, IsTerminating = false)]
        
void Say(string msg);//群聊信息

        [OperationContract(IsOneWay 
= true, IsInitiating = false, IsTerminating = false)]
        
void Whisper(string to, string msg);//私聊信息

        [OperationContract(IsOneWay 
= true, IsInitiating = false, IsTerminating = true)]
        
void Leave();//用户加入
    }
    
/// <summary>
    
/// 双向通信的回调接口
    
/// </summary>
    interface IChatCallback
    {
        [OperationContract(IsOneWay 
= true)]
        
void Receive(string senderName, string message);

        [OperationContract(IsOneWay 
= true)]
        
void ReceiveWhisper(string senderName, string message);

        [OperationContract(IsOneWay 
= true)]
        
void UserEnter(string name);

        [OperationContract(IsOneWay 
= true)]
        
void UserLeave(string name);
    }

    
/// <summary>
    
/// 设定消息的类型
    
/// </summary>
    public enum MessageType { Receive, UserEnter, UserLeave, ReceiveWhisper };
    
/// <summary>
    
/// 定义一个本例的事件消息类. 创建包含有关事件的其他有用的信息的变量,只要派生自EventArgs即可。
    
/// </summary>
    public class ChatEventArgs : EventArgs
    {
        
public MessageType msgType;
        
public string name;
        
public string message;
    }
}

ChatService.cs

Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WCFChatService
{
    
// InstanceContextMode.PerSession 服务器为每个客户会话创建一个新的上下文对象。ConcurrencyMode.Multiple 异步的多线程实例
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    
public class ChatService : IChatService
    {
        
private static Object syncObj = new Object();////定义一个静态对象用于线程部份代码块的锁定,用于lock操作
        IChatCallback callback = null;

        
public delegate void ChatEventHandler(object sender, ChatEventArgs e);//定义用于把处理程序赋予给事件的委托。
        public static event ChatEventHandler ChatEvent;//定义事件
        static Dictionary<string, ChatEventHandler> chatters = new Dictionary<string, ChatEventHandler>();//创建一个静态Dictionary(表示键和值)集合(字典),用于记录在线成员,Dictionary<(Of <(TKey, TValue>)>) 泛型类

        
private string name;
        
private ChatEventHandler myEventHandler = null;


        
public string[] Join(string name)
        {
            
bool userAdded = false;
            myEventHandler 
= new ChatEventHandler(MyEventHandler);//将MyEventHandler方法作为参数传递给委托

            
lock (syncObj)//线程的同步性,同步访问多个线程的任何变量,利用lock(独占锁),确保数据访问的唯一性。
            {
                
if (!chatters.ContainsKey(name) && name != "" && name != null)
                {
                    
this.name = name;
                    chatters.Add(name, MyEventHandler);
                    userAdded 
= true;
                }
            }

            
if (userAdded)
            {
                callback 
= OperationContext.Current.GetCallbackChannel<IChatCallback>();//获取当前操作客户端实例的通道给IChatCallback接口的实例callback,此通道是一个定义为IChatCallback类型的泛类型,通道的类型是事先服务契约协定好的双工机制。
                ChatEventArgs e = new ChatEventArgs();//实例化事件消息类ChatEventArgs
                e.msgType = MessageType.UserEnter;
                e.name 
= name;
                BroadcastMessage(e);
                ChatEvent 
+= myEventHandler;
                
string[] list = new string[chatters.Count]; //以下代码返回当前进入聊天室成员的称列表
                lock (syncObj)
                {
                    chatters.Keys.CopyTo(list, 
0);//将字典中记录的用户信息复制到数组中返回。
                }
                
return list;
            }
            
else
            {
                
return null;
            }
        }

        
public void Say(string msg)
        {
            ChatEventArgs e 
= new ChatEventArgs();
            e.msgType 
= MessageType.Receive;
            e.name 
= this.name;
            e.message 
= msg;
            BroadcastMessage(e);
        }

        
public void Whisper(string to, string msg)
        {
            ChatEventArgs e 
= new ChatEventArgs();
            e.msgType 
= MessageType.ReceiveWhisper;
            e.name 
= this.name;
            e.message 
= msg;
            
try
            {
                ChatEventHandler chatterTo;
//创建一个临时委托实例
                lock (syncObj)
                {
                    chatterTo 
= chatters[to]; //查找成员字典中,找到要接收者的委托调用
                }
                chatterTo.BeginInvoke(
this, e, new AsyncCallback(EndAsync), null);//异步方式调用接收者的委托调用
            }
            
catch (KeyNotFoundException)
            {
            }
        }

        
public void Leave()
        {
            
if (this.name == null)
                
return;

            
lock (syncObj)
            {
                chatters.Remove(
this.name);
            }
            ChatEvent 
-= myEventHandler;
            ChatEventArgs e 
= new ChatEventArgs();
            e.msgType 
= MessageType.UserLeave;
            e.name 
= this.name;
            
this.name = null;
            BroadcastMessage(e);
        }

        
//回调,根据客户端动作通知对应客户端执行对应的操作
        private void MyEventHandler(object sender, ChatEventArgs e)
        {
            
try
            {
                
switch (e.msgType)
                {
                    
case MessageType.Receive:
                        callback.Receive(e.name, e.message);
                        
break;
                    
case MessageType.ReceiveWhisper:
                        callback.ReceiveWhisper(e.name, e.message);
                        
break;
                    
case MessageType.UserEnter:
                        callback.UserEnter(e.name);
                        
break;
                    
case MessageType.UserLeave:
                        callback.UserLeave(e.name);
                        
break;
                }
            }
            
catch
            {
                Leave();
            }
        }

        
private void BroadcastMessage(ChatEventArgs e)
        {

            ChatEventHandler temp 
= ChatEvent;

            
if (temp != null)
            {
                
//循环将在线的用户广播信息
                foreach (ChatEventHandler handler in temp.GetInvocationList())
                {
                    
//异步方式调用多路广播委托的调用列表中的ChatEventHandler 
                    handler.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);
                }
            }
        }
        
//广播中线程调用完成的回调方法功能:清除异常多路广播委托的调用列表中异常对象(空对象)
        private void EndAsync(IAsyncResult ar)
        {
            ChatEventHandler d 
= null;

            
try
            {
                
//封装异步委托上的异步操作结果
                System.Runtime.Remoting.Messaging.AsyncResult asres = (System.Runtime.Remoting.Messaging.AsyncResult)ar;
                d 
= ((ChatEventHandler)asres.AsyncDelegate);
                d.EndInvoke(ar);
            }
            
catch
            {
                ChatEvent 
-= d;
            }
        }
    }
}

------------客户端:

Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ServiceModel;

namespace WCFChatClient
{
    
public partial class ChatForm : Form, IChatServiceCallback
    {
        
/// <summary>
        
/// 该函数将指定的消息发送到一个或多个窗口。此函数为指定的窗口调用窗口程序,直到窗口程序处理完消息再返回。 
        
/// </summary>
        
/// <param name="hWnd">其窗口程序将接收消息的窗口的句柄</param>
        
/// <param name="msg">指定被发送的消息</param>
        
/// <param name="wParam">指定附加的消息指定信息</param>
        
/// <param name="lParam">指定附加的消息指定信息</param>
        [DllImport("user32.dll")]
        
private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
        
//当一个窗口标准垂直滚动条产生一个滚动事件时发送此消息给那个窗口,也发送给拥有它的控件
        private const int WM_VSCROLL = 0x115;
        
private const int SB_BOTTOM = 7;
        
private int lastSelectedIndex = -1;

        
private ChatServiceClient proxy;
        
private string userName;

        
private WaitForm wfDlg = new WaitForm();
        
private delegate void HandleDelegate(string[] list);
        
private delegate void HandleErrorDelegate();

        
public ChatForm()
        {
            InitializeComponent();
            ShowInterChatMenuItem(
true);
        }

        
/// <summary>
        
/// 连接服务器
        
/// </summary>
        private void InterChatMenuItem_Click(object sender, EventArgs e)
        {
            lbOnlineUsers.Items.Clear();
            LoginForm loginDlg 
= new LoginForm();
            
if (loginDlg.ShowDialog() == DialogResult.OK)
            {
                userName 
= loginDlg.txtUserName.Text;
                loginDlg.Close();
            }

            txtChatContent.Focus();
            Application.DoEvents();
            InstanceContext site 
= new InstanceContext(this);//为实现服务实例的对象进行初始化
            proxy = new ChatServiceClient(site);
            IAsyncResult iar 
= proxy.BeginJoin(userName, new AsyncCallback(OnEndJoin), null);
            wfDlg.ShowDialog();
        }

        
private void OnEndJoin(IAsyncResult iar)
        {
            
try
            {
                
string[] list = proxy.EndJoin(iar);
                HandleEndJoin(list);

            }
            
catch (Exception e)
            {
                HandleEndJoinError();
            }

        }
        
/// <summary>
        
/// 错误提示
        
/// </summary>
        private void HandleEndJoinError()
        {
            
if (wfDlg.InvokeRequired)
                wfDlg.Invoke(
new HandleErrorDelegate(HandleEndJoinError));
            
else
            {
                wfDlg.ShowError(
"无法连接聊天室!");
                ExitChatSession();
            }
        }
        
/// <summary>
        
/// 登录结束后的处理
        
/// </summary>
        
/// <param name="list"></param>
        private void HandleEndJoin(string[] list)
        {
            
if (wfDlg.InvokeRequired)
                wfDlg.Invoke(
new HandleDelegate(HandleEndJoin), new object[] { list });
            
else
            {
                wfDlg.Visible 
= false;
                ShowInterChatMenuItem(
false);
                
foreach (string name in list)
                {
                    lbOnlineUsers.Items.Add(name);
                }
                AppendText(
" 用户: " + userName + "--------登录---------" + DateTime.Now.ToString()+ Environment.NewLine);
            }
        }
        
/// <summary>
        
/// 退出聊天室
        
/// </summary>
        private void OutInterChatMenuItem_Click(object sender, EventArgs e)
        {
            ExitChatSession();
            Application.Exit();
        }
        
/// <summary>
        
/// 群聊
        
/// </summary>
        private void btnChat_Click(object sender, EventArgs e)
        {
            SayAndClear(
"", txtChatContent.Text, false);
            txtChatContent.Focus();
        }
        
/// <summary>
        
/// 发送消息
        
/// </summary>
        private void SayAndClear(string to, string msg, bool pvt)
        {
            
if (msg != "")
            {
                
try
                {
                    CommunicationState cs 
= proxy.State;
                    
//pvt 公聊还是私聊
                    if (!pvt)
                    {
                        proxy.Say(msg);
                    }
                    
else
                    {
                        proxy.Whisper(to, msg);
                    }

                    txtChatContent.Text 
= "";
                }
                
catch
                {
                    AbortProxyAndUpdateUI();
                    AppendText(
"失去连接: " + DateTime.Now.ToString() + Environment.NewLine);
                    ExitChatSession();
                }
            }
        }
        
private void txtChatContent_KeyPress(object sender, KeyPressEventArgs e)
        {
            
if (e.KeyChar == 13)
            {
                e.Handled 
= true;
                btnChat.PerformClick();
            }
        }
        
/// <summary>
        
/// 只有选择一个用户时,私聊按钮才可用
        
/// </summary>
        private void lbOnlineUsers_SelectedIndexChanged(object sender, EventArgs e)
        {
            AdjustWhisperButton();
        }
        
/// <summary>
        
/// 私聊
        
/// </summary>       
        private void btnWhisper_Click(object sender, EventArgs e)
        {
            
if (txtChatDetails.Text == "")
            {
                
return;
            }
            
object to = lbOnlineUsers.SelectedItem;
            
if (to != null)
            {
                
string receiverName = (string)to;
                AppendText(
"私下对" + receiverName + "说: " + txtChatContent.Text);//+ Environment.NewLine
                SayAndClear(receiverName, txtChatContent.Text, true);
                txtChatContent.Focus();
            }
        }
        
/// <summary>
        
/// 连接聊天室
        
/// </summary>
        private void ShowInterChatMenuItem(bool show)
        {
            InterChatMenuItem.Enabled 
= show;
            OutInterChatMenuItem.Enabled 
= this.btnChat.Enabled = !show;
        }
        
private void AppendText(string text)
        {
            txtChatDetails.Text 
+= text;
            SendMessage(txtChatDetails.Handle, WM_VSCROLL, SB_BOTTOM, 
new IntPtr(0));
        }
        
/// <summary>
        
/// 退出应用程序时,释放使用资源
        
/// </summary>
        private void ExitChatSession()
        {
            
try
            {
                proxy.Leave();
            }
            
catch { }
            
finally
            {
                AbortProxyAndUpdateUI();
            }
        }
        
/// <summary>
        
/// 释放使用资源
        
/// </summary>
        private void AbortProxyAndUpdateUI()
        {
            
if (proxy != null)
            {
                proxy.Abort();
                proxy.Close();
                proxy 
= null;
            }
            ShowInterChatMenuItem(
true);
        }
        
/// <summary>
        
/// 接收消息
        
/// </summary>
        public void Receive(string senderName, string message)
        {
            AppendText(senderName 
+ "说: " + message + Environment.NewLine);
        }
        
/// <summary>
        
/// 接收私聊消息
        
/// </summary>
        public void ReceiveWhisper(string senderName, string message)
        {
            AppendText(senderName 
+ " 私下说: " + message + Environment.NewLine);
        }
        
/// <summary>
        
/// 新用户登录
        
/// </summary>
        public void UserEnter(string name)
        {
            AppendText(
"用户 " + name + " --------登录---------" + DateTime.Now.ToString() + Environment.NewLine);
            lbOnlineUsers.Items.Add(name);
        }
        
/// <summary>
        
/// 用户离开
        
/// </summary>
        public void UserLeave(string name)
        {
            AppendText(
"用户 " + name + " --------离开---------" + DateTime.Now.ToString() + Environment.NewLine);
            lbOnlineUsers.Items.Remove(name);
            AdjustWhisperButton();
        }
        
/// <summary>
        
/// 控制私聊按钮的可用性,只有选择了用户时按钮才可用
        
/// </summary>
        private void AdjustWhisperButton()
        {
            
if (lbOnlineUsers.SelectedIndex == lastSelectedIndex)
            {
                lbOnlineUsers.SelectedIndex 
= -1;
                lastSelectedIndex 
= -1;
                btnWhisper.Enabled 
= false;
            }
            
else
            {
                btnWhisper.Enabled 
= true;
                lastSelectedIndex 
= lbOnlineUsers.SelectedIndex;
            }

            txtChatContent.Focus();
        }
        
/// <summary>
        
/// 窗体关闭时,释放使用资源
        
/// </summary>
        private void ChatForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            AbortProxyAndUpdateUI();
            Application.Exit();
        }
    }
}

代码中我做了详细的讲解,相信园友们完全可以看懂。代码中的一些使用的方法还是值得大家参考学习的。这里涉及到了WCF的使用方法,需要注意的是:如果想利用工具生成代理类,需要加上下面的代码:

Code
if (host.Description.Behaviors.Find<System.ServiceModel.Description.ServiceMetadataBehavior>() == null)
            {
                BindingElement metaElement 
= new TcpTransportBindingElement();
                CustomBinding metaBind 
= new CustomBinding(metaElement);
                host.Description.Behaviors.Add(
new System.ServiceModel.Description.ServiceMetadataBehavior());
                host.AddServiceEndpoint(
typeof(System.ServiceModel.Description.IMetadataExchange), metaBind, "MEX");
            }

否则在生成代理类的时候会报错如下的错误:

源码下载:
/Files/gaoweipeng/WCFChat.rar

转载于:https://www.cnblogs.com/soundcode/archive/2011/08/08/2131113.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/378278.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c# uri.host_C#| 具有示例的Uri.Equality()运算符

c# uri.hostUri.Equality()运算符 (Uri.Equality() Operator) Uri.Equality() Operator is overloaded which is used to compare two Uri objects. It returns true if two Uri objects contain the same Uri otherwise it returns false. Uri.Equality()运算符已重载&#xf…

第六章至第九章的单元测试

1,‌助剂与纤维作用力大于纤维分子之间的作用力,则该助剂最好用作() 纤维增塑膨化剂。 2,助剂扩散速率快,优先占领纤维上的染座,但助剂与纤维之间作用力小于染料与纤维之间作用力,该助剂可以作为() 匀染剂。 3,助剂占领纤维上的染座,但助剂与纤维之间作用力大于染…

【神经网络扩展】:断点续训和参数提取

课程来源&#xff1a;人工智能实践:Tensorflow笔记2 文章目录前言断点续训主要步骤参数提取主要步骤总结前言 本讲目标:断点续训&#xff0c;存取最优模型&#xff1b;保存可训练参数至文本 断点续训主要步骤 读取模型&#xff1a; 先定义出存放模型的路径和文件名&#xff0…

开发DBA(APPLICATION DBA)的重要性

开发DBA是干什么的&#xff1f; 1. 审核开发人员写的SQL&#xff0c;并且纠正存在性能问题的SQL ---非常重要 2. 编写复杂业务逻辑SQL&#xff0c;因为复杂业务逻辑SQL开发人员写出的SQL基本上都是有性能问题的&#xff0c;与其让开发人员写&#xff0c;不如DBA自己写。---非常…

javascript和var之间的区别?

You can define your variables in JavaScript using two keywords - the let keyword and the var keyword. The var keyword is the oldest way of defining and declaring variables in JavaScript whereas the let is fairly new and was introduced by ES15. 您可以使用两…

小米手环6NFC安装太空人表盘

以前看我室友峰哥、班长都有手环&#xff0c;一直想买个手环&#xff0c;不舍得&#xff0c;然后今年除夕的时候降价&#xff0c;一狠心&#xff0c;入手了&#xff0c;配上除夕的打年兽活动还有看春晚京东敲鼓领的红包和这几年攒下来的京东豆豆&#xff0c;原价279的小米手环6…

计算机二级c语言题库缩印,计算机二级C语言上机题库(可缩印做考试小抄资料)...

小抄,答案,形成性考核册,形成性考核册答案,参考答案,小抄资料,考试资料,考试笔记第一套1.程序填空程序通过定义学生结构体数组&#xff0c;存储了若干个学生的学号、姓名和三门课的成绩。函数fun 的功能是将存放学生数据的结构体数组&#xff0c;按照姓名的字典序(从小到大排序…

为什么两层3*3卷积核效果比1层5*5卷积核效果要好?

目录1、感受野2、2层3 * 3卷积与1层5 * 5卷积3、2层3 * 3卷积与1层5 * 5卷积的计算量比较4、2层3 * 3卷积与1层5 * 5卷积的非线性比较5、2层3 * 3卷积与1层5 * 5卷积的参数量比较1、感受野 感受野&#xff1a;卷积神经网络各输出特征像素点&#xff0c;在原始图片映射区域大小。…

算法正确性和复杂度分析

算法正确性——循环不变式 算法复杂度的计算 方法一 代换法 —局部代换 这里直接对n变量进行代换 —替换成对数或者指数的情形 n 2^m —整体代换 这里直接对递推项进行代换 —替换成内部递推下标的形式 T(2^n) S(n) 方法二 递归树法 —用实例说明 —分析每一层的内容 —除了…

第十五章 Python和Web

第十五章 Python和Web 本章讨论Python Web编程的一些方面。 三个重要的主题&#xff1a;屏幕抓取、CGI和mod_python。 屏幕抓取 屏幕抓取是通过程序下载网页并从中提取信息的过程。 下载数据并对其进行分析。 从Python Job Board&#xff08;http://python.org/jobs&#x…

array_chunk_PHP array_chunk()函数与示例

array_chunkPHP array_chunk()函数 (PHP array_chunk() Function) array_chunk() function is an array function, it is used to split a given array in number of array (chunks of arrays). array_chunk()函数是一个数组函数&#xff0c;用于将给定数组拆分为多个数组(数组…

raise

raise - Change a windows position in the stacking order button .b -text "Hi there!"pack [frame .f -background blue]pack [label .f.l1 -text "This is above"]pack .b -in .fpack [label .f.l2 -text "This is below"]raise .b转载于:ht…

c语言输出最大素数,for语句计算输出10000以内最大素数怎么搞最简单??各位大神们...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼#include #include int* pt NULL; // primes_tableint pt_size 0; // primes_table 数量大小int init_primes_table(void){FILE* pFile;pFile fopen("primes_table.bin", "rb");if (pFile NULL) {fputs(&q…

【数据结构基础笔记】【图】

代码参考《妙趣横生的算法.C语言实现》 文章目录前言1、图的概念2、图的存储形式1、邻接矩阵&#xff1a;2、邻接表3、代码定义邻接表3、图的创建4、深度优先搜索DFS5、广度优先搜索BFS6、实例分析前言 本章总结&#xff1a;图的概念、图的存储形式、邻接表定义、图的创建、图…

第十六章 测试基础

第十六章 测试基础 在编译型语言中&#xff0c;需要不断重复编辑、编译、运行的循环。 在Python中&#xff0c;不存在编译阶段&#xff0c;只有编辑和运行阶段。测试就是运行程序。 先测试再编码 极限编程先锋引入了“测试一点点&#xff0c;再编写一点点代码”的理念。 换而…

如何蹭网

引言蹭网&#xff0c;在普通人的眼里&#xff0c;是一种很高深的技术活&#xff0c;总觉得肯定很难&#xff0c;肯定很难搞。还没开始学&#xff0c;就已经败给了自己的心里&#xff0c;其实&#xff0c;蹭网太过于简单。我可以毫不夸张的说&#xff0c;只要你会windows的基本操…

android对象缓存,Android简单实现 缓存数据

前言1、每一种要缓存的数据都是有对应的versionCode&#xff0c;通过versionCode请求网络获取是否需要更新2、提前将要缓存的数据放入assets文件夹中&#xff0c;打包上线。缓存设计代码实现/*** Created by huangbo on 2017/6/19.** 主要是缓存的工具类** 缓存设计&#xff1a…

通信原理.绪论

今天刚上通信原理的第一节课&#xff0c;没有涉及过多的讲解&#xff0c;只是讲了下大概的知识框架。现记录如下&#xff1a; 目录1、基本概念消息、信息与信号2、通信系统模型1、信息源2、发送设备3、信道4、接收设备5、信宿6、模拟通信系统模型7、数字通信系统模型8、信源编…

Android版本演进中的兼容性问题

原文&#xff1a;http://android.eoe.cn/topic/summary Android 3.0 的主要变化包括: 不再使用硬件按键进行导航 (返回、菜单、搜索和主屏幕)&#xff0c;而是采用虚拟按键 (返回&#xff0c;主屏幕和最近的应用)。在操作栏固定菜单。 Android 4.0 则把这些变化带到了手机平台。…

css rgba透明_rgba()函数以及CSS中的示例

css rgba透明Introduction: 介绍&#xff1a; Functions are used regularly while we are developing a web page or website. Therefore, to be a good developer you need to master as many functions as you can. This way your coding knowledge will increase as well …