提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
一个小练习题
提示:以下是本篇文章正文内容,下面案例可供参考
一、过程
服务器:创建套接字--填充网络信息结构体--绑定--监听--阻塞等待(accept)-- 查看服务器是否有要下载的文件若有则返回有并进行读取和发送,没有则返回无循环接收新的正确的文件名。
客户端:创建套接字-- 填充网络信息结构体--连接(connect)-- 发送要下载的文件名,接收服务器回复,如果没有文件则重新发送新的文件名。
注意事项:TCP的粘包问题 :
TCP底层有一个 Nagle 算法,作用是将一定短的时间内发往同一个接收端的 ,多个小的数据包组装成一个整体,一起发给接收方 ,但是接受方没法区分数据的类型,就可能导致有冲突的现象,例如想在文件下载完成后给客户端发一个结束的标志,但该标志可能会被当作正文接收。
解决方法:
1.加一个延时,加一点延时就能解决这个问题,但是在服务器上加延时不太好,所以不能采用该方法。
2.定长发送:每次发送的数据都是固定长度的,这样可以避免粘包,但随之而来的问题是定长发送后,如果最后一次发送的数据没有规定长度这么多怎么办,解决这一问题的其中一种办法就是定义一个结构体,结构体中定义一个数来记录发送的际有效长度。
二、代码实现
1.服务器
代码如下(示例):
#include <head.h>
typedef struct MSG {
char buff[128];
int count;
} msg_t;
int main(int argc, const char* argv[]) {
if (3 != argc) {
puts("usage : ./xxx ip prot");
exit(-1);
}
int sockfd, acceptfd, nbytes, fd;
msg_t msg;
if (-1 == (sockfd = socket(AF_INET, SOCK_STREAM, 0))) {
PRINT_ERR("socket error");
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
PRINT_ERR("bind error");
}
if (-1 == listen(sockfd, 5)) {
PRINT_ERR("listen error");
}
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddr_len = sizeof(clientaddr);
while (1) {
if (-1 == (acceptfd = accept(sockfd, (struct sockaddr*)&clientaddr,
&clientaddr_len))) {
PRINT_ERR("accept error");
}
printf("客户端[%s:%d]连接成功\n", inet_ntoa(clientaddr.sin_addr),
ntohs(clientaddr.sin_port));
while (1) {
memset(&msg, 0, sizeof(msg));
if (-1 == (nbytes = recv(acceptfd, &msg, sizeof(msg), 0))) {
PRINT_ERR("recv error");
} else if (0 == nbytes) {
printf("客户端[%s:%d]断开连接\n",
inet_ntoa(clientaddr.sin_addr),
ntohs(clientaddr.sin_port));
break;
}
if (-1 == (fd = open(msg.buff, O_RDONLY))) {
if (errno == ENOENT) {
memset(msg.buff, 0, 128);
strcpy(msg.buff, "文件不存在");
if (-1 == send(acceptfd, &msg, sizeof(msg), 0)) {
PRINT_ERR("send error");
}
} else {
PRINT_ERR("open error");
}
} else {
memset(msg.buff, 0, 128);
strcpy(msg.buff, "找到文件,准备下载");
if (-1 == send(acceptfd, &msg, sizeof(msg), 0)) {
PRINT_ERR("send error");
}
}
while (1) {
memset(msg.buff, 0, 128);
if (-1 == (msg.count = read(fd, msg.buff, 128))) {
PRINT_ERR("read error");
}
if (-1 == send(acceptfd, &msg, sizeof(msg), 0)) {
PRINT_ERR("send error");
}
if (0 == msg.count) {
close(fd);
break;
}
}
}
close(acceptfd);
}
close(sockfd);
return 0;
}
2.客户端
代码如下(示例):
#include <head.h>
typedef struct MSG {
char buff[128];
int count;
} msg_t;
int main(int argc, const char* argv[]) {
if (3 != argc) {
puts("usage : ./xxx ip prot");
exit(-1);
}
int sockfd, nbytes;
char buff[128] = {0};
msg_t msg;
msg.count = 0;
if (-1 == (sockfd = socket(AF_INET, SOCK_STREAM, 0))) {
PRINT_ERR("socket error");
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
if (-1 == connect(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
PRINT_ERR("connect error");
}
puts("已连接到服务器");
while (1) {
while (1) {
printf("请输入要下载的文件\n");
fgets(msg.buff, 128, stdin);
msg.buff[strlen(msg.buff) - 1] = '\0';
strcpy(buff, msg.buff);
if (-1 == send(sockfd, &msg, sizeof(msg), 0)) {
PRINT_ERR("send error");
}
memset(&msg, 0, sizeof(msg));
if (-1 == recv(sockfd, &msg, sizeof(msg), 0)) {
PRINT_ERR("recv error");
}
if (!strcmp(msg.buff, "找到文件,准备下载")) {
printf("准备下载\n");
break;
} else if (!strcmp(msg.buff, "文件不存在")) {
printf("文件不存在,请重新输入\n");
}
}
int fd = open(buff, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (-1 == fd) {
PRINT_ERR("open error");
}
while (1) {
memset(&msg, 0, sizeof(msg));
if (-1 == recv(sockfd, &msg, sizeof(msg), 0)) {
PRINT_ERR("recv error");
}
if (0 == msg.count) {
break;
}
if (-1 == write(fd, msg.buff, msg.count)) {
PRINT_ERR("write error");
}
}
close(fd);
}
return 0;
}
总结
TCP通信的流程不变,唯一需要处理的只有数据的解析,即如何判断服务器是否有要下载的文件,这时就需要对open这个函数的灵活使用,其返回值为 -1 时,错误码中ENOENT就可以判断文件是否存在,如下图:
另外一点就是在判断结束时发的信息可能粘包,所以定义一个结构体定长发送来解决这个问题。