一、操作系统: Ubuntu16.0.4(两台虚拟机)
二、编译工具:vs code(相关配置见之前的文章vs code安装与配置,在搜索C++配件时,可能有些组件必须添加上,不然程序编译会报错,缺少一些库)
三、服务器端实现功能:
1、开启服务器端,一直侦听服务器端口(8000);
2、接受客户端请求,控制台打印响应报文,响应报文由状态行、响应头部、空行、数据体4个部分组成;
3、读取请求并发送文件,发送完一次数据后关闭连接,若客户端需要循环从服务器下载数据,客户端需要循环连接服务器;
四、服务器端代码实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
unsigned short port = 8000; //设置默认端口号
if(argc > 1)
{
port = atoi(argv[1]); //将参数2赋值给端口号变量
}
//创建TCP套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if( sockfd < 0)
{
perror("socket");
exit(-1);
}
//服务器套接字地址变量赋值
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET; //IPV4族
my_addr.sin_port = htons(port); //将端口号转换成网络字节序
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本机IP地址
//绑定TCP套接字
if( bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)) != 0)
{
perror("bind");
close(sockfd);
exit(-1);
}
//监听
if( listen(sockfd, 10) != 0)
{
perror("listen");
close(sockfd);
exit(-1);
}
printf("Listenning at port=%d\n",port); //打印端口号信息
//printf("Usage: http://127.0.0.1:%d/html/index.html\n", port);
while(1)
{
char cli_ip[INET_ADDRSTRLEN] = {0}; //存放客户端点分十进制IP地址
struct sockaddr_in client_addr;
socklen_t cliaddr_len = sizeof(client_addr);
//等待客户端连接
int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
printf("connfd=%d\n",connfd); //打印已连接套接字
if(connfd > 0)
{
if(fork() == 0) //创建进程并判断返回值
{
close(sockfd);
//子进程执行
int fd = 0;
int len = 0;
char buf[1024] = "";
char filename[50] = "";
//将网络字节序转换成点分十进制形式存放在cli_ip中
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("connected form %s\n\r", cli_ip); //打印点分十进制形式的客户端IP地址
recv(connfd, buf, sizeof(buf), 0); //接收客户端发送的请求内容
sscanf(buf, "GET /%[^ ]", filename); //解析客户端发送请求字符串
printf("filename=*%s*\n", filename);
fd = open(filename, O_RDONLY); //以只读方式打开文件
if( fd < 0) //如果打开文件失败
{
//HTTP失败头部
char err[]= "HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<HTML><BODY>File not found</BODY></HTML>";
perror("open error");
send(connfd, err, strlen(err), 0);
close(connfd); //关闭已连接套接字
exit(0); //子进程退出
}
//打开文件成功后
//接收成功时返回的头部
char head[]="HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"\r\n";
send(connfd, head, strlen(head), 0); //发送HTTP请求成功头部
while( (len = read(fd, buf, sizeof(buf))) > 0) //循环读取文件内容
{
send(connfd, buf, len, 0); //将读得的数据发送给客户端
}
close(fd); //成功后关闭文件
close(connfd); //关闭已连接套接字,这样的话,完成一次数据传输后就关闭了连接,并非keep-alive!
exit(0); //子进程退出
}
}
close(connfd); //父进程关闭连接套接字
}
close(sockfd);
printf("exit main!\n");
return 0;
}
五、客户端实现功能
1、创建通信端点:套接字
2、设置服务器地址结构体
3、主动连接服务器
4、编写http请求报文包,并发送到服务器端
5、获取http响应报文
6、下载指定的服务器端文件
六、客户端代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
int main()
{
while(1) //这里是为了让服务器循环发送数据,因为服务器端发送完一次数据会关闭连接,所以循环
//while(1)要从建立连接开始
{
// 创建通信端点:套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("creat socket\r\n");
// 设置服务器地址结构体
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr)); // 初始化服务器地址
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_port = htons(8000); // nginx服务器监听的端口
inet_pton(AF_INET, "192.168.254.33", &server_addr.sin_addr); // 服务器ip
printf("server addr has ready\r\n");
// 主动连接服务器
int bytes_received=0;
int filesaved=0;
int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(err_log != 0)
{
perror("connect");
printf("connect has error\r\n");
close(sockfd);
return -1;
}
else{
printf("connect has ready\r\n");
}
//http请求报文包
char send_buf[] =
"GET /GetRoundData.html HTTP/1.1\r\n"
"Accept: text/html,image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/xaml+xml, application/x-ms-xbap, */*\r\n"
"Accept-Language: zh-Hans-CN,zh-Hans;q=0.8,en-US;q=0.5,en;q=0.3\r\n"
"User-Agent: Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)\r\n"
"Accept-Encoding: gzip, deflate\r\n"
"Host: 192.168.254.66:8000\r\n"
"Connection: Keep-Alive\r\n"
"\r\n";
//发送http请求报文包
send(sockfd, send_buf, sizeof(send_buf)-1, 0);
printf("message has send\r\n");
//
//int contentlengh=0;
//获取http响应报文
char recv_buf[8*1024] = {0}; //recv_buf是响应信息(包括响应头部和数据体),返回值是保存的字节数
printf("err_log=%d\n\r",err_log);
bytes_received=recv(sockfd, recv_buf, sizeof(recv_buf), 0);
printf("bytes_received=%d\r\n",bytes_received);
//在指定位置存储服务器上文件/index.html,并在控制台打印文件内容
//int byteRec=bytes_received-44;
//if((byteRec))
//{
//将服务器文件存储到客户端指定位置
FILE* fd=fopen("GetRoundData.json","wb+");
//int filesaved=fwrite(recv_buf,sizeof(recv_buf),1,fd);
filesaved=fwrite(recv_buf,1,bytes_received,fd);
printf("filesaved=%d",filesaved);
fclose(fd);
sleep(2); // 等待2秒后再次发送连接请求
}
//else{
//printf("data is null!!!!\n\r");
//sleep(2);
//}
return 0;
}
七、程序运行过程和结果
1、首先运行服务器端,显示在监听端口8000,看是否有客户端发出连接请求
2、运行客户端,循环发送请求,循环显示信息
3、在服务器端,控制台输出信息
至此,只要服务器端一直运行,客户端可以多次连接服务器端获取服务器上的文件,并下载保存数据在客户端
目前仍存在一些问题:
1、有些不稳定,第一次没有打印出文件内容,原因待查
2、保存的文件包含有头部,该如何去除
3、若有多个应用需要访问服务器,如何用多线程来实现对服务器端的访问;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)