C# GUI和网络编程

1. 入口函数以及Application类

Application类提供一个run函数,在入口函数中调用它可以使得程序进入新进程,并且持续运行窗口。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CSharpNetApp1
    static class Program
        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            //Application.Run(new Form1()); 
            /*******************************************************************/
            // 子窗口 exampleWindow 运行在一个子线程中
            // 主线程 Application 把持住,在持续运行
            // createWindow()返回一个ExampleWindow对象,ExampleWindow是Form的子类
            Application.Run(ExampleWindow.createWindow());
            /*******************************************************************/

2. 组件事件

创建组件以及对应的事件 (比如Button的点击事件,TextBox的文字更改事件)

class classA{
    static classA createClassA(){
        ClassA classA = new ClassA();
        TextBox Box = new TextBox();
        Box.Click += new System.EventHandler(WhenTextChanger); // 事件处理函数
        classA.Controls.Add(Box);
    // 事件处理函数
    static void WhenTextChanger(object sender, System.EventArgs e){
        // 其中 sender 经过强转可以转换成为这个组件
        TextBox Box = (TextBox)sender;
class ExampleWindow : Form
    public static ExampleWindow createWindow()
        ExampleWindow wikiWindow = new ExampleWindow();
        wikiWindow.Text = "我喜欢维基教科书";
        wikiWindow.Width = 400;
        wikiWindow.Height = 300;
        Button HelloButton = new Button();
        HelloButton.Location = new Point(20, 20);
        HelloButton.Size = new Size(100, 30);
        HelloButton.Text = "点击我";
        HelloButton.Click += new System.EventHandler(WhenHelloButtonClick);
        wikiWindow.Controls.Add(HelloButton);
        TextBox Box = new TextBox();
        Box.Location = new Point(20, 60);
        Box.Size = new Size(150, 30);
        Box.Font = new Font("Arial", 12);
        Box.TextChanged += new System.EventHandler(WhenTextChanged);
        wikiWindow.Controls.Add(Box);
        return wikiWindow;
    // 窗体运行在子线程中,所以创建监听事件在此 --> below
    /*******************************************************************/
    static void WhenHelloButtonClick(object sender, System.EventArgs e)
        MessageBox.Show("你点击了我,按下OK,退出本条消息");
    static void WhenTextChanged(object sender, System.EventArgs e)
        TextBox Box = (TextBox)sender;
        MessageBox.Show(Box.Text);
    /*******************************************************************/

3. 线程

开启新线程首先新建一个线程对象 Thread,在创建过程中将事件处理函数传递进去,如果事件处理函数要求参数,则在启动线程的时候传递到 newThread.Start() 函数里去。

public static void createThread(int i)
    Thread newThread = new Thread(ThreadFunction);
    newThread.Start(i);
static void ThreadFunction(object i)
    while (true)
        // 将Console重定向至TextBox
        MessageBox.Show(String.Format("Hi Thread: {0}",i));
        Thread.Sleep(2000);

4. 套接字

4.1 TCP

导入包System.Net.Sockets

using System.Net.Sockets;
using System.Net;
static void NetworkFunction()
    String server = "webcode.me";
    int port = 80;
    var request = $"GET / HTTP/1.1\r\nHost: {server}\r\nConnection: Close\r\n\r\n";
    Byte[] requestBytes = Encoding.ASCII.GetBytes(request);
    Byte[] bytesReceived = new Byte[256];
    IPHostEntry hostEntry = Dns.GetHostEntry(server);
    var ipe = new IPEndPoint(hostEntry.AddressList[0], port);
    using var socket = new Socket(AddressFamily.InterNetwork,
        SocketType.Stream, ProtocolType.Tcp);
    socket.Connect(ipe);
    if (socket.Connected)
        MessageBox.Show("连接建立");
        MessageBox.Show("连接失败");
        return;
    socket.Send(requestBytes, requestBytes.Length, 0);
    int bytes = 0;
    var sb = new StringBuilder();
        bytes = socket.Receive(bytesReceived, bytesReceived.Length, 0);
        sb.Append(Encoding.ASCII.GetString(bytesReceived, 0, bytes));
    } while (bytes > 0);
    MessageBox.Show(sb.ToString());

该实例使用Socket类将数据发送到HTTP服务器并接受响应,该进程在连接时阻塞直到受到整个页面。套接字编程时低级的编程,使用HttpWebRequest或者HttpClient抽象这些低级类。

string server = "webcode.me";
int port = 80;

这里定义了服务器域名和端口。

var request = $"GET / HTTP/1.1\r\nHost: {server}\r\nConnection: Close\r\n\r\n";

这里是一个GET请求,它的格式是由通信过程定义的,其中\r\n字符是通信过程必须的部分。

Byte[] requestBytes = Encoding.ASCII.GetBytes(request);

这里将request文本请求编码为AICII字节。

Byte[] bytesReceived = new Byte[256];

这里定义了存储服务器响应内容的字节数组。

IPHostEntry hostEntry = Dns.GetHostEntry(server);

通过DnsGetHostEntry找出域名的IP地址。

var ipe = new IPEndPoint(hostEntry.AddressList[0], port);

这里创建了一个IPEndPoint,它是一个网络端点,有一个网络IP地址和端口号组成。从AddressList属性中检索IP地址。

using var socket = new Socket(AddressFamily.InterNetwork,
        SocketType.Stream, ProtocolType.Tcp);

这里创建一个套接字,该AddressFamily.InterNetwork指定我们使用IPv4地址。在SocketType.Stream提供可靠的,双向的,基于连接的字节流。ProtocalType.TCP指定协议的类型为TCP。

socket.Connect(ipe);

开始连接,本线程开始阻塞,直到连接成功或者请求超时。

if(socket.Connected)
    Console.WriteLine("连接建立");
    Console.WriteLine("连接失败");
    return;

若是连接失败,则结束该进程。

socket.Send(requestBytes, requestBytes.Length, 0);

若是连接成功,则发送请求。

int bytes = 0;
var sb = new StringBuilder();
    bytes = socket.Receive(bytesReceived, bytesReceived.Length, 0);
    sb.Append(Encoding.ASCII.GetString(bytesReceived, 0, bytes));
} while (bytes > 0);

接收并存储响应内容。

4.2 TCP建立连接小结

[1] 声明服务器域名和端口

[2] 建立请求报文,将报文字符串编码为ASCII字节

[3] 找到服务器域名所对应的网络IP地址

[4] 建立套接字,并且进行连接

4.3 UDP 发送

UdpClient提供用户数据报协议 (UDP) 网络服务。

static void UDPNetworkFunction()
    UdpClient udpClient = new UdpClient("255.255.255.255", 5000);
    Byte[] data = Encoding.ASCII.GetBytes("Hello!");
    udpClient.Send(data, data.Length);
    IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
    Byte[] received = udpClient.Receive(ref RemoteIpEndPoint);
    string output = Encoding.ASCII.GetString(received);
    MessageBox.Show(output);
    udpClient.Close();

该UDP服务向地址255.255.255.255的端口5000发送消息 "Hello!"。

4.4 UDP接收

/**
 * UDP网络程序-接收
static void UDPNetworkAcceptFunction(object window)
    ExampleWindow mWindow = (ExampleWindow)window;
    int PORT = 5000;
    UdpClient Server = new UdpClient(PORT);
    var ResponseData = Encoding.UTF8.GetBytes("Hi");
    while (true)
        var ClientEp = new IPEndPoint(IPAddress.Any, 5000);
        var ClientRequestData = Server.Receive(ref ClientEp);
        var ClientRequest = Encoding.UTF8.GetString(ClientRequestData);
        // 线程中更新组件
        mWindow.textBox1.Invoke((MethodInvoker)delegate {
            // Running on the UI thread
            mWindow.textBox1.Text += ClientRequest;
        MessageBox.Show(string.Format("Received {0} from {1}, sending response",ClientRequest, ClientEp.Address.ToString()));

4.5 UDP小结

[1] 声明监听IP和端口,使用UdpClient建立监听

[2] 声明远程IP和端口,发送或接收消息

5. 在线程中更新GUI

5.1 启用线程执行网络功能

Thread UDPNetAcceptThread = new Thread(UDPNetworkAcceptFunction);
UDPNetAcceptThread.IsBackground = true; // 设置为后台线程,才能让应用程序在主线程完成后退出
UDPNetAcceptThread.Start(wikiWindow);

5.2 更新textbox控件

使用委托,也就是 uicomponent.Invoke(MethodInvoker)deledate{ 在UI的创建线程中的操作 } 这套语法。

// 线程中更新组件
mWindow.textBox1.Invoke((MethodInvoker)delegate {
    // Running on the UI thread
    mWindow.textBox1.Text += ClientRequest;

5.3 终止线程

在启动线程之间将线程设置为后台线程,才能让应用程序在主线程完成后退出。

UDPNetAcceptThread.IsBackground = true; // 设置为后台线程,才能让应用程序在主线程完成后退出

6. UDP多个应用绑定到一个端口

将原来新建 UdpClient 的步骤修改掉,不要在构造器内传入端口

int PORT = 5000;