Linux应用实战之网络服务器(五) 登录服务器初步调试
创始人
2025-01-16 19:03:47
0

0、前言

准备做一个Linux网络服务器应用实战,通过网页和运行在Linux下的服务器程序通信,这是第五篇,编写服务器程序,与编写好的登录界面进行初步调试。

1、服务器编程

1.1 TCP服务器编程

在之前的登录界面中,我们指定了登录服务器的IP和端口号,其中IP即为服务器的IP地址,端口号即为服务器监听的端口,服务器编程即为TCP编程,具体流程可参考之前的文章Linux应用 TCP网络编程,HTTP每次与服务器通信都会创建一个TCP连接,初步编写代码如下,接收客户端发送的数据进行打印:

#include  #include  #include  #include  #include  #include  #include  #include    #define PORT 8081 #define MAX_SIZE 1024 * 10  int main()  {     int server_fd, new_socket;     struct sockaddr_in address;     int addrlen = sizeof(address);      // 创建TCP套接字     if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {         perror("socket failed");         exit(EXIT_FAILURE);     }      address.sin_family = AF_INET;     address.sin_addr.s_addr = INADDR_ANY;     address.sin_port = htons(PORT);      // 绑定套接字到指定端口     if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {         perror("bind failed");         exit(EXIT_FAILURE);     }      // 监听端口     if (listen(server_fd, 3) < 0) {         perror("listen failed");         exit(EXIT_FAILURE);     }      printf("Server listening on port %d\n", PORT);      while(1)     {         printf("waiting......\n");         // 接受连接         if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0)          {             perror("accept failed");             exit(EXIT_FAILURE);         }         char rcvbuffer[MAX_SIZE] = {0};                  bzero((char *)rcvbuffer, sizeof(rcvbuffer));         int bytesReceived = read(new_socket, rcvbuffer, MAX_SIZE);         printf("bytesReceived = %d\n", bytesReceived);         if (bytesReceived > 0)          {             printf("Received request: \n%s\n", rcvbuffer);                  }                   close(new_socket);     }      close(server_fd);      return 0; }

在虚拟机上运行该服务程序,注意网页中的IP地址和端口号要与虚拟机地址还有服务器监听的端口号一致,测试结果如下:

1.2 OPTIONS处理

在登录界面连接的过程中服务器并没有按期望收到POST请求,而是收到了OPTIONS,查阅了相关资料:OPTIONS请求在CORS(跨源资源共享)机制中是一个预检请求(preflight request)。当浏览器遇到一个非简单请求(non-simple request)时,它会在实际请求之前自动发送一个OPTIONS请求到服务器,以检查服务器是否允许这个跨域请求。

因此,服务器首先需要处理OPTIONS请求然后才能收到正常的POST,简单添加一下处理代码:

if (bytesReceived > 0)  {     printf("Received request: \n%s\n", rcvbuffer);      if(strstr(rcvbuffer, "OPTIONS") != NULL)     {         // 构造CORS响应头部           const char* headers = "HTTP/1.1 200 OK\r\n"                            "Access-Control-Allow-Origin: *\r\n"                              "Access-Control-Allow-Methods: POST, OPTIONS\r\n"                              "Access-Control-Allow-Headers: Content-Type\r\n"                              "Access-Control-Max-Age: 86400\r\n"                              "X-Content-Type-Options: nosniff\r\n"                            "Cache-Control: no-cache, no-store, must-revalidate\r\n"                            "Content-Length: 0\r\n"                              "Connection: close\r\n"                              "\r\n";               printf("send:\n%s", headers);         send(new_socket, headers, strlen(headers), 0);     } }  

继续测试,可以收到携带账号密码的POST请求:

1.3  登录请求处理

收到登录请求后可以获取到POST中携带的用户名和密码,服务器验证通过后回复成功,登录侧就可以显示成功,先简单添加一下登录处理,检测到登录后直接回复成功,修改代码如下:

if (bytesReceived > 0)  {     printf("Received request: \n%s\n", rcvbuffer);      if(strstr(rcvbuffer, "OPTIONS") != NULL)     {         // 构造CORS响应头部           const char* headers = "HTTP/1.1 200 OK\r\n"                            "Access-Control-Allow-Origin: *\r\n"                              "Access-Control-Allow-Methods: POST, OPTIONS\r\n"                              "Access-Control-Allow-Headers: Content-Type\r\n"                              "Access-Control-Max-Age: 86400\r\n"                              "X-Content-Type-Options: nosniff\r\n"                            "Cache-Control: no-cache, no-store, must-revalidate\r\n"                            "Content-Length: 0\r\n"                              "Connection: close\r\n"                              "\r\n";               printf("send:\n%s", headers);         send(new_socket, headers, strlen(headers), 0);     }     else if(strstr(rcvbuffer, "username") != NULL)     {          const char* json_response = "{\"status\":\"ok\"}";           int json_response_length = strlen(json_response);                      // 构造HTTP响应头部           const char* http_version = "HTTP/1.1";           const char* status_code = "200";           const char* status_message = "OK";           const char* access_control_allow_origin = "Access-Control-Allow-Origin: *\r\n";         const char* content_type = "Content-Type: application/json\r\n";           const char* content_length = "Content-Length: ";           char content_length_header[32];           snprintf(content_length_header, sizeof(content_length_header), "%d", json_response_length);                      // 构造完整的HTTP响应           char response[1024] = {0}; // 假设响应不会超过1024字节           snprintf(response, sizeof(response),                    "%s %s %s\r\n"                   "%s"                  "%s"                    "%s%s\r\n"                    "\r\n"                    "%s",                    http_version, status_code, status_message, access_control_allow_origin,                   content_type, content_length_header, "\r\n",                    json_response);                printf("send:\n%s\n", response);         send(new_socket, response, strlen(response), 0);     }  }   

再进行登录测试发现可以显示登录成功:

1.4 查询请求

修改查询界面HTML代码,添加按键点击后向服务器发送不同的请求命令:

在服务器端添加简单的匹配字符串处理:

 else if(strstr(rcvbuffer, "query1") != NULL) { 	char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nHello from server Reply to query1"; 	printf("send:\n%s\n", response); 	send(new_socket, response, strlen(response), 0); } else if(strstr(rcvbuffer, "query2") != NULL) { 	char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nHello from server Reply to query2"; 	printf("send:\n%s\n", response); 	send(new_socket, response, strlen(response), 0); } else if(strstr(rcvbuffer, "query3") != NULL) { 	char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nHello from server Reply to query3"; 	printf("send:\n%s\n", response); 	send(new_socket, response, strlen(response), 0); } else if(strstr(rcvbuffer, "query4") != NULL) { 	char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nHello from server Reply to query4"; 	printf("send:\n%s\n", response); 	send(new_socket, response, strlen(response), 0); }

最终实现如下效果,点击不同的按键收到服务器不同的回复:

2、优化

我们在服务器测编写文件发送接口,将HTML文件发送至客户端,客户端只需要输入服务器的地址端口等信息就可以从服务器获取登录界面,登录界面登录成功后向服务器发送请求界面,然后在请求界面进行数据请求。

基于上述分析,在服务器测添加相关代码,最终代码整合如下:

#include  #include  #include  #include  #include  #include  #include  #include    #define PORT 8081 #define MAX_SIZE 1024 * 5  // 发送html文件给客户端 void vSendHtmlToCllient(const char *filepath,int new_socket) {     FILE *file;     char *response_header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\rAccess-Control-Allow-Origin: *\r\nCache-Control: no-cache, no-store, must-revalidate\r\nX-Content-Type-Options: nosniff\r\n\r\n";          // 发送响应头部     send(new_socket, response_header, strlen(response_header), 0);     printf("send:\n%s\n", response_header);          // 读取文件内容并发送     char buffer[1024] = {0};     size_t bytes_read;      file = fopen(filepath, "r");     if (file == NULL)      {         perror("fopen");         exit(EXIT_FAILURE);     }          while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0)      {         send(new_socket, buffer, bytes_read, 0);         printf("%s", buffer);         bzero(buffer, sizeof(buffer));     } }  int main()  {     int server_fd, new_socket;     struct sockaddr_in address;     int addrlen = sizeof(address);     char rcvbuffer[MAX_SIZE] = {0};      // 创建TCP套接字     if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {         perror("socket failed");         exit(EXIT_FAILURE);     }      address.sin_family = AF_INET;     address.sin_addr.s_addr = INADDR_ANY;     address.sin_port = htons(PORT);      // 绑定套接字到指定端口     if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {         perror("bind failed");         exit(EXIT_FAILURE);     }      // 监听端口     if (listen(server_fd, 3) < 0) {         perror("listen failed");         exit(EXIT_FAILURE);     }      printf("Server listening on port %d\n", PORT);      while(1)     {         printf("waiting......\n");         // 接受连接         if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0)          {             perror("accept failed");             exit(EXIT_FAILURE);         }                  bzero((char *)rcvbuffer, sizeof(rcvbuffer));         int bytesReceived = read(new_socket, rcvbuffer, MAX_SIZE);         printf("bytesReceived = %d\n", bytesReceived);         if (bytesReceived > 0)          {             printf("Received request: \n%s\n", rcvbuffer);              // 只做简单处理             if(strstr(rcvbuffer, "OPTIONS") != NULL)             {                 // 构造CORS响应头部                   const char* headers = "HTTP/1.1 200 OK\r\n"                                    "Access-Control-Allow-Origin: *\r\n"                                      "Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate"                                    "Access-Control-Allow-Methods: POST, OPTIONS\r\n"                                      "Access-Control-Allow-Headers: Content-Type\r\n"                                      "Access-Control-Max-Age: 86400\r\n"                                      "X-Content-Type-Options: nosniff\r\n"                                    "Cache-Control: no-cache, no-store, must-revalidate\r\n"                                    "Content-Length: 0\r\n"                                      //"Connection: close\r\n"                                      "\r\n";                               printf("send:\n%s", headers);                 send(new_socket, headers, strlen(headers), 0);             }             else if(strstr(rcvbuffer, "username") != NULL)             {                  const char* json_response = "{\"status\":\"ok\"}";                   int json_response_length = strlen(json_response);                                      // 构造HTTP响应头部                   const char* http_version = "HTTP/1.1";                   const char* status_code = "200";                   const char* status_message = "OK";                   const char* access_control_allow_origin = "Access-Control-Allow-Origin: *\r\n";                 const char* access_control_allow_Cache = "Cache-Control: no-store, no-cache, must-revalidate\r\n";                 const char* content_type = "Content-Type: application/json\r\n";                   const char* content_length = "Content-Length: ";                   char content_length_header[32];                   snprintf(content_length_header, sizeof(content_length_header), "%d", json_response_length);                                      // 构造完整的HTTP响应                   char response[1024] = {0}; // 假设响应不会超过1024字节                   snprintf(response, sizeof(response),                            "%s %s %s\r\n"                           "%s"                          "%s"                            "%s"                          "%s%s\r\n"                            "\r\n"                            "%s",                            http_version, status_code, status_message, access_control_allow_origin, access_control_allow_Cache,                          content_type, content_length_header, "\r\n",                            json_response);                                printf("send:\n%s\n", response);                 send(new_socket, response, strlen(response), 0);             }             else if(strstr(rcvbuffer, "query1") != NULL)             {                 char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nHello from server Reply to query1";                 printf("send:\n%s\n", response);                 send(new_socket, response, strlen(response), 0);             }             else if(strstr(rcvbuffer, "query2") != NULL)             {                 char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nHello from server Reply to query2";                 printf("send:\n%s\n", response);                 send(new_socket, response, strlen(response), 0);             }             else if(strstr(rcvbuffer, "query3") != NULL)             {                 char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nHello from server Reply to query3";                 printf("send:\n%s\n", response);                 send(new_socket, response, strlen(response), 0);             }             else if(strstr(rcvbuffer, "query4") != NULL)             {                 char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nHello from server Reply to query4";                 printf("send:\n%s\n", response);                 send(new_socket, response, strlen(response), 0);             }             else if(strstr(rcvbuffer, "gethtml") != NULL)             {                 vSendHtmlToCllient("./Require.html",new_socket);             }             else if(strstr(rcvbuffer, "Login") != NULL)             {                 vSendHtmlToCllient("./Login.html",new_socket);             }         }           close(new_socket);     }      close(server_fd);     return 0; }

登录界面也需要添加登录成功后获取请求界面的相关代码,最终代码如下:

                 Login            

Login



请求界面最终代码如下:

    Query Interface    

Query Interface

登录界面保存文件名称为Login.html,请求界面保存文件名称为Require.html,两个文件和服务器程序放在同一目录下,测试流程大致如下:

测试结果如下:

3、总结

本文是 Linux网络服务器应用实战的服务器和客户端初步调试篇,实现了客户端和服务器的初步通信,通过Web客户端可以和服务器进行简单通信,实现数据的简单收发。

相关内容

热门资讯

hadoop hdfs的dat... hadoop hdfs的datanode的一块磁盘故障导致服务器的根分区写满了 作者 伍增田 Tom...
GPU桌面虚拟化HyperV实...  目录创建虚拟机添加GPU刷入显卡驱动创建虚拟机a说科技:1台电脑当10台用ÿ...
第一章 TCP/IP 协议 作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。  座右铭ÿ...
XShell连接实验室服务器 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮...
discord创建机器人帐户并... discord创建机器人帐户并邀请到自己的服务器使用您最喜欢的构建管理器将 Javacord 添加为...
黑暗之焰宇宙:乐高宇宙服务器仿... 黑暗之焰宇宙:乐高宇宙服务器仿真器简介黑暗之焰宇宙(Darkflame ...
在业务高峰期拔掉服务器电源是一... [mysqld]innodb_force_recovery = 1如果innodb_forc...
Linux smbd命令教程:... Linux smbd命令介绍smbd是Samba套件的一部分。smbd是一个服务器守护进程ÿ...
Lottie动画资源放到服务器... object FileDownloadConstant { const val filePath &...
iPhone设置163邮箱 收... iPhone设置163邮箱 收件服务器和发件服务器填写内容收件服务器 主机名: imap.163.c...