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;