TCP/IP protocol Learning (4) C # Socket-based Web Server --- static resource processing,
A Web server is the host of a Web resource. It must process requests from a browser on the user side and specify the corresponding Web resource to be returned to the user. These resources not only include HTML files, JS scripts, and jpg images, it also includes dynamic content generated by software. To meet the above requirements, a complete Web Server workflow:
1) The server obtains the http request packet sent to the server by the browser through the TCP/IP connection.
2) the HTTP request is analyzed by the HTTP parsing engine of the Web server to obtain the request method, resource address, and other information, and then processed.
3) For static requests, query the files in the request url path on the server and return (if not Found, 404 No Found is returned ).
4) involves dynamic requests, suchCGI, AJAX, ASPAccording to the http method.
The core of the Web server is C # Socket communication, http parsing engine, static resource file search, dynamic data receiving and sending, this section mainly implements the first three parts (Web servers that can query static resources) because of the individual writing progress. Dynamic Data processing involves different processing methods: CGI, AJAX, and ASP, summarize relevant knowledge after completion.
1. C # Socket communication
C # Socket encapsulates the TCP/IP protocol to implement APIs that meet TCP communication requirements. In the B/S architecture, the processing on the server is basically the same as that on the C/S connection. The main tasks include creating Socket sockets, listening for connections, establishing connections, obtaining requests, processing and returning data, close the connection.
The program entry function monitors client requests in polling mode.
// Create a listener Thread Listen_thread = new Thread (socket_listen); Listen_thread.IsBackground = false; Listen_thread.Start ();
Listen to the thread, create a Socket, bind and listen to the specified port, wait for the connection to be established, after the connection is established, take into account the high concurrency characteristics of web page requests, adopt another thread to process the established connection, to achieve the concurrent server mode.
Socket server_socket = null; // The listening IP address and port are used as the server. Only the local IP address or the loopback address can be bound (it cannot conflict with other system process ports) // If bound to this IP address, other devices in the LAN can access the current Server string host = "127.0.0.1" through http: // host: port; int port = 3000; IPAddress ip = IPAddress. parse (host); IPEndPoint ipe = new IPEndPoint (ip, port); // create a Socket, bind it to the specified port, and start listening to server_socket = new Socket (AddressFamily. interNetwork, SocketType. stream, ProtocolType. tcp); server_socket.Bind (ipe); server_socket.Listen (100); Console. writeLine ("Server Binding at" + host + ":" + port. toString () + "... "); Console. writeLine ("Wait for connect .... "); while (true) {Socket CurrentSocket; // the three-way handshake is successful. Create a connection CurrentSocket = server_socket.Accept (); Console. writeLine ("New TCP Socket Create... "); // a single Thread is used to process server sending and receiving. In the concurrent server mode, ParameterizedThreadStart tStart = new ParameterizedThreadStart (socket_process); Thread process_thread = new Thread (tStart); process_thread.IsBackground = false; process_thread.Start (CurrentSocket );}
Connection processing. socket communication processing is mainly responsible for receiving the data generated by the connection and submitting the data processed by the http engine to the client browser.
Socket CurrentSocket = (Socket) obj; try {string recvStr = ""; byte [] recvBytes = new byte [2000]; int length; // obtain the data transmitted by the current Socket connection, and convert it to the ASCII format length = CurrentSocket. receive (recvBytes, recvBytes. length, 0); recvStr = Encoding. ASCII. getString (recvBytes, 0, length); // processed by the http engine. The returned data is byte [] bs = http_engine (recvStr, length ); // The data processed by the socket sending engine CurrentSocket. send (bs, bs. length, 0); Console. writeLine ("File Send Finish, Socket Close .... \ r \ n "); // close the socket connection CurrentSocket. close ();} catch (Exception exception) {Console. writeLine (exception); CurrentSocket. close ();}
The implementation of the C # Socket communication architecture is no different from that of the C/S structure. If you have learned about Socket, you can easily implement the above socket communication architecture. However, the following section describes the http parsing engine, the core of Web server implementation, which is the biggest difference between the B/S architecture and the C/S architecture.
2. http parsing Engine
The Web server processes browser request data packets and returns the specified http resource or data. These are implemented by the http parsing engine. before writing the http parsing engine, we need to know the received data for subsequent processing. Here we provide the http request packet captured through WireShark:
Although the HTTP request package contains a lot of content, but the current implementation of the function is less, so only the starting line of the http message can be concerned, and the first field can be directly discarded and not processed, if the authentication mechanism is used in the future, such as whitelist, blacklist filtering, account/password protection, and resource permission management, the first part still needs to be processed.
For the starting line of an http packet, it is separated by space and separated by '\ r \ n' as the end and header. GET: HTTP method, '/': Resource path url, HTTP/1.1: Protocol version. Refer to the http authoritative guide. Common HTTP methods include GET, PUT, DELETE, POST and HEAD: static servers in this section mainly involve the GET method. After learning how to parse an HTTP request message, we first define an HTTP message parsing structure to store the parsed information.
Public class HTTPPrase {// http Method public string http_method; // http resource public string url; // http version Number public string version; // request webpage type for url resolution public string type ;};
Next we will start to use the String method provided by C # To intercept http packets to initialize parameters in the above structure.
Int pos; // Get the http packet header and convert it to lowercase Based on \ r \ n for later processing. // Get/HTTP/1.1/r/n pos = str. indexOf ("\ r \ n"); string str_head = str. substring (0, pos); str_head = str_head.ToLower (); // truncate the start row based on ''and assign the value to the corresponding string [] arr = Regex. split (str_head, @ "\ s +"); HTTPServer. HTTPPrase http_head = new HTTPServer. HTTPPrase (); http_head.http_method = arr [0]; // "Get" http_head.url = arr [1]; // "/" http_head.version = arr [2]; // "HTT P/1.1 "// determine whether any dynamic data obtained or submitted through ajax is http_head.ajax_status = str_head.IndexOf (". ajax ")! =-1? True: false; byte [] bs = http_head.ajax_status = true? Ajax_process (http_head, str): static_process (http_head, str); return bs;
You can submit the data to the backend interface for processing. Because dynamic web page processing requires the combination of the web page and the backend, the workload is heavy. Therefore, this section mainly describes the implementation of static Web page requests. Generally, lan Web requests directly access the server through ip + port mode. Therefore, the url of the first request received is '/'. In this case, you must set the webpage to index.html. For other requests, the url value is generally '/xxx. js ", '/xxx/xxx.jpg", etc. When reading data from the server, we need to define an absolute address. Therefore, we need to add the root address of the resource storage in front.Current folder of the program + htmlAs the root address of the resource, and the data path stored by the operating system is \ xxx. js, so the url data in the request must be replaced with '\' (to ensure that the escape character can be converted to a path character, '\' must be used to represent the actual '\'), in addition, in order to return the correct Content-Type field for the subsequent http Response Message, there is also the intercept '. to obtain the type of the request file.
// Obtain the folder of the current program string url_str = System. appDomain. currentDomain. setupInformation. applicationBase; if (string. compare (head. url, "/") = 0) {// For the first request 127.0.0.1: 3000/return index.html url_str + = "html \ index.html"; head. type = "html";} else {// other requests, such as/spring. replace js... \ html \ spring. js facilitates C # querying the file path url_str = url_str + "html \" + head. url. substring (1); url_str = url_str.Replace ('/', '\'); int pos = url_str.IndexOf ('. '); // obtain the webpage type head of the current request. type = url_str.Substring (pos + 1 );}
So far, the entire http parsing process has been completed, includingHttp method. The url resource address is obtained and converted to the windows system path, Protocol version.Obtain three parts. For static webpage requests, it will be relatively simple in the future. The resources in the query system path are opened through the file stream and placed in the memory in the form of a response stream as the body of the http Response Message.
// Open the file using (FileStream fs = new FileStream (url_str, FileMode. open, FileAccess. read) {// StreamReader temp = new StreamReader (fs, Encoding. default); int fslen = (int) fs. length; byte [] fbyte = new byte [fslen]; int r = fs. read (fbyte, 0, fslen); fs. close ();//......}
After the file is opened successfully, we need to generate an http response packet. The http response packet is the same as the request packet, which consists of three parts.
Status Code: it provides a convenient way for the client to understand the transaction processing result. Main implementations include:
HTTP/1.1 200 OKThe request is correct. The entity's subject contains the requested resources.
HTTP/1.1 400 Bad RequestNotifies the client that it has sent an incorrect request.
HTTP/1.1 401 UnauthorizedReturn together with the appropriate header to notify the client to perform corresponding authentication
HTTP/1.1 404 No FoundThe server cannot find the requested URL.
Response Header: provides additional server-related messages to the client. This project is easy to implement:
Content-type: CurrentType \ r \ n
Server: C # Web \ r \ n
Content-Length: CurrentLength \ r \ n
Connection: close
Here, Contenet-type needs to be replaced based on the type we obtained above. Common replacement rules are described here.
The Content-Length field is the Length of the http Response Message body, that is, the total Length of the resource we obtain (fslen in the above section, when the response header and body data are integrated and sent to the client through socket, the whole process of static server is realized.
String HTTP_Current_Head = HTTPServer. HTTP_ OK _Head.Replace ("CurrentLength", Convert. toString (fslen); // return different header types based on different URLs for detailed comparison see http://tool.oschina.net/commons switch (head. type) {case "jpg": HTTP_Current_Head = HTTP_Current_Head.Replace ("CurrentType", "application/x-jpg"); break; case "png": HTTP_Current_Head = HTTP_Current_Head.Replace ("CurrentType ", "image/png"); break; case "html": HTTP_Current_Head = HTTP_Current_Head.Replace ("CurrentType", "text/html"); break; case "gif ": HTTP_Current_Head = HTTP_Current_Head.Replace ("CurrentType", "image/gif"); break; case "js": HTTP_Current_Head = HTTP_Current_Head.Replace ("CurrentType", "application/x-javascript "); break; case "asp": HTTP_Current_Head = HTTP_Current_Head.Replace ("CurrentType", "text/asp"); break; default: HTTP_Current_Head = HTTP_Current_Head.Replace ("CurrentType ", "text/html"); break;} send_str = HTTPServer. HTTP_ OK _Start + HTTP_Current_Head; byte [] head_byte = new byte [send_str.Length]; head_byte = Encoding. UTF8.GetBytes (send_str); // string stream merging to generate the Sending File // The byte []-> string, string merging, string-> byte [], this method reads garbled images // Therefore, It is modified to string merging, string-> byte [], byte [] merging, bytes [] send_byte = new byte [send_str.Length + fbyte. length]; Buffer. blockCopy (head_byte, 0, send_byte, 0, head_byte.Length); Buffer. blockCopy (fbyte, 0, send_byte, head_byte.Length * sizeof (byte), fbyte. length); Console. writeLine ("File Send .... "); return send_byte;
So far, a simple static web server has been implemented to put the resource files you want to accessCurrent Program Folder/html/And define the first page as index.html. Click the server program and enterHttp: // FIG: 3000,You can view the returned webpage.
For more information about the program, see download a Web server.