1.建立与服务器的TCP链接
2.在TCP的链接下 socket的基础下发送http格式的协议请求
3.服务器在tcp链接socket 返回http协议的reponse
首先需要将域名翻译成ip然后通过tcp链接ip地址和端口最后发送http协议
关于文中的 hostent结构体的定义
struct hostent
{
char *h_name; //正式主机名
char **h_aliases; //主机别名
int h_addrtype; //主机IP地址类型:IPV4-AF_INET
int h_length; //主机IP地址字节长度,对于IPv4是四字节,即32位
char **h_addr_list; //主机的IP地址列表
};
#define h_addr h_addr_list[0] //保存的是IP地址
关于struct sockaddr_in结构体内容定义
struct sockaddr_in{
sa_family_t sin_family; //地址族
uint16_t sin_port; //端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero; //预留未使用
};
struct in_addr{
In_addr_t s_addr; //32位IPv4地址
};
阻塞和非阻塞
如果socket是阻塞 那read()去读取数据的时候 里面没有数据 那也会挂起无法读取到数据便一直等待数据
如果socket是非阻塞 那read()去读数据的时候 就算没有数据也会立刻返回 就不会挂起
做服务器的时候会优先选择非阻塞的io
关于组合报文头部(格式非常重要!!!
sprintf(buffer,"GET %s %s\r\n\
Host: %s\r\n\
%s\r\n\
\r\n",
resource, HTTP_VERSION,
hostname,
CONNETION_TYPE
);
//注意 Host,%s,\r前面不能有空格!!!!出现空格格式就会不符合报文规范服务器就会无法识别
下列是本文中实现的tcp链接 服务器请求资源的源码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#define HTTP_VERSION "HTTP/1.1"
#define CONNETION_TYPE "Connection: close\r\n"
#define BUFFER_SIZE 4096
char* host_ip(const char *hostname)
{
struct hostent *host_entry = gethostbyname(hostname); //这个功能就是上一篇文章中讲述的DNS域名转换成ip的操作
//192.168.1.126 ->
//inet_ntoa() 将无符号int 转化为 char*
//0x12121212 -> xxx.xxx.xxx.xxx
if (host_entry)
{
return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);
}
return NULL;
}
int http_create_socket(char *ip)
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in sin = { 0 };
sin.sin_family = AF_INET;
sin.sin_port = htons(80); //端口号
sin.sin_addr.s_addr = inet_addr(ip); //存储IP 这个inet_addr是和inet_ntoa想法的函数
if (0 != connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)))
{
return -1;
}
fcntl(sockfd, F_SETFL, O_NONBLOCK);//设置非阻塞的io
return sockfd;
}
//hostname 是域名 resource 是域名后面的/后面跟着的
char * http_send_request(const char *hostname , const char *resource)
{
//域名转ip
char* ip = host_ip(hostname);
//与服务器建立链接
int sockfd = http_create_socket(ip);
char buffer[BUFFER_SIZE] = {0};
//组合请求报文头部数据
sprintf(buffer,"GET %s %s\r\n\
Host: %s\r\n\
%s\r\n\
\r\n",
resource, HTTP_VERSION,
hostname,
CONNETION_TYPE
);
//发送数据
send(sockfd,buffer,strlen(buffer),0);
//接收数据 select
//select监测网络io中有没有可读数据
fd_set fdread; //fd_set是一个集合将所有的网络io放在一个集合中去一起判断有无数据 有数据置1无置0
FD_ZERO(&fdread); //先初始化将fd置空
FD_SET(sockfd, &fdread); //sockfd 需要监测的网络io ,fd_set集合
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
char* result = malloc(sizeof(int));
//使用malloc分配空间时一定要用memset清空 防止指针是无效数据 可能这片内存存放其它数据
memset(result, 0, sizeof(int));
while (1) {
int selection = select(sockfd + 1, &fdread, NULL, NULL, &tv); //select(maxfd+1,&rset 哪个io可读,&wset 哪个io可写.&errorset 哪个io出错,&timeval指select多长时间去遍历一次io
if (!selection || !FD_ISSET(sockfd, &fdread)) { //FD_ISSET用来判断fdread中的数据是否和sockfd中的一致
break;
}
else {
//清空并重新初始化buffer
memset(buffer, 0, BUFFER_SIZE);
int len = recv(sockfd, buffer, BUFFER_SIZE, 0);
if (len == 0) { // disconnect recv返回0 对方关闭了服务器 导致链接失败
break;
}
//可能会多次recv去读数据 因为可能一次buffer存放不完
result = realloc(result, (strlen(result) + len + 1) * sizeof(char)); //重新分配空间
strncat(result, buffer, len);
}
}
return result;
}
int main(int argc, char* argv[])
{
if (argc < 3) return -1;
char* response = http_send_request(argv[1], argv[2]);
printf("response : %s\n", response);
free(response);
}