HanDs
管理员

[Visual Studio文章] 简单的TCP服务器 





学习中请遵循国家相关法律法规,黑客不作恶。没有网络安全就没有国家安全

本站需要登陆后才能查看

一、简单的TCP服务器


介绍

WinSock API是一套供Microsoft Windows操作系统使用的套接字程序库,它最初基于Berkeley套接

字,但是其中加入了一些Microsoft的特殊改动。在这篇文章中,我要试着给你介绍如何使用

WinSock来进行套接字程序设计,并假设你没有在任何操作系统上进行过网络编程的经验。
如果你只有一台单独的机器,那么不用着急,你仍然可以进行WinSock程序设计。你可以使用名为

localhost的本地回环地址,它的IP地址是127.0.0.1。这样一来,如果你在机器上运行了一个TCP

服务器,那么同一机器上的客户端程序就可以使用这个回环地址连接到服务器了。

简单的TCP服务器

在本文中,我将通过一个简单的TCP服务器来向你介绍WinSock,我们会一步一步地创建这个程序。

但是,在我们开始之前,你还必须做一些事情,这样我们才能为开始我们的WinSock程序做好准备


·首先,使用VC++ 6.0应用程序向导来创建一个Win32 console application。
·选择add support for MFC选项。
·打开stdafx.h文件,并添加这一行:#include <winsock2.h>。
·选择Project-Settings-Link,并在库模块列表中加入ws2_32.lib。

main函数

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    int nRetCode = 0;

    cout << "Press ESCAPE to terminate program\r\n";
    AfxBeginThread(ServerThread,0);
    while(_getch()!=27);

    return nRetCode;
}

我们在main()中所做的是开启一个线程,然后对一个_getch()调用进行循环。_getch()仅仅是等待

一个键的按下,并返回这个读入字符的ASCII值。我们一直循环,直到返回27这个值为止——既然

27是ESCAPE键的ASCII码。你可能想知道的是,即使我们按下了ESCAPE,我们开启的线程也还会是

活动的状态。不用为这些事情担心,因为当main()返回的时候,进程就会被终止,主线程开启的线

程也会被突然终止。

ServerThread函数

现在我所要做的事情就是把我们的ServerThread函数列出来,并使用代码的注释来解释相关的代码

行做了些什么。我们的TCP服务器主要做的事情是监听端口20248,这个数字也就是我在Code

Project的成员ID。这个过程中的事件是:当客户端连接的时候,服务器将会向客户端发回一条消

息告知它的IP地址,然后关闭连接并继续接收20248端口的连接。它还会在运行的控制台上打印出

连接来自的IP地址。总而言之,你可能会认为这是一个绝对没用的程序。事实上,你们中的有些人

甚至可能会认为它和Windows中的SNDREC32.EXE一样没用。我说,你们也忒苛刻了吧。

UINT  ServerThread(LPVOID pParam)
{
    cout << "Starting up TCP server\r\n";

    // SOCKET其实是unsigned int的一个typedef。
    // 在Unix中,套接字句柄就像文件句柄一样,都是unsigned int。
    // 既然在Windows下这些不是真的,那么我们就定义了一种新的数据类型,名为SOCKET。
    SOCKET server;

    // WSADATA是一个struct,WSAStartup的调用将会填充之。
    WSADATA wsaData;

    // sockaddr_in为TCP/IP套接字指定了套接字的地址。
    // 其它的协议都使用相似的结构。
    sockaddr_in local;

    // WSAStartup为程序调用WinSock进行了初始化。
    // 第一个参数指定了程序允许使用的WinSock规范的最高版本。
    int wsaret=WSAStartup(0x101,&wsaData);

    // 如果成功,WSAStartup返回零。
    // 如果失败,我们就退出。
    if(wsaret!=0)
    {
        return 0;
    }

    // 现在我们来为sockaddr_in结构赋值。
    local.sin_family=AF_INET; // 地址族
    local.sin_addr.s_addr=INADDR_ANY; // 网际IP地址
    local.sin_port=htons((u_short)20248); // 使用的端口

    // 由socket函数创建我们的SOCKET。
    server=socket(AF_INET,SOCK_STREAM,0);

    // 如果socket()函数失败,我们就退出。
    if(server==INVALID_SOCKET)
    {
        return 0;
    }

    // bind将我们刚创建的套接字和sockaddr_in结构联系起来。
    // 它主要使用本地地址及一个特定的端口来连接套接字。
    // 如果它返回非零值,就表示出现错误。
    if(bind(server,(sockaddr*)&local,sizeof(local))!=0)
    {
        return 0;
    }

    // listen命令套接字监听来自客户端的连接。
    // 第二个参数是最大连接数。
    if(listen(server,10)!=0)
    {
        return 0;
    }

    // 我们需要一些变量来保存客户端的套接字,因此我们在此声明之。
    SOCKET client;
    sockaddr_in from;
    int fromlen=sizeof(from);

    while(true) // 无限循环
    {
        char temp[512];

        // accept()将会接收即将到来的客户端连接。
        client=accept(server,
            (struct sockaddr*)&from,&fromlen);

        sprintf(temp,"Your IP is %s\r\n",inet_ntoa(from.sin_addr));

        // 我们简单地向客户端发送这个字符串。
        send(client,temp,strlen(temp),0);
        cout << "Connection from " << inet_ntoa(from.sin_addr) <<"\r\n";

        // 关闭客户端套接字
        closesocket(client);

    }

    // closesocket()关闭套接字,并释放套接字描述符。
    closesocket(server);

    // 最初这个函数也许有些用处,现在保留它只是为了向后兼容。
    // 但是调用它可能会更安全,因为我相信某些实现会使用它来结束WS2_32.DLL的使用。
    WSACleanup();

    return 0;
}

测试

运行这个服务器,并在它运行的时候使用telnet来连接机器的20248端口。如果你是在同一台机器

上使用,那么就连接到localhost。

示例输出

我们将会在服务器上看到这样的输出:

E:\work\Server\Debug>server
Press ESCAPE to terminate program
Starting up TCP server
Connection from 203.200.100.122
Connection from 127.0.0.1
E:\work\Server\Debug>

这是客户端得到的:

nish@sumida:~$ telnet 202.89.211.88 20248
Trying 202.89.211.88...
Connected to 202.89.211.88.
Escape character is '^]'.
Your IP is 203.200.100.122
Connection closed by foreign host.
nish@sumida:~$

总结

呃,在本文中你了解了如何创建一个简单的TCP服务器。在以后的文章中,我会给你一些更多的材

料,你可以通过这些材料创建一个合适的TCP客户端。如果有谁对于编译这些代码有问题的话可以

mail我,我会发给你一个压缩了的工程。谢谢您的阅读。


 
二、简单的TCP客户端

 

介绍
本文是
《Winsock程序设计入门(1) - 简单的TCP服务器》
一文的结局,如果你还没有读过第1部分的话,我还是建议你首先读一下。在本文中,我将示范给

你如何编写一个简单的TCP客户端程序。我们要编写一个程序,这个程序将连接到一个HTTP服务器

,并获得一个文件。
一个简单的TCP客户端程序流程
1、使用WSAStartup()初始化WinSock库。
2、使用socket()创建一个IPPROTO_TCP SOCKET。
3、使用gethostbyname()/gethostbyaddr()获取主机信息。
4、使用connect()和我们创建的套接字连接服务器。
5、使用send()/recv()发送和接收数据,直到我们的TCP会话结束。
6、使用closesocket()关闭套接字连接。
7、使用WSACleanup()释放WinSock。
初始化WinSock
正如其它每个WinSock程序一样,我们需要初始化WinSock库。这也基本上是一种检查WinSock是否

在当前系统可用的方法,对于以前的版本,我们当然希望是这样。
int wsaret=WSAStartup(0x101,&wsaData);
if(wsaret)
    return;
创建SOCKET
套接字是一种实体,它担当了客户端和服务器之间的端点。当客户端连接到服务器之后,就会存在

两个套接字——客户端一边的套接字和相应的服务器一边的套接字。让我们来称它们为CLIENTSOCK

和SERVERSOCK。当客户端在CLIENTSOCK使用send()时,服务器可以在SERVERSOCK使用recv()来接收

客户端所发送的数据,反之亦然。对于我们的目的,我们使用一个名为socket()的函数来创建套接

字。
SOCKET conn;
conn=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(conn==INVALID_SOCKET)
    return;
获取主机信息
显然,我们在连接到主机(服务器)之前,要获取它的信息。我们可以使用两个函数——

gethostbyname()和gethostbyaddr()。当我们拥有服务器的DNS名称时,我们可以使用

gethostbyname()函数,例如codeproject.com或ftp.myserver.org之类的名称。当我们拥有要连接

的服务器的IP地址时,可以使用gethostbyaddr()函数,例如192.168.1.1或202.54.1.100。
显然,我们希望能使我们的最终用户既能使用DNS名称,也能使用IP地址。那么,为了这些工作对

他来说透明,我们需要像下面这样玩一个小把戏。我们对入口字符串使用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()函数用于向目标服务器建立连接。我们向它传递我们先前创建的套接字和一个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;
}
会话
当套接字连接建立后,客户端和服务器就可以通过send()和recv()来发送/接收数据了。这通常称

为TCP会话。对于我们的特定情况,我们需要进行HTTP会话。和那些复杂的SMTP或POP3协议相比,

它还是比较简单的。HTTP的GET命令用于从HTTP服务器上获取文件。这个文件可以是HTML文件、图

像文件、压缩文件、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(),直到它返回零,这时候我们就会知道服务器已经将数据发送完毕了。并

且,对于我们的特定情况,我们可以将这些数据写入文件,就像我们要下载并保存这个文件一样。
while(y=recv(conn,buff,512,0))
{
    f.Write(buff,y);
}
关闭连接
现在我们的会话结束了,我们必须关闭连接。在我们的情况下,HTTP连接在文件发送完毕之后就会

被服务器关闭了,但是这不要紧,我们仍然需要关闭我们的套接字并释放资源。在更加复杂的会话

中,我们通常在调用closesocket()之前调用shutdown()来确定缓冲区已经被刷新,否则可能会有

部分数据丢失。
closesocket(conn);
释放WinSock
我们调用WSACleanup()来结束WinSock的使用。
WSACleanup();
感谢您的阅读。


学习中请遵守法律法规,本网站内容均来自于互联网,本网站不负担法律责任
简单
#1楼
发帖时间:2016-7-9   |   查看数:0   |   回复数:0
游客组
快速回复