文章目录
- 1. 循环服务器
- 2. 并发服务器
- 2.1 多进程并发服务器
- 2.2 多线程并发服务器
- 3. 基于TCP的文件传输服务(目前只有下载)
- 1.tftp下载模型
- 2.TFTP通信过程总结
- 3.tftp下载协议分析
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_INET
server_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)); // 清空buf
res = 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_INET
server_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)); // 清空buf
res = 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下载模型
2.TFTP通信过程总结
- 服务器在69号端口等待客户端的请求
- 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
- 每个数据包的编号都有变化(从1开始)
- 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
- 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。
3.tftp下载协议分析
差错码:当操作码是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_INET
sin.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);
#endif
sprintf(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;
}