• 05_TCP并发服务器


    知识点1【TCP并发服务器】

    1、多线程(常用)

    2、解决上述问题:端口复用 仅仅是端口的复用

    3、并发服务器 多进程实现

    总结:

     知识点2【HTTP协议】 HTTP基于TCP

    1、HTTP协议的概述

    2、Webserver 通信过程

    3、Web编程开发

    知识点1【TCP并发服务器】

    并发服务器:同时 服务于 多个客户端

    TCP并发服务器:本质是TCP服务器同时服务多个客户端

    TCP并发服务器的注意点:

    TCP服务器、提取多个客户端、开启进程或线程处理每个客户端

    1、多线程(常用)

    1. #include<stdio.h>
    2. #include<sys/types.h>
    3. #include<sys/socket.h>
    4. #include<netinet/in.h>
    5. #include<arpa/inet.h>
    6. #include<string.h>
    7. #include<pthread.h>
    8. //TCP并发ECHO服务器(并发回执服务器---客户端给服务器发啥 服务器就给客户端回啥)
    9. void* deal_client_fun(void *arg)//arg = &new_fd
    10. {
    11. //并发服务器的核心服务代码(各不相同)
    12. //通过arg获得已连接套接字
    13. int fd = *(int *)arg;
    14. while(1)//以下语句是服务器的核心代码
    15. {
    16. //获取客户端请求
    17. char buf[128]="";
    18. int len = recv(fd,buf,sizeof(buf), 0);
    19. if(len == 0)
    20. break;
    21. //回应客户端
    22. send(fd, buf, len, 0);
    23. }
    24. close(fd);
    25. }
    26. int main()
    27. {
    28. //1、创建tcp监听套接字
    29. int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    30. if(sockfd < 0)
    31. {
    32. perror("socket");
    33. }
    34. int yes = 1;
    35. setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(yes));
    36. //2、给TCP监听套接字 bind固定的IP以及端口信息
    37. struct sockaddr_in my_addr;
    38. bzero(&my_addr,sizeof(my_addr));
    39. my_addr.sin_family = AF_INET;
    40. my_addr.sin_port = htons(8000);
    41. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    42. int ret = bind(sockfd, (struct sockaddr *)&my_addr,sizeof(my_addr));
    43. if(ret == -1)
    44. {
    45. perror("bind");
    46. }
    47. //3、调用listen 将sockfd主动变被动 同时创建链接队列
    48. listen(sockfd, 10);
    49. //4、提取完成链接的客户端 accept
    50. //accept调用一次 只能提取一个客户端
    51. while(1)
    52. {
    53. struct sockaddr_in cli_addr;
    54. socklen_t cli_len = sizeof(cli_addr);
    55. int new_fd = accept(sockfd,(struct sockaddr *)&cli_addr , &cli_len);
    56. //遍历客户端的信息ip port
    57. unsigned short port=ntohs(cli_addr.sin_port);
    58. char ip[16]="";
    59. inet_ntop(AF_INET,&cli_addr.sin_addr.s_addr, ip, 16);
    60. printf("已有客户端:%s:%hu连接上了服务器\n", ip, port);
    61. //对每一个客户端 开启一个线程 单独的服务器客户端
    62. pthread_t tid;
    63. pthread_create(&tid,NULL, deal_client_fun, (void *)&new_fd);
    64. //线程分离
    65. pthread_detach(tid);
    66. }
    67. //关闭监听套接字
    68. close(sockfd);
    69. return 0;
    70. }

     运行结果:

    上述代码 如果客户端 正常退出 不会有啥影响,但是如果服务器 意外退出 绑定的端口信息来不及释放,就会造成 系统临时占用服务器上次bind的端口,如果在5~6分钟之内再次运行服务器 这是导致新运行的服务器 bind失败 

    2、解决上述问题:端口复用 仅仅是端口的复用

    服务器的进程网络资源 任然被占用 一般1分钟作用释放

    1. int yes = 1;
    2. setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(yes));

    将上面的两句话添加到socket只有 bind函数之前

    3、并发服务器 多进程实现

     

     案例1:

    1. #include<stdio.h>
    2. #include<sys/types.h>
    3. #include<sys/socket.h>
    4. #include<netinet/in.h>
    5. #include<arpa/inet.h>
    6. #include<string.h>
    7. #include<pthread.h>
    8. #include<unistd.h>
    9. //TCP并发ECHO服务器(并发回执服务器---客户端给服务器发啥 服务器就给客户端回啥)
    10. void deal_client_fun(int fd)//fd = new_fd
    11. {
    12. while(1)//以下语句是服务器的核心代码
    13. {
    14. //获取客户端请求
    15. char buf[128]="";
    16. int len = recv(fd,buf,sizeof(buf), 0);
    17. if(len == 0)
    18. break;
    19. //回应客户端
    20. send(fd, buf, len, 0);
    21. }
    22. return;
    23. }
    24. int main()
    25. {
    26. //1、创建tcp监听套接字
    27. int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    28. if(sockfd < 0)
    29. {
    30. perror("socket");
    31. }
    32. //端口复用
    33. int yes = 1;
    34. setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(yes));
    35. //2、给TCP监听套接字 bind固定的IP以及端口信息
    36. struct sockaddr_in my_addr;
    37. bzero(&my_addr,sizeof(my_addr));
    38. my_addr.sin_family = AF_INET;
    39. my_addr.sin_port = htons(8000);
    40. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    41. int ret = bind(sockfd, (struct sockaddr *)&my_addr,sizeof(my_addr));
    42. if(ret == -1)
    43. {
    44. perror("bind");
    45. }
    46. //3、调用listen 将sockfd主动变被动 同时创建链接队列
    47. listen(sockfd, 10);
    48. //4、提取完成链接的客户端 accept
    49. //accept调用一次 只能提取一个客户端
    50. while(1)
    51. {
    52. struct sockaddr_in cli_addr;
    53. socklen_t cli_len = sizeof(cli_addr);
    54. int new_fd = accept(sockfd,(struct sockaddr *)&cli_addr , &cli_len);
    55. //遍历客户端的信息ip port
    56. unsigned short port=ntohs(cli_addr.sin_port);
    57. char ip[16]="";
    58. inet_ntop(AF_INET,&cli_addr.sin_addr.s_addr, ip, 16);
    59. printf("已有客户端:%s:%hu连接上了服务器\n", ip, port);
    60. pid_t pid;
    61. if(fork() == 0)//子进程 服务器客户端 不需要监听套接字
    62. {
    63. //关闭监听套接字
    64. close(sockfd);
    65. //服务于客户端
    66. deal_client_fun(new_fd);
    67. //关闭已连接套接字
    68. close(new_fd);
    69. _exit(-1);
    70. }
    71. else//父进程
    72. {
    73. //监听新的连接到来 不需要和客户端通信 必须关闭已连接套接字new_fd
    74. close(new_fd);
    75. }
    76. }
    77. //关闭监听套接字
    78. close(sockfd);
    79. return 0;
    80. }

    运行结果:

    总结:

    TCP并发服务器 进程版:父子进程 资源独立 某个进程结束 不会影响已有的进程 服务器更加稳定 代价多进程 会消耗很多资源。

    TCP并发服务器 线程版:线程共享进程资源 资源开销小 但是一旦主进程结束 所有线程都会结束 服务器先对进程 不是那么稳定

    临时复习:已连接套接字 和 accpet中返回的客户端地址结构分析

     知识点2【HTTP协议】 HTTP基于TCP

    1、HTTP协议的概述

    4.7.1 web服务器简介

    Web服务器又称WWW服务器、网站服务器等

    特点

    使用HTTP协议与客户机浏览器进行信息交流

    不仅能存储信息,还能在用户通过web浏览器提供的信息的基础上运行脚本和程序

    该服务器需可安装在UNIX、Linux或Windows等操作系统上

    著名的服务器有Apache、Tomcat、 IIS等

    4.7.2 HTTP协议

    Webserver—HTTP协议

    概念

    一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议

    特点

    1、支持C/S架构

    2、简单快速:客户向服务器请求服务时,只需传送请求方法和路径 ,常用方法:GET、POST

    3、无连接:限制每次连接只处理一个请求

    4、无状态:即如果后续处理需要前面的信息,它必须重传,这样可能导致每次连接传送的数据量会增大

    2、Webserver 通信过程

    我们写的是服务器端  必须是TCP并发服务器 客户端 由浏览器充当

    3、Web编程开发

    网页浏览(使用GET方式)

    客户端浏览器请求:

     服务器收到的数据(浏览器发出的文件请求)

     服务器应答的格式:请求成功 服务器打开文件成功 给浏览器发送的报文

    服务器应答的格式:请求失败 打开本地文件失败 给浏览器发报文

    案例:webserver服务器

    1. #include<stdio.h>
    2. #include<sys/types.h>
    3. #include<sys/socket.h>
    4. #include<netinet/in.h>
    5. #include<arpa/inet.h>
    6. #include<string.h>
    7. #include<pthread.h>
    8. #include<unistd.h>
    9. #include <sys/stat.h>
    10. #include <fcntl.h>
    11. char head[] = "HTTP/1.1 200 OK\r\n" \
    12. "Content-Type: text/html\r\n" \
    13. "\r\n";
    14. char err[]= "HTTP/1.1 404 Not Found\r\n" \
    15. "Content-Type: text/html\r\n" \
    16. "\r\n" \
    17. "File not found";
    18. void *deal_client_fun(void *arg)//arg=new_fd
    19. {
    20. int new_fd = (int)arg;
    21. //1、recv获取客户端的请求(只需要调用一次)
    22. unsigned char buf[512]="";
    23. recv(new_fd,buf,sizeof(buf), 0);
    24. //2、解析buf 提取所请求的文件名
    25. char file_name[128]="./html/";
    26. sscanf(buf,"GET /%[^ ]", file_name+7);
    27. if(file_name[7]=='\0')
    28. strcpy(file_name,"./html/index.html");
    29. //3、打开本地文件
    30. int fd = open(file_name, O_RDONLY);
    31. if(fd < 0)//打开文件失败
    32. {
    33. perror("open");
    34. //发送失败报文给客户端
    35. send(new_fd, err, strlen(err), 0);
    36. }
    37. else//打开本地文件成功
    38. {
    39. //发送成功报文 请准备接受
    40. send(new_fd, head, strlen(head), 0);
    41. //不停的给浏览器客户端 发送文件数据
    42. while(1)
    43. {
    44. //从本地文件读取数据
    45. unsigned char buf[1024]="";
    46. int ret = read(fd,buf,sizeof(buf));
    47. printf("ret=%d\n", ret);
    48. if(ret<1024)//文件末尾 将数据发送出去
    49. {
    50. send(new_fd,buf,ret,0);
    51. break;
    52. }
    53. send(new_fd,buf,ret,0);
    54. }
    55. //关闭本地文件描述符
    56. close(fd);
    57. }
    58. close(new_fd);
    59. return NULL;
    60. }
    61. //运行的方式:./a.out 8000
    62. int main(int argc,char *argv[])
    63. {
    64. if(argc != 2)
    65. {
    66. printf("./a.out 8000\n");
    67. return 0;
    68. }
    69. //1、创建TCP监听套接字
    70. int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    71. if(sockfd < 0)
    72. {
    73. perror("socket");
    74. return 0;
    75. }
    76. //2、端口复用
    77. int yes = 1;
    78. setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(yes));
    79. //3、给服务器绑定固定的IP以及端口
    80. struct sockaddr_in my_addr;
    81. bzero(&my_addr,sizeof(my_addr));
    82. my_addr.sin_family = AF_INET;
    83. my_addr.sin_port = htons(atoi(argv[1]));
    84. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    85. int ret = bind(sockfd, (struct sockaddr *)&my_addr,sizeof(my_addr));
    86. if(ret == -1)
    87. {
    88. perror("bind");
    89. }
    90. //4、使用listen函数 将套接字 由主动变被动 创建连接队列
    91. listen(sockfd, 15);
    92. //5、while不停的提取客户端 产生已连接套接字
    93. while(1)
    94. {
    95. struct sockaddr_in cli_addr;
    96. socklen_t cli_len = sizeof(cli_addr);
    97. int new_fd = accept(sockfd,(struct sockaddr *)&cli_addr , &cli_len);
    98. //遍历客户端的信息ip port
    99. unsigned short port=ntohs(cli_addr.sin_port);
    100. char ip[16]="";
    101. inet_ntop(AF_INET,&cli_addr.sin_addr.s_addr, ip, 16);
    102. printf("已有客户端:%s:%hu连接上了服务器\n", ip, port);
    103. //创建线程 服务于客户端
    104. pthread_t tid;
    105. pthread_create(&tid,NULL, deal_client_fun, (void *)new_fd);
    106. pthread_detach(tid);
    107. }
    108. //关闭监听套接字
    109. close(sockfd);
    110. return 0;
    111. }

     运行结果:

  • 相关阅读:
    精确率、召回率、F1值
    新手GitHub使用指南
    try-cathc-finally
    java基于springboot二手车交易管理系统附源码
    【通信】基于matlab粒子群算法5G物联网云网络优化【含Matlab源码 2160期】
    分布式搜索引擎es-3
    《吐血整理》高级系列教程-吃透Fiddler抓包教程(21)-如何使用Fiddler生成Jmeter脚本-上篇
    配置git-ssh
    酷开科技夯实流量基础,构建智慧生活新风尚!
    工业物联网系统下如何实现设备数据采集与设备维护
  • 原文地址:https://blog.csdn.net/buhuidage/article/details/127941188