preamble
In the digital age, network programming has become an indispensable part of software development, and for .NET developers in particular, mastering network programming skills in C# is a necessary step to the next level. Whether you're building high-performance web applications or implementing complex distributed systems, network programming is the cornerstone that holds it all together.
This is a comprehensive and concise introduction to C# network programming for .NET developers, from the basics to advanced topics, dissecting them one by one to help you build a solid network programming foundation that will take you on a coding journey through the network world.
I. HTTP Requests
HTTP (Hypertext Transfer Protocol) is one of the most widely used network protocols on the Internet, mainly used for transferring hypertext from World Wide Web servers to local browsers.
In C#, there are several ways to handle HTTP requests, ranging from the traditionalnamespace to the modern
HttpClient
class, each method has its own application scenario.
1. UseHttpClient
Send HTTP request
HttpClient
is the recommended class for sending HTTP requests in C#, which provides an asynchronous API to better handle long-running operations and avoid blocking UI threads.
Below is an example of a simple GET request:
using System; using ; using ; public class HttpClientExample { public static async Task Main() { using var client = new HttpClient(); var response = await ("/data"); if () { var content = await (); (content); } else { ($"Failed to retrieve data: {}"); } } }
2. UseWebClient
Send HTTP request
(go ahead and do it) without hesitatingWebClient
class still exists in the .NET Framework, but in .NET Core and subsequent versions it has been marked as obsolete and it is recommended to use theHttpClient
。
However, for simple synchronization requests, theWebClient
Still available:
using System; using ; using ; class WebClientExample { public static void Main() { using (var client = new WebClient()) { try { string result = ("/info"); (result); } catch (Exception ex) { ($"Error: {}"); } } } }
3. UseHttpRequestMessage
cap (a poem)HttpMessageHandler
For more complex HTTP requests, such as those requiring custom request headers or handling authentication, you can use theHttpRequestMessage
cap (a poem)HttpMessageHandler
。
This approach provides more flexibility and control:
using System; using ; using ; class HttpRequestMessageExample { public static async Task Main() { using var client = new HttpClient(); var request = new HttpRequestMessage(, "/info"); ("Authorization", "Bearer your-access-token"); var response = await (request); if () { var content = await (); (content); } else { ($"Failed to retrieve data: {}"); } } }
4. Cautions
-
Safety and performance: utilization
HttpClient
When doing so, make sure that you reuse the same instance for the lifetime of an application, rather than creating a new instance for each request. - Error handling: Always checks the result of an HTTP request and handles possible exceptions and non-successful HTTP status codes.
-
Timeouts and Cancellations: utilization
HttpClient
When theCancellationToken
to control the timeout and cancelation of requests.
By mastering these points, you will be able to effectively handle a variety of HTTP requests in C#, from simple GET requests to complex POST requests, including authentication and error handling.
WebSocket communication
WebSocket is a protocol for full-duplex communication over a single TCP connection, which provides lower latency and higher efficiency than the traditional HTTP request/response model, and is ideal for scenarios such as real-time data streaming, chat applications, and online games. In C#, both server-side and client-side, you can use WebSocket to communicate.
1、Client use WebSocket
In C#, you can use theunder the namespace
ClientWebSocket
class to create a WebSocket client. The following is a simple example showing how to connect to a WebSocket server and send and receive messages:
using System; using ; using ; using ; using ; using ; /// <summary> /// WebSocket client class for establishing a connection and communicating with a WebSocket server. /// </summary> public class WebSocketClient { /// <summary> /// Client WebSocket instance. /// </summary> private readonly ClientWebSocket _webSocket = new ClientWebSocket(); /// <summary> /// CancellationTokenSource used to cancel the operation. /// </summary> private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); /// <summary> /// Connects to the specified WebSocket server. /// </summary> /// <param name="uri">URI of the WebSocket server.</param> public async Task Connect(string uri) { // Connect to the WebSocket server using the URI provided await _webSocket.ConnectAsync(new Uri(uri), _cancellationTokenSource.Token); } /// <summary> /// Sends a message to the WebSocket server. /// </summary> /// <param name="message">The message string to be sent.</param> public async Task SendMessage(string message) { // Convert message to UTF8 encoded bytes byte[] buffer = Encoding.(message); // Create an ArraySegment that encapsulates the buffer of bytes to be sent ArraySegment<byte> segment = new ArraySegment<byte>(buffer); // Send a message to the WebSocket server await _webSocket.SendAsync(segment, , true, _cancellationTokenSource.Token); } /// <summary> /// Receives messages sent by the WebSocket server. /// </summary> /// <param name="onMessageReceived">A callback function that is called when a message is received.</param> public async Task ReceiveMessage(Action<string> onMessageReceived) { // Continuously receive messages while the WebSocket connection is open while (_webSocket.State == ) { var buffer = new byte[1024]; // Receiving data from a WebSocket server var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationTokenSource.Token); // If the type received is closed, the connection is closed if ( == ) { await _webSocket.CloseAsync(, "Closing", ); break; } // Converts the received bytes to strings and handles them with the callback function var receivedMessage = Encoding.(buffer, 0, ); onMessageReceived(receivedMessage); } } /// <summary> /// Disconnects from the WebSocket server. /// </summary> public async Task Disconnect() { // Cancel receive and send operations _cancellationTokenSource.Cancel(); // Close the WebSocket connection await _webSocket.CloseAsync(, "Closing", ); } }
2、Server side using WebSocket
On the server side, you can use theto support WebSocket.
The following is a simple example of a WebSocket service endpoint configuration:
using ; using ; using ; using ; using System; using ; public class Startup { /// <summary> /// Configure the service container. /// </summary> /// <param name="services">Service Collection.</param> public void ConfigureServices(IServiceCollection services) { // Adding Controller Services (); } /// <summary> /// Configure the application pipeline. /// </summary> /// <param name="app">Application builder.</param> /// <param name="env">Host environment.</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // Enabling exception pages in the development environment if (()) { (); } // Enabling Routing (); // Enabling WebSocket Middleware (); // Configuring the Endpoint Processor (endpoints => { // Mapping the default GET request handler ("/", async context => { await ("Hello World!"); }); // Mapping WebSocket Request Handlers ("/ws", async context => { // Check if the current request is a WebSocket request if () { // Accepting WebSocket Connections using var webSocket = await (); // Continuously listening for WebSocket messages while (true) { // Prepare receive buffer var buffer = new byte[1024 * 4]; // Receiving WebSocket messages var result = await (new ArraySegment<byte>(buffer), ); // If the type received is a close message, close the connection if ( == ) { await (, "Closing", ); break; } // Decode incoming messages var message = Encoding.(buffer, 0, ); ($"Received: {message}"); // Reply message to client await ( new ArraySegment<byte>(Encoding.($"Echo: {message}")), , , ); } } else { // If it is not a WebSocket request, a 400 error is returned = 400; } }); }); } }
In the server-side code above, the WebSocket middleware is first enabled and then maps a/ws
endpoint to handle WebSocket connections.
When a connection request is received, we accept the connection and enter a loop that listens for messages sent by the client and then simply returns a postback message.
3. Description
WebSocket provides C# developers with powerful real-time communication capabilities. Whether you're building complex real-time data streaming applications or simple chat rooms, WebSocket is an option worth considering. By mastering the details of client-side and server-side implementations, you can take full advantage of WebSocket to create high-performance and low-latency real-time applications.
Socket Programming
Socket programming is a fundamental concept in computer network communications that provides the ability to send and receive data between different computers.
In C#, socket programming is done primarily through theunder the namespace
Socket
class to implement. sockets can be used to create the two main types of network connections, TCP/IP and UDP, corresponding to Stream Sockets and Datagram Sockets, respectively.
1. Socket Basics
Socket address family: Specifies the type of network protocol, such asfor IPv4.
Socket type:for TCP.
for UDP.
Socket Protocol:maybe
, respectively, for TCP and UDP.
2、TCP Socket Client
TCP Socket clients are typically used to establish persistent connections and to send and receive data over streams.
The following is a simple TCP client example:
using System; using ; using ; using ; public class TcpClientExample { public static void Main() { try { // Create a new Socket instance using (Socket socket = new Socket(, , )) { // Connecting to the server IPAddress ipAddress = ("127.0.0.1"); IPEndPoint remoteEP = new IPEndPoint(ipAddress, 11000); (remoteEP); // Send data string message = "Hello Server!"; byte[] data = (message); (data); // Receive server response data = new byte[1024]; int bytes = (data); ("Received: {0}", (data, 0, bytes)); } } catch (Exception e) { ("Error: {0}", ()); } } }
3、TCP Socket Server
The TCP Socket Server is responsible for listening for connection requests from clients and processing data from them.
The following is a simple example of a TCP server:
using System; using ; using ; using ; public class TcpServerExample { public static void Main() { try { // Create a new Socket instance using (Socket listener = new Socket(, , )) { // Bind to local port IPAddress ipAddress = ; IPEndPoint localEP = new IPEndPoint(ipAddress, 11000); (localEP); // monitor a connection (10); // Accepting client connections ("Waiting for a connection..."); Socket handler = (); // receive data byte[] data = new byte[1024]; int bytes = (data); ("Text received: {0}", (data, 0, bytes)); // Send Response string response = "Hello Client!"; byte[] responseData = (response); (responseData); } } catch (Exception e) { ("Error: {0}", ()); } } }
4、UDP Socket
UDP sockets are used for connectionless, unreliable network communication and are typically used for real-time data transfer, such as video streaming or gaming.
The following is a simple UDP client and server example:
UDP client
using System; using ; using ; using ; public class UdpClientExample { public static void Main() { try { // Create a new Socket instance using (UdpClient client = new UdpClient()) { // Send data string message = "Hello UDP Server!"; byte[] data = (message); IPEndPoint server = new IPEndPoint(("127.0.0.1"), 11000); (data, , server); // Receive server response data = (ref server); ("Received: {0}", (data)); } } catch (Exception e) { ("Error: {0}", ()); } } }
UDP server
using System; using ; using ; using ; public class UdpServerExample { public static void Main() { try { // Create a new Socket instance using (UdpClient listener = new UdpClient(11000)) { // receive data IPEndPoint client = new IPEndPoint(, 0); byte[] data = (ref client); ("Text received: {0}", (data)); // Send Response string response = "Hello UDP Client!"; byte[] responseData = (response); (responseData, , client); } } catch (Exception e) { ("Error: {0}", ()); } } }
The above example shows how to use C#'sSocket
class to implement TCP and UDP client-server communication.
In practice, issues such as concurrent connections, error handling and resource management may also need to be addressed.
In addition, for TCP communication, an asynchronous programming model is often recommended for performance and resource usage considerations.
IV. C# Network security
Network security is a crucial aspect when programming for the web in C# and involves confidentiality, integrity and availability of data transmission. Here are some key network security points that are essential for building secure web applications:
1、SSL/TLS encryption
Use in C#HttpClient
When theHttpClientHandler
class to configure SSL/TLS related options to secure HTTPS requests.
Here's an example that demonstrates how to use theHttpClientHandler
to configure SSL/TLS settings:
using System; using ; using ; using .X509Certificates; using ; class Program { static async Task Main() { // Creating an Instance of HttpClientHandler var handler = new HttpClientHandler(); // Configuring SSL/TLS Settings // Setting up a delegate for checking server certificates = ; // Set whether to redirect automatically = true; // Setting up a proxy // = true; // = new WebProxy(":8080"); // Creating an HttpClient Instance using var httpClient = new HttpClient(handler); // Setting the request header (); (new MediaTypeWithQualityHeaderValue("application/json")); // Send HTTPS request var response = await ("/data"); // Checking the response status if () { var content = await (); (content); } else { ($"Failed to retrieve data: {}"); } } }
account for
-
ServerCertificateCustomValidationCallback
: This attribute allows you to specify a delegate that will be used to validate the server's SSL certificate. In this example, we use the, it will accept any certificate, which may be useful in a test environment, but it is highly recommended to use stricter certificate validation logic in a production environment.
-
AllowAutoRedirect
: This property controls whether or not to allowHttpClient
Automatically handle redirects. By default, it is turned on. -
UseProxy
cap (a poem)Proxy
: You can configure these two properties if you need to send requests through a proxy server. -
DefaultRequestHeaders
: Used to set the default header for requests, such asAccept
to specify the desired response format.
Caveats:
- In practice, it is not recommended to use
DangerousAcceptAnyServerCertificateValidator
, because it bypasses normal certificate validation and may expose the application to man-in-the-middle attacks. In a production environment, you should implement your own certificate validation logic to ensure that only valid and trusted certificates are accepted. - Additionally, if the application needs to handle a specific SSL/TLS protocol version or encryption suite, this can also be handled through the
SslProtocols
Attributes are further customizedHttpClientHandler
The SSL/TLS settings of the - For example, it can be set to
SslProtocols.Tls12
maybeSslProtocols.Tls13
, to limit the version of the protocol used.
2、Password security storage
Securely storing passwords in C# is a crucial practice, especially when it comes to user accounts and sensitive information. To protect passwords from being leaked or cracked, avoid storing passwords in plaintext and instead use encryption or hashing.
Here are some recommended practices:
- Using Hash Functions
Using a secure hash function, such as SHA-256 or SHA-512, it is possible to convert a password into a fixed-length digest. However, simple hashes are vulnerable to rainbow table attacks and therefore require the addition of a salt value (salt).
Sample code:
using System; using ; using ; public static class PasswordHasher { public static string HashPassword(string password, byte[] salt) { using (var sha256 = ()) { var passwordSalted = Encoding.(password + Encoding.(salt)); var hash = (passwordSalted); return Convert.ToBase64String(hash); } } public static byte[] GenerateSalt() { using (var rng = new RNGCryptoServiceProvider()) { var salt = new byte[32]; (salt); return salt; } } } // usage example byte[] salt = (); string hashedPassword = ("password123", salt);
- Using salted hashes
A randomly generated salt value is combined with the password before hashing it. This prevents rainbow table attacks and brute force cracking.
- Using slow hash functions
Using slow hash functions like PBKDF2, bcrypt, scrypt, or Argon2 can be significantly more difficult to crack because they are designed with preventing brute force cracking in mind.
Sample code:
using System; using ; using ; using ; public static class PasswordHasher { public static string HashPasswordUsingBcrypt(string password) { using (var bcrypt = new Rfc2898DeriveBytes(password, 16, 10000)) // 16 bytes of salt, 10000 iterations { return Convert.ToBase64String((24)); // 24 bytes of hash } } } // usage example string hashedPassword = ("password123");
- Storing Hash and Salt Values
In the database, in addition to storing the hashed password, the salt value used for that password should also be stored so that the hash can be recalculated using the same salt value during verification.
- Verify Password
When a user logs in, the hash and salt values are retrieved from the database, the entered password is hashed using the same hash function and salt value, and then compared to the stored hash value.
Sample code:
public static bool VerifyPassword(string inputPassword, string storedHash, byte[] storedSalt) { string hashOfInput = (inputPassword, storedSalt); return hashOfInput == storedHash; }
- Do not store answers to password reset questions
Answers to password reset questions should be handled as securely as passwords, avoiding storage in clear text.
Core provides built-in methods for password hashing and validation, and using these frameworks is often more secure than implementing them manually. In summary, storing passwords securely involves the use of strong hashing algorithms, salting, appropriate number of iterations, and storage mechanisms. Also, stay on top of the latest security practices and update your code regularly to address new threats.
3、Prevent SQL injection
Use parameterized queries or ORM tools, etc. to prevent SQL injection attacks.
string query = "SELECT * FROM SystemUser WHERE Username = @username"; SqlCommand command = new SqlCommand(query, connection); ("@username", inputUsername);
4. Prevention of cross-site scripting attacks (XSS)
Proper encoding and validation of user input to prevent malicious script injection.
string userContent = "<script>alert('XSS');</script>"; string encodedContent = (userContent);
5. Prevention of cross-site request forgery (CSRF)
MVC can use mechanisms such as Anti-Forgery Token to prevent CSRF attacks.
@()
6. Authentication and authorization
Use more advanced authentication mechanisms such as JWT (JSON Web Token) and implement appropriate authorization policies in your application.
[Authorize] public ActionResult SecureAction() { // safe operation }
7. Judgment document security
In C#, determining whether a file is "safe" can be done from a number of perspectives, usually involving the source of the file, its contents, permissions, and whether it contains potentially malicious code.
Below I'll describe a few possible ways to check the security of a file:
- Checking the origin of documents
Ensure that the file is downloaded or fetched from a trusted source. In web applications, you can use theContent-Disposition
response header to check that the file is provided as an attachment and that the filename is as expected.
- Verify the type and extension of the file
Determine if the file type is what is expected by checking the file's extension or MIME type, e.g., if an image file is expected, then only accept the.jpg
, .png
etc. extensions.
private bool IsFileSafeByExtension(string filePath) { string[] allowedExtensions = { ".jpg", ".png", ".gif" }; string extension = (filePath).ToLower(); return (extension); }
- Checking the contents of the document
Use file signatures or magic numbers to verify that the actual type of a file matches the declared type and to prevent extension spoofing.
private bool IsFileSafeByContent(string filePath) { byte[] magicNumbers = (filePath); if ( >= 2 && magicNumbers[0] == 0xFF && magicNumbers[1] == 0xD8) // JPEG { return true; } // Add checks for other formats... return false; }
- Scan for viruses and malware
Use anti-virus software or online APIs to check files for viruses or malware.
private bool IsFileSafeFromVirus(string filePath) { // Example: Using an antivirus API to scan the file. // This is just a placeholder and you should replace it with actual antivirus software integration. return (filePath); }
- Checking file permissions
Ensure that files have the correct permissions to prevent unauthorized access.
private bool IsFileSafeByPermissions(string filePath) { var fileInfo = new FileInfo(filePath); var security = (); // Check permissions here... return true; // Placeholder logic }
- File Size Check
Limit the file size to avoid consuming too much disk space or memory.
private bool IsFileSafeBySize(string filePath, long maxSizeInBytes) { var fileInfo = new FileInfo(filePath); return <= maxSizeInBytes; }
- Content Security Policy (CSP)
In web applications, CSPs are used to restrict the types and sources of resources loaded to prevent attacks such as XSS.
- Example of synthesized check function
private bool IsFileSafe(string filePath) { return IsFileSafeByExtension(filePath) && IsFileSafeByContent(filePath) && IsFileSafeFromVirus(filePath) && IsFileSafeByPermissions(filePath) && IsFileSafeBySize(filePath, 1024 * 1024); // Limit to 1MB }
Please note that the above code snippets are intended as examples only, and real-world applications may require adjustments and additions to specific implementation details, such as the introduction of actual virus scanning libraries or APIs, as well as more complex permission and content checking logic.
Security checks are multi-layered and need to be integrated with specific application scenarios and requirements.
8、Safe Cookie Handling
Cookies are a common mechanism used in Web development to store user information. They can save small amounts of data in the client browser so that the server can track information such as user preferences, login status, and so on. However, if cookies are not handled properly, they can cause serious security issues such as data leakage, session hijacking and cross-site scripting attacks (XSS). Therefore, it is crucial to ensure that cookies are handled securely.
Here are some best practices that should be followed when dealing with cookies:
- Use HTTPS:When transferring cookies, be sure to encrypt the connection using HTTPS, which prevents Man-in-the-Middle Attacks and protects cookie data from eavesdropping.
- Set the HttpOnly flag:Marking cookies as HttpOnly prevents JavaScript scripts from accessing cookies, thus reducing the risk of cross-site scripting attacks (XSS).
- Set the Secure flag:When cookies are marked as Secure, they will only be sent over an HTTPS connection, ensuring that the data is secure in transit.
- Limit the valid paths and domains for cookies:By setting the Path and Domain attributes of cookies, you can control which pages can access specific cookies, reducing the attack surface.
- Use the SameSite property:The SameSite property controls whether cookies are sent with cross-site requests, reducing the possibility of cross-site request forgery (CSRF) attacks. One of three modes can be selected: Strict, Lax or None.
- Set a reasonable expiration time:Setting an appropriate expiration time for cookies avoids the security risks posed by permanent cookies and makes it easier to clean up user information that is no longer needed.
- Regularly review and update cookie policies:Regularly check cookie usage to ensure that all cookie settings comply with the latest security standards and privacy regulations.
By following these best practices, application security can be greatly enhanced to protect user data from malicious attacks. Secure cookie handling in web development is not only a technical requirement, but also a demonstration of responsibility for user privacy and data security.
using System; using ; public class CookieHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { // Create a new cookie object HttpCookie cookie = new HttpCookie("UserSession"); // Setting Cookie Values = "123456"; // Assuming this is a unique identifier for the user // Setting the cookie expiration time = (1); // Setting cookies to expire after one day // Setting the HttpOnly property for added security = true; // If your site supports HTTPS, set the Secure attribute if () = true; // Adding a Cookie to the Response (cookie); } public bool IsReusable { get { return false; } } }
In .NET Core or .NET 6+, different APIs are used to handle cookies, for exampleunder the namespace
IResponseCookies
Interface.
V. Summary
With the article's comprehensive introduction to C# network programming, I believe that I have gained knowledge and understanding of this piece of content. From simple HTTP requests to complex socket communications, from asynchronous programming models to the application of security protocols, each step lays a solid foundation for us to build modern network applications. In real projects, in-depth learning and practicing these knowledge points according to the needs will help to improve the .NET developers' ability in the field of network programming. Continuous learning and practicing is a sure way to become a good .NET developer.
If you find this article useful, welcome to join WeChat [DotNet technician] community to share ideas and grow with other tech-loving peers.