C语言——网络客户端

在上一篇文章C语言——网络与套接字介绍了网络服务器的相关知识。现在我们来看看网络客户端的相关知识。

我们来写一个HTTP协议的网络客户端。说到HTTP协议,其实就像我们在C语言——网络与套接字自定义的IAHP协议一样。**协议是一段结构化对话。**网络客户端和服务器必须按照结构化的对话来进行通信。

打开telnet看看是如何下载网页的。

~$ telnet www.csua.berkeley.edu 80
Trying 128.32.112.230...
Connected to tap.csua.berkeley.edu.
Escape character is '^]'.
GET /officers.html HTTP/1.0

输入GET /officers.html HTTP/1.0 然后按两次回车,网络服务器响应如下:

~$ telnet www.csua.berkeley.edu 80
Trying 128.32.112.230...
Connected to tap.csua.berkeley.edu.
Escape character is '^]'.
GET /officers.html HTTP/1.0

HTTP/1.1 404 Not Found
Server: nginx/1.10.3
Date: Thu, 21 Feb 2019 03:43:36 GMT
Content-Type: text/html
Content-Length: 176
Connection: close
ETag: "5bd8e5b2-b0"

<html>
<head><title>404 Domain Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Domain Not Found</h1></center>
<hr><center>nginx/1.10.3</center>
</body>
</html>
Connection closed by foreign host.
~$

从上面可以看出客户端连上网络服务器后,至少需要一样东西:
GET命令

GET /officers.html HTTP/1.0

客户端和服务器使用套接字通信,但两者获取套接字的方式不同,服务器要用BLAB四步才能获得套接字:

  1. 绑定端口
  2. 监听
  3. 接受连接
  4. 开始通信

服务器终其一生都在等待新客户端的连接。在客户端连接之前,它什么事都不能做。但客户端就不一样了,客户端想什么时候连接服务器并开始通信都可以。客户端只需两步就可以取得套接字:

  1. 连接远程端口
  2. 开始通信

服务器在连接网络时必须决定使用哪个端口,而客户端除了要知道远程服务器使用的端口号还需要知道远程服务器的IP地址。

创建IP地址套接字

客户端知道了服务器的端口号和IP地址就可以创建客户端套接字了,客户端和服务器处理套接字的方式不同,服务器会把套接字绑定到本地端口,而客户端会把套接字连接至远程端口:

//创建套接字
int d_sock = socket(PF_INET,SOCK_STREAM,0);
struct sockaddr_in si;
        memset(&si,0,sizeof(si));
        si.sin_family = PF_INET;
        si.sin_addr.s_addr = inet_addr(host);
        si.sin_port = htons(port);
        
        if(d_sock == -1){
                error("Can't open socket");
        }
        //把套接字连接到远程端口
        int c = connect(d_sock,(struct sockaddr *)&si,sizeof(si));
        if(c == -1){
                error("Can't connect to socket");
        }

创建域名套接字

域名系统是一本巨大的通讯录。计算机向网络发送数据包时需要用到IP地址,而DNS可以把域名转化为IP地址。通常情况下,应该让客户端用DNS来创建套接字,这样用户就不用自已去查找IP地址了。那么就需要用另外一种方式来创建客户端套接字。
利用getaddrinfo()函数(这个函数可以根据域名找IP地址)在堆上创建一种叫名字资源的新数据结构,然后给定域名和端口号,就可以得到名字资源。名字资源把计算机需要的IP地址隐藏起来,大型网站通常有好几个IP地址,代码会从中挑选一个。随后可以用名字资源创建套接字。

        //用名字资源创建套接字了
        struct addrinfo *res;
        struct addrinfo  hints;
        memset(&hints,0,sizeof(hints));
        hints.ai_family = PF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        if(getaddrinfo(host,port,&hints,&res) == -1){
                error("Can't resolve the address");
        }
        //创建套接字
        int d_sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
        if(d_sock == -1){
                error("Can't open socket");
        }
        //把套接字连接到远程端口
        int c = connect(d_sock,res->ai_addr,res->ai_addrlen);
        //因为名字资源在堆上创建,所以要用一个叫freeaddrinfo()函数清除它。
        freeaddrinfo(res);
        if(c == -1){
                error("Can't connect to socket");
        }

一旦把套接字连接到远程端口,就可以用recv()和send()函数读写数据了。
我们给出一个完整的运行实例test1.c:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <strings.h>

void error(char *msg);
int open_socket(char *host,char *port);

int open_socket_ip(char *host,int port);
int say(int socket,char *s);

int main(int argc,char *argv[]){
	int d_sock;
	d_sock = open_socket("www.baidu.com","80");
	char buf[255];
	sprintf(buf,"GET /officers.html HTTP/1.0\r\n\r\n");
	say(d_sock,buf);
	char rec[255];
	int bytesRcvd = recv(d_sock,rec,255,0);
	while(bytesRcvd){
	

		if(bytesRcvd == -1){

			error("Can't reed from server!");
		}
		rec[bytesRcvd] = '\0';
		printf("%s",rec);
		bytesRcvd = recv(d_sock,rec,255,0);
	}
	close(d_sock);
	return 0;
}
void error(char *msg){
	fprintf(stderr,"%s:%s\n",msg,strerror(errno));
	exit(1);
}

int open_socket(char *host,char *port){
	struct addrinfo *res;
	struct addrinfo  hints;
	memset(&hints,0,sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	if(getaddrinfo(host,port,&hints,&res) == -1){
		error("Can't resolve the address");
	}
	int d_sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
	if(d_sock == -1){
		error("Can't open socket");
	}
	int c = connect(d_sock,res->ai_addr,res->ai_addrlen);
	freeaddrinfo(res);
	if(c == -1){
		error("Can't connect to socket");
	}
	return d_sock;
}


int say(int socket,char *s){
	int result = send(socket,s,strlen(s),0);
	if(result == -1){
		fprintf(stderr,"%s:%s\n","Error talking to the server",strerror(errno));
	}
	return result;
}

int open_socket_ip(char *host,int port){
	struct sockaddr_in si;
	memset(&si,0,sizeof(si));
	si.sin_family = PF_INET;
	si.sin_addr.s_addr = inet_addr(host);
	si.sin_port = htons(port);
	int d_sock = socket(PF_INET,SOCK_STREAM,0);
	if(d_sock == -1){
		error("Can't open socket");
	}
	int c = connect(d_sock,(struct sockaddr *)&si,sizeof(si));
	if(c == -1){
		error("Can't connect to socket");
	}
	return d_sock;
}

编译运行一下:

~/Desktop/MyC$ gcc test1.c -o test1
~/Desktop/MyC$ ./test1
HTTP/1.0 302 Found
Cache-Control: max-age=86400
Content-Length: 222
Content-Type: text/html; charset=iso-8859-1
Date: Thu, 21 Feb 2019 03:59:50 GMT
Expires: Fri, 22 Feb 2019 03:59:50 GMT
Location: https://www.baidu.com/search/error.html
Server: Apache

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://www.baidu.com/search/error.html">here</a>.</p>
</body></html>

还记得我们在C语言——网络与套接字自定义了一个IAHP网络协议,那么我们为那个服务器写一个客户端吧,使用IAHP协议通信。
test1.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <strings.h>
//输入错误消息并退出
void error(char *msg);
//使用域名和端口打开套接字
int open_socket(char *host,char *port);
//使用IP和端口打开套接字
int open_socket_ip(char *host,int port);
//发消息
int say(int socket,char *s);
//读取消息
int read_in(int socket,char *buf,int len);

int main(int argc,char *argv[]){
	int d_sock = open_socket_ip("127.0.0.1",30000);//通信用的网络套接字
	say(d_sock,"Who are you?\r\n"); //根据IAHP协议发送消息
	char rec[255];
	int bytesRcvd = recv(d_sock,rec,255,0);
	while(1){
	   	say(d_sock,"Apple who?\r\n\r\n"); //根据IAHP协议发送消息
		if(bytesRcvd == -1){
			error("Can't reed from server!");
		}
		rec[bytesRcvd] = '\0';
		printf("%s",rec);
		bytesRcvd = recv(d_sock,rec,255,0); //接收消息
	}
	close(d_sock);
	return 0;
}
void error(char *msg){
	fprintf(stderr,"%s:%s\n",msg,strerror(errno));
	exit(1);
}

int open_socket(char *host,char *port){
	struct addrinfo *res;
	struct addrinfo  hints;
	memset(&hints,0,sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	if(getaddrinfo(host,port,&hints,&res) == -1){
		error("Can't resolve the address");
	}
	int d_sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
	if(d_sock == -1){
		error("Can't open socket");
	}
	int c = connect(d_sock,res->ai_addr,res->ai_addrlen);
	freeaddrinfo(res);
	if(c == -1){
		error("Can't connect to socket");
	}
	return d_sock;
}


int say(int socket,char *s){
	int result = send(socket,s,strlen(s),0);
	if(result == -1){
		fprintf(stderr,"%s:%s\n","Error talking to the server",strerror(errno));
	}
	return result;
}

int open_socket_ip(char *host,int port){
	
	int d_sock = socket(PF_INET,SOCK_STREAM,0);
	struct sockaddr_in si;
	memset(&si,0,sizeof(si));
	si.sin_family = PF_INET;
	si.sin_addr.s_addr = inet_addr(host);
	si.sin_port = htons(port);
	if(d_sock == -1){
		error("Can't open socket");
	}
	int c = connect(d_sock,(struct sockaddr *)&si,sizeof(si));
	if(c == -1){
		error("Can't connect to socket");
	}
	return d_sock;
}

int read_in(int socket,char *buf,int len){
        char *s = buf;
        int slen = len;
        int c = recv(socket,s,slen,0);
        while((c>0) && (s[c-1] != '\n')){//循环读取字符,直到没有字符可以读或读到了\n
                s += c;slen -= c;
                c = recv(socket,s,slen,0);
        }
        if(c <0){
                return c;//防止错误
        }else if(c == 0){
                buf[0] = '\0';//什么也没有读到,返回一个空字符串
        }else{
                s[c-1] = '\0';//用\0替换\r
        }

        return len - slen;
}

编译运行一下:
先启动服务器:

~/Desktop/MyC$ ./test
Waiting for connection
|

再启动客户端:

~/Desktop/MyC$ gcc test1.c -o test1
~/Desktop/MyC$ ./test1
Internet Apple Hello Protocol Server 
 Version 1.0
 Hi ! Hi !
>Apple
>Ha Ha Stupid
<html><body>hello world!</body></html>

注:html部分是后来在服务器端加上的。
所以网络客户端是很简单的,建立好套接字,然后去连接远程端口,就可以与服务器通信了。上面仅仅为了显示用而已。

谢谢阅读!

猜你喜欢

转载自blog.csdn.net/weixin_40763897/article/details/87855824