并发服务器:同时 服务于 多个客户端
TCP并发服务器:本质是TCP服务器,同时服务于多个客户端
TCP并发服务器的注意点:
TCP服务器、提取多个客户端、开启进程或线程处理每个客户端

- #include<stdio.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #include<arpa/inet.h>
- #include<string.h>
- #include<pthread.h>
- //TCP并发ECHO服务器(并发回执服务器---客户端给服务器发啥 服务器就给客户端回啥)
- void* deal_client_fun(void *arg)//arg = &new_fd
- {
- //并发服务器的核心服务代码(各不相同)
- //通过arg获得已连接套接字
- int fd = *(int *)arg;
- while(1)//以下语句是服务器的核心代码
- {
- //获取客户端请求
- char buf[128]="";
- int len = recv(fd,buf,sizeof(buf), 0);
- if(len == 0)
- break;
- //回应客户端
- send(fd, buf, len, 0);
- }
-
- close(fd);
- }
- int main()
- {
- //1、创建tcp监听套接字
- int sockfd = socket(AF_INET, SOCK_STREAM, 0);
- if(sockfd < 0)
- {
- perror("socket");
- }
- int yes = 1;
- setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(yes));
-
- //2、给TCP监听套接字 bind固定的IP以及端口信息
- struct sockaddr_in my_addr;
- bzero(&my_addr,sizeof(my_addr));
- my_addr.sin_family = AF_INET;
- my_addr.sin_port = htons(8000);
- my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- int ret = bind(sockfd, (struct sockaddr *)&my_addr,sizeof(my_addr));
- if(ret == -1)
- {
- perror("bind");
- }
- //3、调用listen 将sockfd主动变被动 同时创建链接队列
- listen(sockfd, 10);
-
- //4、提取完成链接的客户端 accept
- //accept调用一次 只能提取一个客户端
- while(1)
- {
- struct sockaddr_in cli_addr;
- socklen_t cli_len = sizeof(cli_addr);
- int new_fd = accept(sockfd,(struct sockaddr *)&cli_addr , &cli_len);
-
- //遍历客户端的信息ip port
- unsigned short port=ntohs(cli_addr.sin_port);
- char ip[16]="";
- inet_ntop(AF_INET,&cli_addr.sin_addr.s_addr, ip, 16);
- printf("已有客户端:%s:%hu连接上了服务器\n", ip, port);
-
- //对每一个客户端 开启一个线程 单独的服务器客户端
- pthread_t tid;
- pthread_create(&tid,NULL, deal_client_fun, (void *)&new_fd);
- //线程分离
- pthread_detach(tid);
- }
-
- //关闭监听套接字
- close(sockfd);
-
- return 0;
- }
运行结果:

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

服务器的进程网络资源 任然被占用 一般1分钟作用释放
- int yes = 1;
- setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(yes));
将上面的两句话添加到socket只有 bind函数之前


案例1:
- #include<stdio.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #include<arpa/inet.h>
- #include<string.h>
- #include<pthread.h>
- #include<unistd.h>
- //TCP并发ECHO服务器(并发回执服务器---客户端给服务器发啥 服务器就给客户端回啥)
- void deal_client_fun(int fd)//fd = new_fd
- {
- while(1)//以下语句是服务器的核心代码
- {
- //获取客户端请求
- char buf[128]="";
- int len = recv(fd,buf,sizeof(buf), 0);
- if(len == 0)
- break;
- //回应客户端
- send(fd, buf, len, 0);
- }
-
- return;
- }
- int main()
- {
- //1、创建tcp监听套接字
- int sockfd = socket(AF_INET, SOCK_STREAM, 0);
- if(sockfd < 0)
- {
- perror("socket");
- }
- //端口复用
- int yes = 1;
- setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(yes));
-
- //2、给TCP监听套接字 bind固定的IP以及端口信息
- struct sockaddr_in my_addr;
- bzero(&my_addr,sizeof(my_addr));
- my_addr.sin_family = AF_INET;
- my_addr.sin_port = htons(8000);
- my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- int ret = bind(sockfd, (struct sockaddr *)&my_addr,sizeof(my_addr));
- if(ret == -1)
- {
- perror("bind");
- }
- //3、调用listen 将sockfd主动变被动 同时创建链接队列
- listen(sockfd, 10);
-
- //4、提取完成链接的客户端 accept
- //accept调用一次 只能提取一个客户端
- while(1)
- {
- struct sockaddr_in cli_addr;
- socklen_t cli_len = sizeof(cli_addr);
- int new_fd = accept(sockfd,(struct sockaddr *)&cli_addr , &cli_len);
-
- //遍历客户端的信息ip port
- unsigned short port=ntohs(cli_addr.sin_port);
- char ip[16]="";
- inet_ntop(AF_INET,&cli_addr.sin_addr.s_addr, ip, 16);
- printf("已有客户端:%s:%hu连接上了服务器\n", ip, port);
-
- pid_t pid;
- if(fork() == 0)//子进程 服务器客户端 不需要监听套接字
- {
- //关闭监听套接字
- close(sockfd);
-
- //服务于客户端
- deal_client_fun(new_fd);
-
- //关闭已连接套接字
- close(new_fd);
- _exit(-1);
- }
- else//父进程
- {
- //监听新的连接到来 不需要和客户端通信 必须关闭已连接套接字new_fd
- close(new_fd);
- }
- }
-
- //关闭监听套接字
- close(sockfd);
-
- return 0;
- }
运行结果:

TCP并发服务器 进程版:父子进程 资源独立 某个进程结束 不会影响已有的进程 服务器更加稳定 代价多进程 会消耗很多资源。
TCP并发服务器 线程版:线程共享进程资源 资源开销小 但是一旦主进程结束 所有线程都会结束 服务器先对进程 不是那么稳定
临时复习:已连接套接字 和 accpet中返回的客户端地址结构分析


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、无状态:即如果后续处理需要前面的信息,它必须重传,这样可能导致每次连接传送的数据量会增大
我们写的是服务器端 必须是TCP并发服务器 客户端 由浏览器充当

网页浏览(使用GET方式)
客户端浏览器请求:


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

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

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

案例:webserver服务器
- #include<stdio.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #include<arpa/inet.h>
- #include<string.h>
- #include<pthread.h>
- #include<unistd.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- char head[] = "HTTP/1.1 200 OK\r\n" \
- "Content-Type: text/html\r\n" \
- "\r\n";
- char err[]= "HTTP/1.1 404 Not Found\r\n" \
- "Content-Type: text/html\r\n" \
- "\r\n" \
- "File not found";
- void *deal_client_fun(void *arg)//arg=new_fd
- {
- int new_fd = (int)arg;
-
- //1、recv获取客户端的请求(只需要调用一次)
- unsigned char buf[512]="";
- recv(new_fd,buf,sizeof(buf), 0);
-
- //2、解析buf 提取所请求的文件名
- char file_name[128]="./html/";
- sscanf(buf,"GET /%[^ ]", file_name+7);
- if(file_name[7]=='\0')
- strcpy(file_name,"./html/index.html");
-
- //3、打开本地文件
- int fd = open(file_name, O_RDONLY);
- if(fd < 0)//打开文件失败
- {
- perror("open");
- //发送失败报文给客户端
- send(new_fd, err, strlen(err), 0);
- }
- else//打开本地文件成功
- {
- //发送成功报文 请准备接受
- send(new_fd, head, strlen(head), 0);
-
- //不停的给浏览器客户端 发送文件数据
- while(1)
- {
- //从本地文件读取数据
- unsigned char buf[1024]="";
- int ret = read(fd,buf,sizeof(buf));
- printf("ret=%d\n", ret);
- if(ret<1024)//文件末尾 将数据发送出去
- {
- send(new_fd,buf,ret,0);
- break;
- }
- send(new_fd,buf,ret,0);
- }
-
- //关闭本地文件描述符
- close(fd);
- }
-
- close(new_fd);
- return NULL;
- }
-
- //运行的方式:./a.out 8000
- int main(int argc,char *argv[])
- {
- if(argc != 2)
- {
- printf("./a.out 8000\n");
- return 0;
- }
-
- //1、创建TCP监听套接字
- int sockfd = socket(AF_INET, SOCK_STREAM, 0);
- if(sockfd < 0)
- {
- perror("socket");
- return 0;
- }
-
- //2、端口复用
- int yes = 1;
- setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(yes));
-
- //3、给服务器绑定固定的IP以及端口
- struct sockaddr_in my_addr;
- bzero(&my_addr,sizeof(my_addr));
- my_addr.sin_family = AF_INET;
- my_addr.sin_port = htons(atoi(argv[1]));
- my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- int ret = bind(sockfd, (struct sockaddr *)&my_addr,sizeof(my_addr));
- if(ret == -1)
- {
- perror("bind");
- }
-
- //4、使用listen函数 将套接字 由主动变被动 创建连接队列
- listen(sockfd, 15);
-
-
- //5、while不停的提取客户端 产生已连接套接字
- while(1)
- {
- struct sockaddr_in cli_addr;
- socklen_t cli_len = sizeof(cli_addr);
- int new_fd = accept(sockfd,(struct sockaddr *)&cli_addr , &cli_len);
-
- //遍历客户端的信息ip port
- unsigned short port=ntohs(cli_addr.sin_port);
- char ip[16]="";
- inet_ntop(AF_INET,&cli_addr.sin_addr.s_addr, ip, 16);
- printf("已有客户端:%s:%hu连接上了服务器\n", ip, port);
-
- //创建线程 服务于客户端
- pthread_t tid;
- pthread_create(&tid,NULL, deal_client_fun, (void *)new_fd);
- pthread_detach(tid);
- }
-
- //关闭监听套接字
- close(sockfd);
- return 0;
- }
运行结果:
