> 文章列表 > TCP并发服务器模型

TCP并发服务器模型

TCP并发服务器模型

文章目录

  • 1. 循环服务器
  • 2. 并发服务器
    • 2.1 多进程并发服务器
    • 2.2 多线程并发服务器
  • 3. 基于TCP的文件传输服务(目前只有下载)
    • 1.tftp下载模型
    • 2.TFTP通信过程总结
    • 3.tftp下载协议分析

1. 循环服务器

  1. 一次只能处理一个客户端,等这个客户端退出后,才能处理下一个客户端
  2. 缺点:循环服务器所处理的客户端最好不要有耗时操作。

2. 并发服务器

  1. 可以同时处理多个客户端的请求,创建子进程 或者 分支线程来处理客户端的请求
  2. 父进程/主线程 只负责连接,子进程/分支线程 只负责与客户端交互。

2.1 多进程并发服务器

// TCP并发服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/types.h>  /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>#define IP "192.168.8.225"                            // 设置服务器IP地址
#define PORT 1024                                      // 设置服务器端口号为1024
int _newfd_server(int newfd, struct sockaddr_in cin); // 生成新的服务器
void handler(int sig);                                 // 回收僵尸进程
int main()
{__sighandler_t sig = signal(17, handler);if (SIG_ERR == sig){perror("signal");return -1;}/*创建字节套,以tcp方式 ipv4*/// int socket(int domain, int type, int protocol);int serve_fd = socket(AF_INET, SOCK_STREAM, 0);if (serve_fd < 0){perror("socket");return -1;}/*允许端口快速重用成功*/int resue = 1;if (setsockopt(serve_fd, SOL_SOCKET, SO_REUSEADDR, &resue, sizeof(resue)) < 0){perror("setsockopt");return -1;}/*填充服务器地址信息结构体*/struct sockaddr_in server_in;server_in.sin_family = AF_INET;            // 必须填AF_INETserver_in.sin_port = htons(PORT);          // 端口号,主机转网络server_in.sin_addr.s_addr = inet_addr(IP); // 服务器IP,/*将服务器IP和端口绑定到套接字上*/// int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);if (bind(serve_fd, (struct sockaddr *)&server_in, sizeof(server_in)) < 0){perror("bind");return -1;}/*将套接字设置为被动监听模式*/// int listen(int sockfd, int backlog);if (listen(serve_fd, 128) < 0) // 第二个参数为队列大小,一般设置为大于1{perror("listen");return -1;}printf("listen success __%d__\\n", __LINE__);/*获取连接成功的套接字,此套接字用于客户端*/struct sockaddr_in cin;           // 存储连接成功的客户端地址信息socklen_t addrlent = sizeof(cin); // 客户端字节大小while (1){int newfd = accept(serve_fd, (struct sockaddr *)&cin, &addrlent); // 阻塞等待客户端连接if (newfd < 0){perror("accept");return -1;}printf("[%s:%d] newfd=%d 连接成功:__%d__\\n", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd, __LINE__);__pid_t cpid = fork(); // 创建新进程if (cpid > 0)          // 父进程执行部分{close(newfd); // 父进程关掉newfd,对子进程无影响}else if (0 == cpid) // 子进程执行部分,{close(serve_fd);_newfd_server(newfd, cin);exit(0);}else{perror("fork");return -1;}}close(serve_fd);return 0;
}
int _newfd_server(int newfd, struct sockaddr_in cin) // 生成新的服务器
{char buf[128] = "";ssize_t res = 0;while (1){/*接受消息*/// ssize_t recv(int sockfd, void *buf, size_t len, int flags);bzero(buf, sizeof(buf));                // 清空bufres = recv(newfd, buf, sizeof(buf), 0); // 以阻塞方式接收消息if (res < 0){perror("recv");return -1;}else if (0 == res){printf("[%s:%d] newfd=%d 客户端下线:__%d__\\n", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd, __LINE__);break;}printf("newfd=%d: %s\\n", newfd, buf);/*发送消息*/// ssize_t send(int sockfd, const void *buf, size_t len, int flags);printf("请输入要发送的消息>>>");fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = 0;if (send(newfd, buf, sizeof(buf), 0) < 0){perror("send");return -1;}printf("发送成功\\n");}
}
void handler(int sig)
{while (waitpid(-1, NULL, WNOHANG) > 0); // 非阻塞等待子进程退出
}

2.2 多线程并发服务器

// TCP并发服务器端,线程方式实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/types.h>  /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>#define IP "192.168.8.225" // 设置服务器IP地址
#define PORT 1024          // 设置服务器端口号为1024
struct server
{int newfd;struct sockaddr_in cin;
};void *_newfd_server(void *arg); // 多线程生成新服务器int main()
{/*创建字节套,以tcp方式 ipv4*/// int socket(int domain, int type, int protocol);int serve_fd = socket(AF_INET, SOCK_STREAM, 0);if (serve_fd < 0){perror("socket");return -1;}/*允许端口快速重用成功*/int resue = 1;if (setsockopt(serve_fd, SOL_SOCKET, SO_REUSEADDR, &resue, sizeof(resue)) < 0){perror("setsockopt");return -1;}/*填充服务器地址信息结构体*/struct sockaddr_in server_in;server_in.sin_family = AF_INET;            // 必须填AF_INETserver_in.sin_port = htons(PORT);          // 端口号,主机转网络server_in.sin_addr.s_addr = inet_addr(IP); // 服务器IP,/*将服务器IP和端口绑定到套接字上*/// int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);if (bind(serve_fd, (struct sockaddr *)&server_in, sizeof(server_in)) < 0){perror("bind");return -1;}/*将套接字设置为被动监听模式*/// int listen(int sockfd, int backlog);if (listen(serve_fd, 128) < 0) // 第二个参数为队列大小,一般设置为大于1{perror("listen");return -1;}printf("listen success __%d__\\n", __LINE__);/*获取连接成功的套接字,此套接字用于客户端*/struct sockaddr_in cin;           // 存储连接成功的客户端地址信息socklen_t addrlent = sizeof(cin); // 客户端字节大小pthread_t pth;struct server ser;while (1){int newfd = accept(serve_fd, (struct sockaddr *)&cin, &addrlent); // 阻塞等待客户端连接if (newfd < 0){perror("accept");return -1;}printf("[%s:%d] newfd=%d 连接成功:__%d__\\n", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd, __LINE__);ser.cin = cin;ser.newfd = newfd;pthread_create(&pth, NULL, _newfd_server, &ser); // 创建线程pthread_detach(pth);                             // 分离线程}close(serve_fd);return 0;
}
void *_newfd_server(void *arg) // 生成新的服务器
{struct sockaddr_in cin = ((struct server *)arg)->cin;int newfd = ((struct server *)arg)->newfd;char buf[128] = "";ssize_t res = 0;while (1){/*接受消息*/// ssize_t recv(int sockfd, void *buf, size_t len, int flags);bzero(buf, sizeof(buf));                // 清空bufres = recv(newfd, buf, sizeof(buf), 0); // 以阻塞方式接收消息if (res < 0){perror("recv");break;}else if (0 == res){printf("[%s:%d] newfd=%d 客户端下线:__%d__\\n", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd, __LINE__);break;}printf("[%s:%d]cfd=%d: %s\\n", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd, buf);}close(newfd);pthread_exit(NULL);
}

3. 基于TCP的文件传输服务(目前只有下载)

1.tftp下载模型

TCP并发服务器模型

2.TFTP通信过程总结

  1. 服务器在69号端口等待客户端的请求
  2. 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
  3. 每个数据包的编号都有变化(从1开始)
  4. 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
  5. 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。

TCP并发服务器模型

3.tftp下载协议分析

TCP并发服务器模型

差错码:当操作码是5的时候

0 未定义,差错错误信息

1 File not found.

2 Access violation.

3 Disk full or allocation exceeded.

4 illegal TFTP operation.

5 Unknown transfer ID.

6 File already exists.

7 No such user.

8 Unsupported option(s) requested.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>#define IP "192.168.31.108" // 本机的ip地址
#define PORT 69             // tftp连接的端口号
#define ERR_MSG(msg)            \\{                           \\printf("%d", __LINE__); \\perror(msg);            \\}
int fun_download(int cfd, struct sockaddr_in sin); // 用于文件的下载
// void fun_upload();   // 用于文件的上传int main()
{// 创建报式套接字int cfd;cfd = socket(AF_INET, SOCK_DGRAM, 0);if (cfd < 0){ERR_MSG("socket");return -1;}// 填充服务器首次链接的地址信息结构体(69号端口)struct sockaddr_in sin;sin.sin_family = AF_INET;            // 必须填AF_INETsin.sin_port = htons(PORT);          // 端口号的网络字节序sin.sin_addr.s_addr = inet_addr(IP); // 服务器的ip地址char c;while (1){printf("#    1.下载  #\\n");printf("#    2.上传  #\\n");printf("#    3.退出  #\\n");printf("请输入选项:\\t");c = getchar();while (getchar() != 10); // 吸收垃圾字符,只有输入回车键才向下执行switch (c){case '1':fun_download(cfd, sin);break;case '2':// fun_upload();break;case '3':return 0;default:printf("请输入正确字符\\n");break;}}
}
int fun_download(int cfd, struct sockaddr_in sin)
{/* 组下载请求协议*/char buf[516] = {0}; // 最大为516字节char filename[128] = "";printf("请输入需要下载的文件名\\t");scanf("%s", filename);while (getchar() != 10); // 吸收垃圾字符,只有输入回车键才向下执行
#if 0//采用逐个赋值的方法short* p1 = (short*)buf;*p1 = htons(1);char* p2 = buf+2;strcpy(p2, filename);char* p4 = p2+strlen(p2)+1;strcpy(p4, "octet");int size = 4+strlen(p2)+strlen(p4);
#endifsprintf(buf, "%c%c%s%c%s%c", 0, 1, filename, 0, "octet", 0); // 用sprintf的方式一次性赋值// printf("%d %d %d  %d\\n", buf[0], buf[1], buf[2], buf[3]);int size = strlen(filename) + strlen("octet") + 4;//   1. 发送读写请求if (sendto(cfd, buf, size, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0){ERR_MSG("sendto");return -1;}printf("读写请求发送成功\\n");int fd = -1; // 文件描述符,赋值为-1是为了防止出错socklen_t addrlen = sizeof(sin);ssize_t len = 0; // 获取到的字节长度int ret = 0;     // 用于函数返回值int count = 0;   // 块编码计数器while (1){bzero(buf, sizeof(buf));len = recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &addrlen); // 接收数据// printf("%d %d %d  %d\\n", buf[0], buf[1], buf[2], buf[3]);if (len < 0)                                                                 // 函数打开失败{ERR_MSG("recefrom");ret = -1;break;}// 如果位操作码是3的话说明接收正常if (3 == buf[1]){// 判断块编号是否正确if (*(unsigned short *)(buf + 2) == htons((count + 1))){count++;// 如果是第一个数据包,也就是块编号==1,那么就新建文件if (*(unsigned short *)(buf + 2) == ntohs(1)){fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664); // 创建同样的文件if (fd < 0){ERR_MSG("open");ret = -1;break;}}// 将读取到的数据保存到文件中if (write(fd, buf + 4, len - 4) < 0){ERR_MSG("write");ret = -1;break;}// 回复ACK,此时只有操作码不同,只需要改操作码buf[1] = 4;if (sendto(cfd, buf, 4, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0){ERR_MSG("sendto");ret = -1;break;}// 如果res<516-4,那么就代表接收失败if (len - 4 < 512){printf("文件拷贝完毕\\n");ret = 0;break;}}}else if (5 == buf[1]) // 收到错误包{unsigned short err = 0;err = ntohs(*(unsigned short *)(buf + 2));switch (err){case 1:printf("文件未找到\\n");ret = -1;break;case 2:printf("访问违规\\n");ret = -1;break;case 3:printf("磁盘已满,或超出分配\\n");ret = -1;break;case 4:printf("TFTP操作违法\\n");ret = -1;break;case 5:printf("传输ID未知\\n");ret = -1;break;case 6:printf("文件已存在\\n");ret = -1;break;case 7:printf("无此用户\\n");ret = -1;break;default:break;}break;}}close(fd);return ret;
}
            ret = -1;break;case 5:printf("传输ID未知\\n");ret = -1;break;case 6:printf("文件已存在\\n");ret = -1;break;case 7:printf("无此用户\\n");ret = -1;break;default:break;}break;}
}
close(fd);
return ret;

}