C#网络程序设计(1)网络编程常识与C#常用特性

    网络程序设计能够帮我们了解联网应用的底层通信原理!

    (1)网络编程常识:

1)什么是网络编程

只有主要实现进程(线程)相互通信和基本的网络应用原理性(协议)功能的程序,才能算是真正的网络编程。

2)网络编程的层次

现实中的互联网是按照"TCP/IP分层协议栈"的体系结构构建的,因此程序员必须搞清楚自己要做的是哪个层次上的编程工作。

TCP/IP协议体系的实现情况:

其中,网络接口层已经被大多数计算机生产厂家集成在了主板上,也就是经常所说的网卡(NIC)。windows操作系统内核中就集成了TCP/IP协议的实现。TCP/IP协议的核心部分是传输层协议(TCP/UDP)、网际层协议(IP)。应用程序通过编程界面(即程序员界面)与内核打交道。

3)编程界面的两种形式

一种室内和直接提供的系统调用,在windows下表现为Windows API函数;另一种是以程序库的方式提供的各种函数和类。MFC就是微软用C++语言对windows API进行面向对象封装后形成的功能强大的类库。前者在核内实现,后者在核外实现。TCP/IP网络环境下的应用程序是通过网络应用编程界面(套接字Socket)实现的。用VC一般是使用MFC封装好的Socket类,而在C#和.NET中可以实现两种编程界面。

    (2)网络程序的工作机制:

网络程序与传统单机程序的区别在于它能够与网络上其他计算机(主机)中的程序互通信息。

1)Socket基本介绍:

网络上计算机之间的通信实际上是计算机上进程之间的通信。为了标志通信的进程,首先要标志进程所在的主机,其次要标志主机上不同的进程。 在互联网上使用IP地址来标识不同的主机,在网络协议中使用端口号来识别不同的进程。为了唯一地标志网络中的一个进程要使用如下的二元组:

(IP地址,端口号)

这个二元组可以看做是网络进程地址。它是编程界面呈现给骑上应用程序的"插口",可以看成是两个网络应用进程在通信时,各自通信连接中的一个端点。当进行进城之间的通信时,其中一个程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过与网络接口卡(Network Interface Cards,NIC)相连的传输介质将这段信息发往另一台主机的Socket中。使这段信息能够被其他程序使用:

2)套接字的类型

为了满足不同程序对通信质量和性能的要求,一般的网络系统都提供了一下3种不同类型的套接字,以供用户在设计程序时根据不同需要:

流式套接字(SOCKET_STREAM):提供了一种可靠的,面向连接的双向数据传输服务。实现数据无差错、无重复的发送,内设流量控制,被控制的数据被看成无记录边界的字节流。在TCP/IP协议簇中,使用TCP实现字节流的传输,当用户要发送大量数据,
或对数据传输的可靠性有较高要求时使用流式套接字。
数据包套接字(SOCKET_DGRAM):提供了一种无连接,不可靠的双向数据传输服务。数据以独立的包形式被发送,并且保留了记录边界,不提供可靠性保证。数据在传输过程中可能会被丢失或重复,并且不能保证在接收端数据按发送顺序接收。在TCP/IP协议簇中,
使用UDP实现数据报套接字。
原始套接字(SOCKET_RAW):该套接字允许对较低底层协议(如IP/ICMP)进行直接访问。一般用于对TCP/IP核心协议的网络编程。

在windows系统下,套接字WinSock屏蔽了下面TCP/IP协议栈的复杂性,使得在网络编程者看来,两个程序之间的通信实质就是它们各自绑定的套接字之间的通信。

    (2)C#网络编程常用特性:

在C#诸多优秀的特性中,委托,多线程和跨线程回调在网络应用中用得最多。

1)委托

C#的委托相当于C/C++中的函数指针。函数指针用于获得一个函数的入口地址,实现对函数的操作。委托与C/C++中函数指针的不同之处是:委托是面向对象的,类型安全的和保险的,是引用类型,因此对委托的使用要"先定义,后声明,接着实例化,最后作为参数传递给方法,最后才能使用"。定义委托使用关键字delegate。

1.定义
delegate void MyDelegate(type1 para1,type2 para2 ......);
2.声明
MyDelegate mydelegate;
3.实例化
mydelegate=new MyDelegate(obj.InstanceMethod);
4.作为参数传递
SomeMethod(mydelegate);
方法InstanceMethod的定义
private void InstanceMethod(type1 para1,type2 para2......)
{
//方法体,操作参数
}
5.在代码中使用
private void SomeMethod(MyDelegate mydelgate)
{
mydelegate(arg1,arg2......);
}
注意:委托机制实际上是通过委托实现对方法的动态调用。但调用还必须有一个前提条件:方法InstanceMethod有参数且和MyDelegate的参数一致,并且返回类型相同。委托的实例化中的参数既可以是实例方法,也可以是静态方法。
若实例化委托的语句与作为参数的方法位于一个类中,则可以省略对象名引用,直接用方法名实例化委托。

为什么要使用委托:

在接下来的讨论中,均用文字抄写员程序讲解这三个特性:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; namespace text01_Delegate
{
public partial class TraditionalForm : Form
{
public TraditionalForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (checkBox1.Checked == true)
{
Process1.Text = "运行中......";
Process1.Refresh();//强制该控件重绘自己及其子控件
textBox1.Clear();
textBox1.Refresh();
this.WriteTextToBox1();
Process1.Text = "任务1";
}
if (checkBox2.Checked == true)
{
Process2.Text = "运行中......";
Process2.Refresh();
textBox2.Clear();
textBox2.Refresh();
this.WriteTextToBox2();
Process2.Text = "任务2";
}
}
private void WriteTextToBox1()
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length;i++ )
{
textBox1.AppendText(strData[i]+"\r");
DateTime now = DateTime.Now;
while(now.AddSeconds(1)>DateTime.Now){}
}
}
private void WriteTextToBox2()
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length;i++ )
{
textBox2.AppendText(strData[i]+"\r");
DateTime now = DateTime.Now;
while (now.AddSeconds(1) > DateTime.Now) { }
}
}
}
}

这是一个用传统方法编写的程序,当勾选两个复选框时实现将文本框中的文字依次填到文本区1和文本区2。但此程序存在两个问题:无法实现两个文本区域内容的同时书写;由于书写到文本框1和2内的代码基本相同,从而导致代码冗余。这两个问题可以分别通过多线程和委托解决。

    产生问题的根源:

先今程序语言普遍支持的方法(函数)调用机制是结构化编程时代的产物,而结构化是适应面向过程发展起来的编程方式,故程序中的方法(函数)体也是对操作过程的封装,但是如今的面向对象的程序设计方法要求我们设计一种不同于传统方法的全新的代码封装机制(将程序的方法作为一个函数传递)。

用委托实现文字抄写员程序:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; namespace text01_Delegate
{
public partial class DelegateForm : Form
{
private delegate void WriteTextBox(char ch);
private WriteTextBox writeTextBox;
public DelegateForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if(checkBox1.Checked==true)
{
Process1.Text = "运行中";
Process1.Refresh();
textBox1.Clear();
textBox1.Refresh();
writeTextBox = new WriteTextBox(WriTextToBox1);
wriTextForBoxAndBox2(writeTextBox);
Process1.Text = "任务1";
}
if(checkBox2.Checked==true)
{
Process2.Text = "运行中";
Process2.Refresh();
textBox2.Clear();
textBox2.Refresh();
writeTextBox = new WriteTextBox(WriTextToBox2);
wriTextForBoxAndBox2(writeTextBox);
Process2.Text = "任务2";
}
} private void wriTextForBoxAndBox2(WriteTextBox writeMethod)
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length;i++)
{
writeMethod(strData[i]);
DateTime now = DateTime.Now;
while(now.AddSeconds(1)>DateTime.Now){}
}
}
private void WriTextToBox1(char ch)
{
textBox1.AppendText(ch+"\r");
}
private void WriTextToBox2(char ch)
{
textBox2.AppendText(ch+"\r");
}
}
}

由于运行效果与不使用委托相同,只是减少了代码冗余,因此不再演示。

委托的意义:

使用委托使程序员可以讲方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。如此一来,C#语言凭借自身独特的委托机制,完美地实现了方法声明与方法实现的分离,从而彻底贯彻了面向对象的编程思想。

委托是一个特殊的类,它定义了方法的类型,可以讲方法当做另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用if-else(switch)语句,同时也使得程序具有更好的扩展性。

2)多线程

    线程概述:

一个正在运行的应用程序在操作系统中被视为一个进程,进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。线程上下文包括为使线程在其宿主进程地址空间中无缝地继续执行所需的所有信息,包括CPU寄存器组和堆栈。每个应用程序域都是使用单个线程启动的,但该应用程序域中的代码可以创建附加应用程序域和附加线程。

多线程可以提高程序运行的效率,但也会带来很多问题:

1.线程越多,占用内存也越多;
2.多线程需要协调和管理,所以需要占用CPU时间以便跟踪堆栈;
3.线程之间对共享资源的访问会互相影响;
4.线程太多会导致控制复杂。

    线程的创建:

一个进程可以创建一个或多个线程来执行与改进程关联的部分程序代码。在C#中,线程是使用Thread类处理的,在System.Threading命名空间内。

创建线程有两种方法,一种带参数,一种不带参数。这两种方法都通过委托来实现。

1.不带参数(ThreadStart委托)
Thread thread=new Thread(new ThreadStart(method));
thread.start();
2.带参数(ParameterizedThreadStart)
Thread thread=new Thread(ParameterizedThreadStart(method));
thread.start(args1,args2......);

现代应用程序普遍使用线程机制实现,除了增强程序工作的并发性、提高执行效率,更重要的目的是实现用户界面的实时响应已改善软件的交互性能。

单线程实现文字抄写员:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading; namespace text01_Delegate
{
public partial class SingleThreadForm : Form
{
public SingleThreadForm()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;//线程操作无效,允许从不是创建该控件的线程调用
} private void button1_Click(object sender, EventArgs e)
{
ThreadStart doTask = new ThreadStart(DoTask);
Thread taskThread = new Thread(doTask);
taskThread.Start();
}
private void DoTask()
{
if (checkBox1.Checked == true)
{
Process1.Text = "运行中......";
Process1.Refresh();//强制该控件重绘自己及其子控件
textBox1.Clear();
textBox1.Refresh();
this.WriteTextToBox1();
Process1.Text = "任务1"; textBox3.Focus();
textBox3.SelectAll();
}
if (checkBox2.Checked == true)
{
Process2.Text = "运行中......";
Process2.Refresh();
textBox2.Clear();
textBox2.Refresh();
this.WriteTextToBox2();
Process2.Text = "任务2"; textBox3.Focus();
textBox3.SelectAll();
}
}
private void WriteTextToBox1()
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length; i++)
{
textBox1.AppendText(strData[i] + "\r");
DateTime now = DateTime.Now;
while (now.AddSeconds(1) > DateTime.Now) { }
}
}
private void WriteTextToBox2()
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length; i++)
{
textBox2.AppendText(strData[i] + "\r");
DateTime now = DateTime.Now;
while (now.AddSeconds(1) > DateTime.Now) { }
}
}
}
}

本程序的运行效果与传统方式的相同,因此不再显示。区别是在传统程序中主程序执行将文本写入文本框的操作,但在此单线程程序中主线程分配一个子线程,让子线程代替执行此操作,主线程继续监听用户操作(实时GUI响应)。

ThreadStart与ParameterizedThreadStart实际上就是两个.Net预定义的委托。

多线程并发执行:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading; namespace text01_Delegate
{
public partial class AssociateForm : Form
{
private delegate void WriteTextBox(char ch);
private WriteTextBox writeTextBox;
public AssociateForm()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
} private void button1_Click(object sender, EventArgs e)
{
ThreadStart doTask1 = new ThreadStart(DoTsk1);
Thread tsk1Thread = new Thread(doTask1);
tsk1Thread.Start();
ThreadStart doTask2 = new ThreadStart(DoTsk2);
Thread tsk2Thread = new Thread(doTask2);
tsk2Thread.Start();
}
private void DoTsk1()
{
if(checkBox1.Checked==true)
{
Process1.Text = "运行中......";
Process1.Refresh();
textBox1.Clear();
textBox1.Refresh();
//用委托来实现写文本到TextBox1
writeTextBox = new WriteTextBox(writeTextToBox1);
writeText(writeTextBox);
Process1.Text = "任务1";
textBox3.Focus();
textBox3.SelectAll();
}
}
private void DoTsk2()
{
if (checkBox2.Checked == true)
{
Process2.Text = "运行中......";
Process2.Refresh();
textBox2.Clear();
textBox2.Refresh();
writeTextBox = new WriteTextBox(writeTextToBox2);
writeText(writeTextBox);
Process2.Text = "任务2";
textBox3.Focus();
textBox3.SelectAll();
}
}
private void writeText(WriteTextBox wMthod)
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length;i++)
{
wMthod(strData[i]);
DateTime now = DateTime.Now;
while (now.AddSeconds(1) > DateTime.Now) { }
}
}
private void writeTextToBox1(char ch)
{
textBox1.AppendText(ch+"");
}
private void writeTextToBox2(char ch)
{
textBox2.AppendText(ch+"");
}
}
}

3)跨线程回调

在上述的单线程与多线程并发程序中,窗体的实例化方法中都有如下一句:

CheckForIllegalCrossThreadCalls = false;

.Net对它的解释是:

获取或设置一个值,该值指示是否捕获对错误线程的调用,这些调用在调试应用程序时访问控件的 System.Windows.Forms.Control.Handle 属性。

如果在上述程序中注释掉这一句会出现如下错误:

这是因为在.Net上执行的是托管代码,C#强制要求这些代码必须是线程安全的,即不允许跨线程访问Windows窗体的控件。在前面的例子中,为了使程序可以正常执行,将C#内置控件(Control)类的CheckForIllegalCrossThreadCalls属性人为地设置为false,已屏蔽掉编译器对跨线程调用的检查。如此做法是不安全的,而要在遵守.Net的安全规范前提下,从一个线程成功地访问另一个线程创建的控件就要用到C#的方法回调机制。

    回调实现的过程:

1.定义声明回调:
delegate void DoSomeCallBack(type para);
DoSomeCallBack doSomeCallBack
2.初始化回调方法:
doSomeCallBack=new DoSomeCallBack(DoSomeMethod);
实例化刚刚定义的委托,这里作为参数的DoSomeMethod称为"回调方法",它封装了对另一个线程中目标对象(窗体对象或其他类)的操作代码。
3.触发对象动作:
Opt obj.invoke(doSomeCallBack,arg);
其中Opt obj为目标操作对象,在此假设它是某控件,故调用其INvoke方法。
Invoke方法的方法签名:
Object Control.Invoke(Delegate method,params object[] args);
C#的方法回调机制实际上是委托的一种应用。

方法回调与委托与线程的综合应用:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading; namespace text01_Delegate
{
public partial class IntergrationForm : Form
{
private delegate void WriteTextBox(char ch);
private WriteTextBox writeTextBox; //声明委托以实现回调机制
private delegate void WriteTextBox1CallBack(char ch);
private WriteTextBox1CallBack writeTextBox1CallBack; private delegate void WriteTextBox2CallBack(char ch);
private WriteTextBox2CallBack writeTextBox2CallBack; private delegate void ShowProcess1CallBack(string str);
private ShowProcess1CallBack showProcess1CallBack; private delegate void ShowProcess2CallBack(string str);
private ShowProcess2CallBack showProcess2CallBack;
public IntergrationForm()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = true;
writeTextBox1CallBack = new WriteTextBox1CallBack(writeTextBox1ByItself);
writeTextBox2CallBack = new WriteTextBox2CallBack(writeTextBox2ByItself);
showProcess1CallBack = new ShowProcess1CallBack(showProcess1ByItself);
showProcess2CallBack = new ShowProcess2CallBack(showProcess2ByItself);
}
private void writeTextBox1ByItself(char ch)
{
textBox1.AppendText(ch+"");
}
private void writeTextBox2ByItself(char ch)
{
//textBox1.AppendText(ch+"");这样写程序也是运行正确的
textBox2.AppendText(ch+""); }
private void showProcess1ByItself(string str)
{
Process1.Text = str;
Process1.Refresh();
}
private void showProcess2ByItself(string str)
{
Process2.Text = str;
Process2.Refresh();
}
private void button1_Click(object sender, EventArgs e)
{
if (checkBox1.Checked == true)
{
//Process1.Text = "运行中";
//Process1.Refresh();
Process1.Invoke(showProcess1CallBack, "运行中......");
ThreadStart doTask1 = new ThreadStart(writeTextToBox1);
Thread tsk1Thread = new Thread(doTask1);
tsk1Thread.Start();
//Process1.Text = "任务1";
//textBox3.Focus();
//textBox3.SelectAll();
Process1.Invoke(showProcess1CallBack, "任务1");
}
if (checkBox2.Checked == true)
{
//Process2.Text = "运行中";
//Process2.Refresh();
Process2.Invoke(showProcess2CallBack, "运行中......");
ThreadStart doTask2 = new ThreadStart(writeTextToBox2);
Thread tsk2Thread = new Thread(doTask2);
tsk2Thread.Start();
//Process2.Text = "任务2";
Process2.Invoke(showProcess2CallBack, "任务2");
}
}
private void writeTextToBox1()
{
writeTextBox = new WriteTextBox(wriTextBox1);
wriText(writeTextBox);
}
private void writeTextToBox2()
{
writeTextBox = new WriteTextBox(wriTextBox2);
wriText(writeTextBox);
}
private void wriTextBox1(char ch)
{
textBox1.Invoke(writeTextBox1CallBack,ch);
}
private void wriTextBox2(char ch)
{
textBox2.Invoke(writeTextBox2CallBack,ch);
}
private void wriText(WriteTextBox wMethod)
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length;i++ )
{
wMethod(strData[i]);
DateTime now=DateTime.Now;
while(now.AddSeconds(1)>DateTime.Now){}
}
}
}
}

效果不再演示!

实验文档:http://files.cnblogs.com/files/MenAngel/NetProgram.zip

 

网络编程C#的更多相关文章

  1. 猫哥网络编程系列:HTTP PEM 万能调试法

    注:本文内容较长且细节较多,建议先收藏再阅读,原文将在 Github 上维护与更新. 在 HTTP 接口开发与调试过程中,我们经常遇到以下类似的问题: 为什么本地环境接口可以调用成功,但放到手机上就跑 ...

  2. python select网络编程详细介绍

    刚看了反应堆模式的原理,特意复习了socket编程,本文主要介绍python的基本socket使用和select使用,主要用于了解socket通信过程 一.socket模块 socket - Low- ...

  3. Linux Socket 网络编程

    Linux下的网络编程指的是socket套接字编程,入门比较简单.在学校里学过一些皮毛,平时就是自学玩,没有见识过真正的socket编程大程序,比较遗憾.总感觉每次看的时候都有收获,但是每次看完了之后 ...

  4. 猫哥网络编程系列:详解 BAT 面试题

    从产品上线前的接口开发和调试,到上线后的 bug 定位.性能优化,网络编程知识贯穿着一个互联网产品的整个生命周期.不论你是前后端的开发岗位,还是 SQA.运维等其他技术岗位,掌握网络编程知识均是岗位的 ...

  5. 浅谈C#网络编程(一)

    阅读目录: 基础 Socket编程 多线程并发 阻塞式同步IO 基础 在现今软件开发中,网络编程是非常重要的一部分,本文简要介绍下网络编程的概念和实践. Socket是一种网络编程接口,它是对传输层T ...

  6. C++11网络编程

    Handy是一个简洁优雅的C++11网络库,适用于linux与Mac平台.十行代码即可完成一个完整的网络服务器. 下面是echo服务器的代码: #include <handy/handy.h&g ...

  7. Java - 网络编程

    Java的网络编程学习,关于计算机基础的学习参考:计算机网络基础学习 - sqh.     参考:  

  8. Linux网络编程-IO复用技术

    IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理,从而不会在单个IO上阻塞了.Linux中,提 ...

  9. Python Socket 网络编程

    Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...

  10. iOS网络编程

    今天的重点是UIWebView.NSURLSession.JSon. 网络编程联网准备:1.在Info.plist中添加AppTransportSecurity类型Dictionary:2.在AppT ...

随机推荐

  1. iOS_06_基本运算符

    一.算术运算 c语言一共有34种运算符,包括了常见的加减乘除 1.加法运算+ # 除了能做加法运算,还能表示正号:+5.+90 2.减法运算- # 除了能做减法运算,还能表示符号:-10.-200 3 ...

  2. MySQL字符编码问题,Incorrect string value

    MySQL上插入汉字时报错例如以下.详细见后面分析. Incorrect string value: '\xD0\xC2\xC8A\xBEW' for column 'ctnr' at row 1 M ...

  3. js课程 5-14 js如何实现控制动画角色走动

    js课程 5-14 js如何实现控制动画角色走动 一.总结 一句话总结:首先是onkeydown事件,然后是改变元素的left和top属性 1.常用键盘事件有哪些? • onkeydown和 onke ...

  4. python opencv3 —— 改变颜色空间(color space)

    OpenCV: Changing Colorspaces 1. 查看 opencv 支持的颜色空间转换 opencv 中色彩空间转换由一些定义的全局的宏给出,使用如下的代码,将它们调出: >&g ...

  5. spring boot 2.x Path with "WEB-INF" or "META-INF"

    学习spring boot 2.x时,使用jsp作为前端页面.在application.properties配置了jsp所在位置 spring.mvc.view.prefix:/WEB-INF/vie ...

  6. (转)Vim练级攻略

    (转)Vim练级攻略 原文链接:http://coolshell.cn/articles/5426.html vim的学习曲线相当的大(参看各种文本编辑器的学习曲线),所以,如果你一开始看到的是一大堆 ...

  7. centos中的配置文件 分类: B3_LINUX 2015-04-03 22:21 184人阅读 评论(0) 收藏

    /etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.并从/etc/profile.d目录的配置文件中搜集shell的设置. /etc/bashrc:为每一个 ...

  8. thinkphp5项目--企业单车网站(九)(加强复习啊)(花了那么多时间写的博客,不复习太浪费了)

    thinkphp5项目--企业单车网站(九)(加强复习啊)(花了那么多时间写的博客,不复习太浪费了) 项目地址 fry404006308/BicycleEnterpriseWebsite: Bicyc ...

  9. oracle数据库的备份与恢复

    一.备份 方法1: PLSQL中进行导出    对于方式1: 对于导出可执行文件的选择,可通过下面的几个参考位置去查找: 导入imp:F:\app\Administrator\product\11.1 ...

  10. [TypeStyle] Add responsive styles using TypeStyle Media Queries

    Media queries are very important for designs that you want to work on both mobile and desktop browse ...