1. 前言:
最近对HTTP与SOCKET很好奇。所以详细探究了下,探究过程整理如下:
2. 概念
SOCKET 对TCP/IP协议封装的接口,非协议。有TCP、UDP(后续再详谈)的区别,分长连接和短连接。
HTTP 基于TCP/IP协议的协议,协议。只分长短链接(HTTP1.1协议支持长连接后续再谈)
我一般都认为HTTP是一种特殊的SOCEKT。其特殊性,请看下面的代码(win10 64位,vs2013,C++代码示例)。
3. 客户端代码
3.1 SOCKET TCP 代码
//////////////////////////////////////////////////////////
//\
//\ socket TCP client
//\
//////////////////////////////////////////////////////////
#include
#include
#include
#include
#include
#include
#pragma comment(lib, "ws2_32.lib") /* WinSock使用的库函数 */
using namespace std;
int main()
{
// 初始化WSA
WSADATA ws;
WSAStartup(MAKEWORD(2, 0), &ws);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999); // SOCKET服务端监听端口
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // socket服务端IP
// 创建socket
SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd == INVALID_SOCKET)
{
printf("socket error!\n");
return -1;
}
// 链接服务端
int ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if (ret == SOCKET_ERROR)
{
printf("connect failed!\n");
// 断开socket
closesocket(fd);
return -1;
}
// 发送信息
char *data = "hello server! I'm socket client!\n";
ret = send(fd, data, strlen(data), 0);
if (ret <= 0)
{
printf("send error!\n");
// 断开socket
closesocket(fd);
return -1;
}
// 接收服务端信息
char buf[1024] = { 0 };
ret = recv(fd, buf, 1024, 0);
if (ret <= 0)
{
printf("recv error!\n");
// 断开socket
closesocket(fd);
return -1;
}
printf("recv[%s]\n", buf);
// 断开socket
closesocket(fd);
// 逆初始化wsa
WSACleanup();
return 0;
}
3.2 HTTP 代码
//////////////////////////////////////////////////////////
//\
//\ HTTP client
//\
//////////////////////////////////////////////////////////
#include
#include
#include
#include
#include
#include
#include
#include
#pragma comment(lib, "ws2_32.lib") /* WinSock使用的库函数 */
using namespace std;
// 通过url解析出IP和端口
bool ParseUrl(string &url, string &ip, int &port)
{
// 去除http://
size_t pos = url.find("//");
if (pos == string::npos)
{
printf("url error! [url:%s]\n", url.c_str());
return false;
}
string tmp = url.substr(pos + 2);
pos = tmp.find(":");
ip = tmp.substr(0, pos);
port = atoi(tmp.substr(pos + 1).c_str());
return true;
}
void PostData(string &content, string &url)
{
// 解析IP和PORT
string ip = "";
int port = 0;
if (ParseUrl(url, ip, port) == false)
return;
SOCKET s_fd;
sockaddr_in c_addr;
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(port);
c_addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
s_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int ret = connect(s_fd, (struct sockaddr *)&c_addr, sizeof(c_addr));
if (ret == SOCKET_ERROR)
{
printf("connect error!\n");
closesocket(s_fd);
return;
}
// HTTP 头部必须有POST(或者同类别的其他字段),POST是body可以为空
// 但是必须有,body必须与header间隔两个回车换行
// POST 后面紧跟的/是路径
stringstream str("");
// 若url为 http://192.168.0.151:8080/test则应该修改为: POST /test HTTP/1.1\r\n
str << "POST / HTTP/1.1\r\n";
str << "Host:" << url << "\r\n";
str << "Content-Length:" << content.length() << "\r\n\r\n";
str << content;
int len = send(s_fd, str.str().c_str(), str.str().length(), 0);
if (len <= 0)
{
printf("Post error!\n");
closesocket(s_fd);
return;
}
char buf[1025] = { 0 };
int recv_len = 0;
do
{
recv_len = recv(s_fd, buf, 1024, 0);
if (recv_len > 0)
{
/* 在屏幕上输出 */
buf[recv_len] = 0;
printf("%s", buf);
}
if (recv_len < 1024)
break;
} while (recv_len > 0);
printf("\n");
// 发送完毕后,断开socket
closesocket(s_fd);
}
int main()
{
WSADATA ws;
WSAStartup(MAKEWORD(2, 0), &ws);
string test = "1 2 3 4"; // HTTP body
string url = "http://192.168.0.151:8080";
PostData(test, url);
WSACleanup();
return 0;
}
4. 服务端代码
#include
#include
#include
#include
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
using namespace std;
string response = "HTTP/1.1 200 OK\r\n\Content-Length: 14\r\n\Content-Type : text / plain\r\n\r\n\{ \"status\":200 }";
int main()
{
//初始化WSA
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
// 创建套接字
SOCKET s_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s_fd == INVALID_SOCKET)
{
printf("socket error!\n");
return -1;
}
// 绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8090);
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(s_fd, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error!\n");
return 0;
}
// 开始监听
if (listen(s_fd, 5) == SOCKET_ERROR)
{
printf("listen error!\n");
return 0;
}
// 循环接收数据
SOCKET c_fd;
sockaddr_in client;
int len = sizeof(client);
char tmp[1024] = { 0 };
while (true)
{
printf("等待链接...\n");
c_fd = accept(s_fd, (SOCKADDR *)&client, &len);
if (c_fd == INVALID_SOCKET)
{
printf("connect error!\n");
continue;
}
printf("接收到一个链接: %s \n", inet_ntoa(client.sin_addr));
int ret = recv(c_fd, tmp, 1024, 0);
if (ret > 0)
{
printf("接收内容为: [tmp:%s]\n", tmp);
}
// 回复
send(c_fd, response.c_str(), response.length(), 0);
memset(tmp, 0, 1024);
// 关闭句柄
closesocket(c_fd);
}
closesocket(s_fd);
WSACleanup();
return 0;
}
5. 总结
HTTP通信方式与SOCKET短连接一样,只不过在发送的消息格式以及内容上有些要求。