原创 winsock编程入门

2009-8-18 16:33 2763 4 4 分类: 处理器与DSP
    什么是Socket

<?XML:NAMESPACE PREFIX = O />


接触网络编程当然要了解SocketSocket(套接字)是一种网络编程接口,Socket提供了很多灵活的函数,帮助程序员写出高效的网络应用。Socket分为BSD UNIXwindows两个版本。



win32平台上的Winsock编程都要经过下列基本步骤:


定义变量——获得Winsock版本——加载Winsock——初始化——创建套接字——设置套接字——关闭套接字——卸载Winsock



使用winsock2 API编程,必须包含头文件winsock2.h (链接环境WS2_32.LIB),头文件winsock.hWSOCK32.LIB)是为了兼容winsock1程序时使用的,另外mswsock.hMSWSOCK.DLL)是微软的扩展类,用于开发高性能的winsock程序。



准备好后,你就可以着手建立你的第一个网络程序了。



二,           Socket编程的基本过程


Socket通信分为面向连接的通信(TCP)和面向无连接的通信(UDP),通信流程如下:


                       


                    面向连接的通信


 





                 无连接的通信




1Winsock初始化和结束



每一个winsock应用程序必须首先加载相应的winsock dll版本。方法是调用:


int WSAStartup(


    WORD wVersionRequested,     库版本,高字节副版本,低字节主版本


    LPWSADATA lpWSAData         结构指针,函数自动填充该结构。


);    函数调用成功返回0


可以用宏MAKEWORD(x, y)用来指定第一个参数的值


 


2,建立套接字



套接字是传输提供者的一个句柄。


SOCKET socket (


    int af,          


    int type,        


    int protocol      IPPROTO_TCPIPPROTO_UDP 0(如果不想指定)


);


第一个参数指定通信协议的协议族,AF_INET(IPv4) AF_INET6(IPv6)(因为Socket是网络编程接口而不是一个协议,它使用流行的网络协议(TCP/IPIPX)为应用程序提供的一个编程接口。)


第二个参数指定要创建的套接字的类型。SOCK_STREAM(TCP流套接字), SOCK_ DGRAM(UDP 数据包套接字)SOCK_RAW(原始套接字)


第三个参数指定应用程序所指定应用程序所使用的通信协议。


函数成功返回套接字描述符,失败返回INVALID_SOCKET


 


3,配置套接字



当创建一个套接字后,再进行网络通信之前,必须先配置Socket。面向连接的客户端Socket通过调用connect函数在Socket数据结构中保存地址和远端信息。无连接客户端,服务端以及面向连接Socket的服务端,通过调用bind函数来配置本地信息。


int bind(


    SOCKET                     s,                 创建的套接字


    const struct sockaddr FAR* name,       指向地址缓冲区的指针


    int                        namelen     地址缓冲区的大小


);


成功返回0失败返回SOCKET_ERROR


当创建一个套接字后,套接字数据结构中有一个默认的IP地址和默认的端口号。一个服务程序必须调用bind函数来给其绑定一个IP地址和一个特定的端口号。


第二个参数指定一个sockaddr结构定义如下:


struct sockaddr {


        u_short    sa_family;


        char       sa_data[14];


};


我们通常使用另外一个等价的地址结构:


struct sockaddr_in {


        short   sin_family;


        u_short sin_port;


        struct  in_addr sin_addr;


        char    sin_zero[8];


};


其中sin_family是通信协议族,


sin_port指明端口号,


sin_addr结构中有一个字段s_addr,表示IP地址,该字段是一个整数,

一般用函数inet_addr把点分字符串形式的IP地址转化成unsigned long型的整数值。

如果指定为htonl(INADDR_ANY),那么无论哪个网段上的客户机都能与该服务器通信,

否则,只有与指定IP地址处于同一网段上的客户机能与该服务器通信。


sin_zero[8]为填充,使两个结构大小相同。


 


有一些细节学要说明:   在计算机把IP地址和端口号指定成多字节时,这个数是按“主机字节”(host-byte)顺序表示的,不同的处理器对数的表示方法有“大头”(big-endian——最有意义的字节到最无意义的字节)和“小头”(little-endian)两种形式。但是如果在网络上指定IP地址和端口号时,必须按照big-endian 的形式,一般称之为“网络字节”(network-byte)顺序。


以下几个函数将主机字节顺序转换成网络字节顺序:


u_long htonl(u_long hostlong);


 


int WSAHtonl(


    SOCKET s,


    u_long hostlong,


    u_long FAR * lpnetlong               通过指针返回网络字节顺序的4个字节的数


);


 


u_short htons(u_short hostshort);


 


int WSAHtons(


    SOCKET s,


    u_short hostshort,


    u_short FAR * lpnetshort        通过指针返回网络字节顺序的2个字节的数


);


以下几个函数将网络字节顺序转换成主机字节顺序:


u_long ntohl(u_long netlong);


 


int WSANtohl(


    SOCKET s,


    u_long netlong,


    u_long FAR * lphostlong


);


 


u_short ntohs(u_short netshort);


 


int WSANtohs(


    SOCKET s,


    u_short netshort,


    u_short FAR * lphostshort


);


 



4,实现功能



(1)       服务器端: 需要对绑定的端口进行侦听,函数原型如下


       int listen (


  SOCKET s,   


  int backlog 


);


    Backlog是客户连接请求队列的最大数量,而不是客户机连接的数量限制。


处于侦听的套接字将维护一个客户连接请求队列。


    该函数执行成功返回0,失败返回SOCKET_ERROR


    此外,需要从连接请求队列中取出最前面的一个客户请求,需要用到accept()函数:


         SOCKET accept (


  SOCKET s,                  


  struct sockaddr FAR* addr, 


  int FAR* addrlen           


);


该函数创建一个新的套接字来与客户套接字建立通信,如果连接成功,就返回新建的套接字描述符,

以后与客户通信的就是该套接字,而侦听套接字则继续接受新的连接;如果失败就返回INVALID_SOCKET


第一个参数是侦听套接字


第二个套接字用来返回新创建的套接字的地址结构


第三个套接字返回地址结构的长度


 



(2)       客户端:


connect函数是客户机建立与远程服务器连接而使用的。


int connect (


  SOCKET s,                         


  const struct sockaddr FAR*  name, 


  int namelen                       


);


 


5,数据传输


       收发数据时网络编程的一切在这里我们只讨论同步函数sendrecv


不讨论异步函数WSASendWSARecv


数据发送要用到send函数,原型如下:


int send(


    SOCKET s,


    const char FAR * buf,    发送数据缓冲区的地址


    int len,                  要发送的字节数


    int flags                 一般为0, MSG_DONTROUTE, MSG_OOB(外带数据)


);


成功返回发送字节数,出错返回SOCKET_ERROR


注意send函数把数据从buf复制到socket发送缓冲区后就返回了,

但是这些数据并不时马上就发送到连接的另一端。


 


在已连接的套接字上接受数据,recv函数是最基本的方式。


int recv(


    SOCKET s,


    char FAR* buf,


    int len,       准备接收的字节数或buf缓冲区长度


    int flags      0, MSG_PEEK, MSG_OOB 其中MSG_PEEK表示将有用的数据复制到所提供的接收端缓冲区,

但是没有从系统缓冲区中将它删除


);


成功返回接收的数据的字节数量,失败返回SOCKET_ERROR,如果接受数据时网络中断返回0


 


 


6,关闭Socket


一旦完成任务,记得将套接字关闭以释放资源:


     int closeSocketSOCKET s




三,           Socket编程实例


 


讲了这么多其实还是看实例最为重要了,下面我提供了最简单的面向连接的客户端和服务端程序,

当服务端接受客户端的连接后,先是该客户机地IP地址,并向客户端发送一个回应消息,

最后关闭该连接套接字。这样的服务器当然没什么实际的用途。


设计一个基本的网络服务器有以下几个步骤:


1、初始化Windows Socket


2、创建一个监听的Socket


3、设置服务器地址信息,并将监听端口绑定到这个地址上


4、开始监听


5、接受客户端连接


6、和客户端通信


7、结束服务并清理Windows Socket和相关数据,或者返回第4


 


#include <winsock2.h>


#include <stdio.h>


 


#define SERVPORT    5050


#pragma comment(lib,"ws2_32.lib")


 


void main(void)


{


   WSADATA              wsaData;


   SOCKET               sListen;              // 监听socket


   SOCKET               sClient;        // 连接socket


   SOCKADDR_IN          serverAddr;              // 本机地址信息


   SOCKADDR_IN          clientAddr;        // 客户端地址信息


   int                        clientAddrLen;       // 地址结构的长度


   int                  nResult;


   // 初始化Windows Socket 2.2


 


   WSAStartup(MAKEWORD(2,2), &wsaData);


  


   // 创建一个新的Socket来响应客户端的连接请求


 


   sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);


        


   // 填写服务器绑定的地址信息


   // 端口为5150


   // IP地址为INADDR_ANY,响应每个网络接口的客户机活动


   // 注意使用htonlIP地址转换为网络格式


             


   serverAddr.sin_family = AF_INET;


   serverAddr.sin_port = htons(SERVPORT);   


   serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);


   memset(&(serverAddr.sin_zero), 0, sizeof(serverAddr.sin_zero));        


   // 绑定监听端口


        


   nResult = bind(sListen, (SOCKADDR *)&serverAddr, sizeof(SOCKADDR));


   if (nResult == SOCKET_ERROR)


   {


              printf("bind failed!\n");


 


              return;


   }


 


   // 开始监听,指定最大接受队列长度5,不是连接数的上限


 


   listen(sListen, 5);


 


   // 接受新的连接


   while(1)


   {


              clientAddrLen = sizeof (SOCKADDR);


              sClient = accept(sListen, (SOCKADDR *)&clientAddr, &clientAddrLen);


              if(sClient == INVALID_SOCKET)


              {


                     printf("Accept failed!");


              }


              else


              {


                     printf("Accepted client: %s : %d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));


                     // 向客户端发送信息


                     nResult = send(sClient, "Connect success!", 16, 0);


                     if (nResult == SOCKET_ERROR)


                     {


                            printf("send failed!");


                     }


              }


        // 我们直接关闭连接,


              closesocket(sClient);


       } 


  


  


   // 并关闭监听Socket,然后退出应用程序


 


      closesocket(sListen);


 


   // 释放Windows Socket DLL的相关资源


 


      WSACleanup();


}


 


客户机程序如下:


#include <winsock2.h>


#include <stdio.h>


 


#define SERVPORT    5050              // 端口为5150


#define MAXDATASIZE 100


#define SERVIP      "127.0.0.1"      // 服务器IP地址为"127.0.0.1",注意使用inet_addrIP地址转换为网络格式


#pragma comment(lib,"ws2_32.lib")


 


void main(int argc, char *argv[])


{


   WSADATA              wsaData;


   SOCKET               sConnect;


   SOCKADDR_IN          serverAddr;      


   int                                 recvbytes;


   char                               buf[MAXDATASIZE];


 


   //初始化Windows Socket 2.2


 


   WSAStartup(MAKEWORD(2,2), &wsaData);


  


   // 创建一个新的Socket来连接服务器


 


    sConnect = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);


  


   // 填写连接地址信息


 


    serverAddr.sin_family = AF_INET;


    serverAddr.sin_port = htons(SERVPORT);   


    serverAddr.sin_addr.s_addr = inet_addr(SERVIP);


 


       memset(&(serverAddr.sin_zero), 0, sizeof(serverAddr.sin_zero));


 


   // 向服务器发出连接请求


 


    if (connect(sConnect, (SOCKADDR *)&serverAddr, sizeof(SOCKADDR)) == SOCKET_ERROR)


       {


              printf("connect failed!\n");


 


              return;


       }


       // 接受服务器的回应消息


    recvbytes = recv(sConnect, buf, MAXDATASIZE, 0);


       if (recvbytes == SOCKET_ERROR)


       {


              printf("recv failed!\n");


       }


       else


       {


              buf[recvbytes] = '\0';


              printf("%s\n",buf);


       }


 


    closesocket(sConnect);


 


   // 释放Windows Socket DLL的相关资源


 


 


    WSACleanup();


}


 


四,           结束语


这里介绍的只不过是winsock最基础最基础的知识,开发网络程序从来都不是一件容易的事情,


尽管只需要遵守很少的一些规则: 创建套接字,发起连接,接受连接,


发送和接受数据。真正的困难在于:让你的程序可以适应从单单一个连接到几千个连接,


即开发一个大容量,具可扩展性的Winsock应用程序。


Winscok编程很重要的一部分是IO处理,分别提供了“套接字模式”和“套接字I/O模型”,


可对一个套接字上的I/O行为进行控制。


其中,套接字模式用于决定在随一个套接字调用时,那些Winsock函数的行为,


有阻塞和非阻塞两种模式


而另一方面,套接字模型描述了一个应用程序如何对套接字上进行的I/O进行管理及处理,


包括包括SelectWSAAsyncSelectWSAEventSelect IO重叠模型,完成端口等。


在这里推荐一本好书《windows网络编程技术》,有兴趣研究的同志可以去啃一啃。

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
我要评论
0
4
关闭 站长推荐上一条 /3 下一条