关于c/c++ 网络编程,无论在linux还是windows,要说到自由性,和安全性,socket无疑是比较好的!对于socket,因为它的传输协议只有两种tcp和udp,属于网络层,这里我们不去重点讨论。
关于应用层协议http,如何用C/C++的socket来实现数据传输和下载呢?
1. http是超文本协议,用在html文件中,那么对于html是如何传输数据呢?
通过post或者get传输表单数据,当然http还有其他的方式head,put ,delete,option,trace等方式。head和get差不多,唯一的区别就是head只返回协议头,put和post也很相似,但是可惜html表单数据不支持这一特性,put和post的区别在于,put说出来资源放置于服务器的位置,而post没有,post将这项权利给予服务器来使用。delete顾名思义,就是指定删除在服务器上的资源,option一般用来获取当前URl所支持请求的方法(就是上诉的六种)。
对于c/c++传输单数据,get方法:
get方法, 形如: http://i.cnblogs.com/EditPosts.aspx?opt=1
这个表单传输的数据就是1,其中键值就是opt,这个需要和服务器上的保持一致
对于一个简单的html
1 <html> 2 <head><title>右边</tile></head> 3 <body> 4 <form > 5 <input type="text", name="opt" > 1 </input> 6 </form> 7 </body> 8 </html>
opt就是键值
那么用socket如何实现:
首先,windows下,我们
1. 先要启动异步套接字启动命令
//初始化套结字动态库 2 if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) //异步套接字启动命令 3 /版本(次,主) //返回socket实现细节信息 4 { 5 system("WSAStartup failed!\n"); 6 system("pause"); 7 return -1; 8 }
2.在想linux下一样,创建套接字
sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
3.绑定端口号,和设置要访问的服务器主机地址
//设置服务器地址 servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = inet_addr("203.195.192.24"); servAddr.sin_port = htons((short)80);
4.连接服务器
1 retVal = connect(sHost, (LPSOCKADDR)&servAddr, sizeof(servAddr));
5.然后接收信息字段
char *pHttpGet = "GET %s?%s HTTP/1.1\r\n" "Host: %s:%d\r\n\r\n"; char strHttpGet[1024] = { 0 }; //ZeroMemory(strHttpGet, BUF_SZIE); //初始化内存 char msg[]="username=Gjxun&pwd=sssssss"; sprintf(strHttpGet, pHttpGet, addr, msg, host, port); int var = send(sHost, strHttpGet, strlen(strHttpGet), 0);
recv(sHost,rebuf ,sizeof(rebuf),0);
这是http的基本流程,对于get发送单个或者多个表单数据如上面所示
对于post而言,情况 会多些,也会复杂些
1.如果发送的是单个或者多个字段信息,那么我们的处理方式大致可以有下面这两种
第一种: 就像get一样,只不过单纯的将数据放置于协议的后面,需要注意点的是,格式比较重要,特别协议头和正文部分之间需要各一个空行:
下面的msg亦可以和get一样写成 msg="username=Gxjun&pwd=ssssss"; 还有content-Length的长度: 是正文和正文数据以及尾部长度之后不需要算协议头长度,不然会,当将连接改为Connection: Keep-Alive 出现服务器长时间接受现象。---指导服务器接受到结尾帧或者数据长度达到那个长度为止,才会响应刚才的动作!!!!
1 void sendPost1(char* addr, char * host, char *msg, int port) { 2 char *pHttpPost = "POST %s HTTP/1.1\r\n" 3 "Host: %s:%d\r\n" 4 "Content-Type: application/x-www-form-urlencoded\r\n" 5 "Content-Length: %d\r\n\r\n" 6 "%s"; 7 13 char strHttpPost[1024] = { 0 }; 14 //ZeroMemory(strHttpGet, BUF_SZIE); //初始化内存 15 sprintf(strHttpPost, pHttpPost, addr, host, port, strlen(msg), msg); 16 int var = send(sHost, strHttpPost, strlen(strHttpPost), 0); 17 if (var < 0) { 18 MessageBoxA(NULL, "请求发送失败!", 0, 0); 19 return; 20 } 21 }
另一种方式:多种数据表单的形式:协议头部分,将Content-Type: multipart/form-data; 同时还需要加上一个分割标识,即boundary = Gxjunnndgx ,
整体上就是设置为 Content-Type: multipart/form-data; boundary=71b23e4066ed\r\n";
其他部分参开rfc2038部分。
所以对于单个或者多个字段表单而言:
比如: 需要像如下的html文件一样将username和pwd的键值数据发送给服务器数据数据:
<html> <head></head> <body> <form action="xxx.xxx.xxxx" method="post"> <input type="text" name="username">Gxjun</input> <input type="password" name="pwd">ssssss</input> <form> </body> </html>
1 void sendPost(char* addr, char * host,string username,string psw, int port){ 2 3 std::string header(""); 6 std::string u_content(""); //用户名 7 std::string p_content(""); //密码 8 9 //----------------------post头开始-------------------------------- 10 header += "POST "; 11 header += addr; 12 header += " HTTP/1.1\r\n"; 13 header += "Host: "; 14 header += host; 15 header += "\r\n"; 16 header += "Connection: Keep-Alive\r\n"; 17 header += "Accept: */*\r\n"; 18 header += "Pragma: no-cache\r\n"; 19 header += "Content-Type: multipart/form-data; boundary=71gxjun\r\n"; 20 21 //用户名数据表单 22 u_content += "--71gxjun\r\n"; 23 u_content += "Content-Disposition: form-data; name=\"u\"\r\n\r\n"; 24 u_content += username+"\r\n"; 25 26 //密码数据表单 27 p_content += "--71gxjun\r\n"; 28 p_content += "Content-Disposition: form-data; name=\"p\"\r\n\r\n"; 29 p_content += psw+"\r\n"; 30 //post尾时间戳 31 std::string strContent("--71gxjun--\r\n\r\n"); 32 char temp[64] = { 0 }; 33 //注意下面这个参数Content-Length,这个参数值是:http请求头长度+请求尾长度+文件总长度 34 // 就分块传送 35 sprintf(temp, "Content-Length: %d\r\n\r\n", 36 p_content.length()+u_content.length() + strContent.length()); 37 header += temp; 38 std::string str_http_request; 39 str_http_request.append(header); 40 41 //----------------------post头结束----------------------------------- 42 //发送post头 43 send(sHost, str_http_request.c_str(), str_http_request.length(), 0); 44 Sleep(0.2); 45 send(sHost, p_content.c_str(), p_content.length(), 0); 46 Sleep(0.2); 47 send(sHost, u_content.c_str(), u_content.length(), 0); 48 Sleep(0.2); 49 ::send(sHost, strContent.c_str(), strContent.length(), 0); 50 Sleep(0.2); 51 } 52
对于boundary=abcdegxjun 这部分的数据可以随意定义,但不要太简单,不然可能会和数据混淆,上面是两个字段的发送,所以需要两部分的正文加正文数据,对于尾部的结束标识,前面需要“--”两个横短线后面也需要两个横短线“--”,对于中间的分割标志,只需要前面有“--”就可以了! 还需要注意的是数据发送完之后,需要换行,然后再接上分割标识。
4.然后对于文件和照片的传输 ---在linux下,一切接文件,在window下我们也可以将照片看做二进制文件处理
其实文件的传输,都可以作为二进制文件来传输,我们可以将文件
1 char * ReadFile(char *pathpic, int &pic_len){ 2 //将图片读取出来 3 FILE *fp = fopen(pathpic, "rb"); //打开文件 4 if (!fp){ 5 MessageBoxA(NULL, "没有找到文件位置", 0, 0); 6 return NULL; 7 } 8 fseek(fp, 0, SEEK_END); //一直寻找到文件尾部 9 pic_len = ftell(fp); //得到图片的长度 10 rewind(fp); //rewind将文件指针指向开头 11 char *pic_buf = new char[pic_len + 1]; //开辟一个空间在堆上 12 memset(pic_buf, 0, pic_len + 1); //清空文件指针 13 //读取文件内容 14 fread(pic_buf,sizeof(char),pic_len,fp); 15 //测试将文件再保存于D:中 16 /* 17 MessageBoxA(NULL, "文件开始", 0, 0); 18 FILE *fpw = fopen("C:\\AA.jpg","wb"); 19 fwrite(pic_buf,sizeof(char), pic_len, fpw); 20 fclose(fpw); //关闭文件流 21 MessageBoxA(NULL, "文件结束", 0, 0); 22 */ 23 fclose(fp); 24 25 return pic_buf; 26 }
然后下面是一个关于多个字段和多个照片,运用一个form表单,通过一次post,将数据上传到服务器上! 注: 这里是在c\s模式, 客户端是c++ ,服务器是php
代码如下:
1 char * ReadFile(char *pathpic, int &pic_len){ 2 //将图片读取出来 3 FILE *fp = fopen(pathpic, "rb"); //打开文件 4 if (!fp){ 5 MessageBoxA(NULL, "没有找到文件位置", 0, 0); 6 return NULL; 7 } 8 fseek(fp, 0, SEEK_END); //一直寻找到文件尾部 9 pic_len = ftell(fp); //得到图片的长度 10 rewind(fp); //rewind将文件指针指向开头 11 char *pic_buf = new char[pic_len + 1]; //开辟一个空间在堆上 12 memset(pic_buf, 0, pic_len + 1); //清空文件指针 13 //读取文件内容 14 fread(pic_buf,sizeof(char),pic_len,fp); 15 //测试将文件再保存于D:中 16 /* 17 MessageBoxA(NULL, "文件开始", 0, 0); 18 FILE *fpw = fopen("C:\\AA.jpg","wb"); 19 fwrite(pic_buf,sizeof(char), pic_len, fpw); 20 fclose(fpw); //关闭文件流 21 MessageBoxA(NULL, "文件结束", 0, 0); 22 */ 23 fclose(fp); 24 25 return pic_buf; 26 } 27 28 void sendPic(char* addr, char * host, char *pathpic, char* picname, int port, string username, string psw) { 29 30 //先读取文件流 31 //实名图片读取,等级图片读取 32 int Spic_len, Dpic_len; 33 char *Spic_data=NULL, *Dpic_data=NULL; 34 35 Spic_data=ReadFile(pathpic, Spic_len); 36 Dpic_data = ReadFile(picname, Dpic_len); 37 std::string header(""); 38 std::string content(""); //实名文件 39 std::string nex_content(""); //等级文件 40 std::string u_content(""); //用户名 41 std::string p_content(""); //密码 42 43 //----------------------post头开始-------------------------------- 44 header += "POST "; 45 header += addr; 46 header += " HTTP/1.1\r\n"; 47 header += "Host: "; 48 header += host; 49 header += "\r\n"; 50 header += "Connection: Keep-Alive\r\n"; 51 header += "Accept: */*\r\n"; 52 header += "Pragma: no-cache\r\n"; 53 header += "Content-Type: multipart/form-data;boundary=71b23e4066ed\r\n"; 54 55 //用户名数据表单 56 u_content += "--71b23e4066ed\r\n"; 57 u_content += "Content-Disposition: form-data; name=\"u\"\r\n\r\n"; 58 u_content += username+"\r\n"; 59 60 //密码数据表单 61 p_content += "--71b23e4066ed\r\n"; 62 p_content += "Content-Disposition: form-data; name=\"p\"\r\n\r\n"; 63 p_content += psw+"\r\n"; 64 65 //发送文件数据 66 content += "--71b23e4066ed\r\n"; 67 content += "Content-Disposition: form-data; name=\"picurl\"; filename=\""; 68 content += pathpic; 69 content += "\"\r\n"; 70 content += "Content-Type: image/jpeg \r\n\r\n"; 71 72 //发送文件数据 73 nex_content += "\r\n--71b23e4066ed\r\n"; 74 nex_content += "Content-Disposition: form-data; name=\"id_account\"; filename=\""; 75 nex_content += picname; //picname; 76 nex_content += "\"\r\n"; 77 nex_content += "Content-Type: image/jpeg\r\n\r\n"; 78 79 //post尾时间戳 80 std::string strContent("\r\n--71b23e4066ed--\r\n"); 81 char temp[64] = { 0 }; 82 //注意下面这个参数Content-Length,这个参数值是:http请求头长度+请求尾长度+文件总长度 83 // 就分块传送 84 sprintf(temp, "Content-Length: %d\r\n\r\n", 85 content.length() + nex_content.length() +p_content.length()+u_content.length() + Spic_len + Dpic_len + strContent.length()); 86 header += temp; 87 std::string str_http_request; 88 str_http_request.append(header); 89 90 //----------------------post头结束----------------------------------- 91 //发送post头 92 send(sHost, str_http_request.c_str(), str_http_request.length(), 0); 93 char fBuff[1024]; 94 int buffsize = 1024; // 每个数据包存放文件的buffer大小 95 int nStart;//记录post初始位置 96 int nSize;//记录剩余文件大小 97 Sleep(0.2); 98 //发送用户名表单 99 send(sHost, u_content.c_str(), u_content.length(), 0); 100 Sleep(0.2); 101 //发送密码表单 102 send(sHost, p_content.c_str(), p_content.length(), 0); 103 Sleep(0.2); 104 //发送尾部 105 //发送格式 106 send(sHost, content.c_str(), content.length(), 0); 107 Sleep(0.2); 108 send(sHost, Spic_data, Spic_len, 0); 109 Sleep(0.2); 110 //发送等级图片数据 111 send(sHost, nex_content.c_str(), nex_content.length(), 0); 112 Sleep(0.2); 113 send(sHost, Dpic_data, Dpic_len, 0); 114 Sleep(0.2); 115 //如果数据是在够大,需要作调整,可以使用如下的方式,切割文件发送数据 116 /* 117 for (int i = 0; i < Spic_len; i += bufsize) 118 { 119 nStart = i; 120 if (i + bufsize + 1> Spic_len){ 121 nSize = Spic_len - i; 122 } 123 else{ 124 nSize = bufsize; 125 } 126 127 memcpy(fBuff, Spic_data + nStart, nSize); 128 ::send(sHost, fBuff, nSize, 0); 129 Sleep(0.2); //防止毡包 130 } 131 132 //发送等级图片数据 133 ::send(sHost, nex_content.c_str(), nex_content.length(), 0); 134 Sleep(0.2); 135 bufsize = 4096; 136 for (int i = 0; i < Dpic_len; i += bufsize) 137 { 138 nStart = i; 139 if (i + bufsize + 1> Dpic_len){ 140 nSize = Dpic_len - i; 141 } 142 else{ 143 nSize = bufsize; 144 } 145 146 memcpy(fBuff, Dpic_data + nStart, nSize); 147 ::send(sHost, fBuff, nSize, 0); 148 Sleep(0.2); //防止毡包 149 } 150 */ 151 /* 152 for (int i = 0; i < Dpic_len; i += nPacketBufferSize) 153 { 154 nStart = i; 155 if (i + nPacketBufferSize + 1> Dpic_len){ 156 nSize = Dpic_len - i; 157 } 158 else{ 159 nSize = nPacketBufferSize; 160 } 161 162 memcpy(fBuff, Dpic_data + nStart, nSize); 163 ::send(sHost, fBuff, nSize, 0); 164 Sleep(0.2); //防止毡包 165 }*/ 166 167 send(sHost, strContent.c_str(), strContent.length(), 0); 168 Sleep(0.2); 169 170 if (Spic_data == NULL) 171 { 172 MessageBox(NULL, L"文件数据为空", 0, 0); 173 } 174 //释放内存 175 delete Spic_data; 176 delete Dpic_data; 177 178 }
当这些基本做好了之后,就需要看返回的结果:
对于返回http返回结果协议头的简单解析: 如果需要深入研究去看 rfc2616,这里就简单的罗列一些100-500的简单的含义吧
100-199 用于指定客户端应相应的某些动作。
200-299 用于表示请求成功。
300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息。
400-499 用于指出客户端的错误。
500-599 用于支持服务器错误。
详细的文档,可以看看这个在线文档,http://tool.oschina.net/commons?type=5
学习的过程中参考过几位博主,此处表达谢意,终于对http在以前认知的基础上,再次的又重新的知识了一番!! 记录些这些,希望对以后学习的人,能够提供一点点帮助!!!