简介
该文是开始Winsock编程 - 简单的TCP服务器端的续篇,如果您没有看过前文那我提醒你先看前文.本文将展示如何编写一个简单的TCP客户端程序. 我们的程序将链接到一个HTTP服务器并接收一个文件.
编写一个简单的TCP客户端程序流程
1.使用WSAStartup()初始化WinSock库
2.使用socket()创建IPPROTO_TCP类型的SOCKET
3.使用gethostbyname()/gethostbyaddr()获得主机信息
4.利用我们创建的socket并使用connect()链接到服务器端
5.使用send()/recv()发送或接收数据直到我们的tcp会话结束
6.使用closesocket()关闭socket连接
7.使用WSACleanup()注销WinSock
初始化WinSock
像任何其他的WinSock程序一样我们需初始化WinSock库. 我们这样做是为了检查我们希望使用的WinSock版本在我们的系统上是否可用。
int wsaret=WSAStartup(0x101,&wsaData);
if(wsaret)
return;
创建SOCKET
socket作为节点界于客户端和服务器端. 当一个客户端连接到一个服务器端,将同时存在两个sockets. 即客户端的socket和与之对应的服务器端的socket.我们称之为CLIENTSOCK和ERVERSOCK。当客户端在CLIENTSOCK边使用send()服务器端则使用recv()在SERVERSOCK边接收客户端发送的数据。相似的情况在服务器端也成立.为了达到我们的目标,我们使用socket()创建socket。
SOCKET conn;
conn=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(conn==INVALID_SOCKET)
return;
获得主机信息
很明显在我们与服务器端进行连接之前我们必须活得服务器端的相关信息。这里有两个函数可供使用 - gethostbyname()和gethostbyaddr()。当我们知道服务器端的DNS信息我们使用gethostbyname()函数, 比如xxx.com or ftp.myserver.org。当我们明确知道服务器端的IP地址时我们使用gethostbyaddr()函数, 比如192.168.1.1或202.54.1.100.
很明显我们得让终端用户在DNS和IP地址之间有所选择,即都有效。因此为了让这部分对终端用户透明,我们的进行一些特殊处理.我们在输入字符串中使用inet_addr()函数进行校验。
inet_addr()函数将一个IP地址转化为标准格式的网络地址. 因此如果其返回失败, 我们就知道此字符串不是一个IP地址,如果执行成功我们则假定其为一个有效的IP地址。
if(inet_addr(servername)==INADDR_NONE)
{
hp=gethostbyname(servername);
}
else
{
addr=inet_addr(servername);
hp=gethostbyaddr((char*)&addr,sizeof(addr),AF_INET);
}
if(hp==NULL)
{
closesocket(conn);
return;
}
连接到服务器端
connect()函数用于建立到目标服务器的连接. 我们将早先创建的socket以及一个sockaddr结构传给它. 用gethostbyname()/gethostbyaddr()获得的主机地址以及一个端口号填充sockaddr结构体.
server.sin_addr.s_addr=*((unsigned long*)hp->h_addr);
server.sin_family=AF_INET;
server.sin_port=htons(80);
if(connect(conn,(struct sockaddr*)&server,sizeof(server)))
{
closesocket(conn);
return;
}
聊天
一旦socket连接被确立,客户端和服务器端可使用send()和recv()传递数据.这广泛用在TCP会话中。 在我们的事例中我们使用HTTP会话,这个对比SMTP和POP3协议来说就简单很多. HTTP GET 用于从HTTP服务器接收文件. 它可能是任何一种类型的文件,比如HTML文件、image文件、zip文件以及MP3文件等. 它们通过最简单的形式传输. 这些命令也有其他复杂的用法。
GET http-path-to-file\r\n\r\n
在我们的程序中我们使用上面的形式发送GET命令 :-
sprintf(buff,"GET %s\r\n\r\n",filepath);
send(conn,buff,strlen(buff),0);
一旦我们发送了诸如上面形式的命令我们知道服务器将开始向我们发送我们请求的文件。就像我们使用send() 发送我们的命令一样我们使用recv()接收将要从服务器端发给我么的文件。
我们等待recv()返回0,此时我们知道服务器端数据传送完毕即我们接收完成. 与此同时我们将所有数据写入文件保存或是下载到文件。
while(y=recv(conn,buff,512,0))
{
f.Write(buff,y);
}
关闭连接
到此我们的会话完成,我们必须关闭连接.。在我们的事例中服务器端在传送完成文件后关闭HTTP连接,这样处理是可以的.我们需要关闭socket并释放其占用的资源。在许多复杂的情况下我们调用closesocket()调用shutdown()以确保缓冲区被清除。否则的话我们将遇到数据丢失情况。
closesocket(conn);
注销WinSock
我们调用WSACleanup()撤销我们的WinSock.
WSACleanup();
谢谢。