导航

公告

在WINDOWS环境下使用SOCKET前需要初始化windows socket库,否则socket()会返回SOCKET_ERROR(-1)

  WSADATA     wsaData;
  WORD wVersionRequested;// Version
  wVersionRequested = MAKEWORD(1,1);//Version Info
  // Initialize Windows socket library
  WSAStartup(wVersionRequested, &wsaData);

总的来说网络程序是由两个部分组成的--客户端和服务器端.它们的建立步骤一般是:

    服务器端 socket-->bind-->listen—>accept
    客户端 socket-->connect

SOCKET socket(int domain, int type,int protocol)

  • domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等). AF_UNIX只能够用于单一的Unix系统进程间通信,而AF_INET是针对Internet的,因而可以允许在远程主机之间通信
  • type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等) SOCK_STREAM表明我们用的是TCP协议,这样会提供按顺序的,可靠,双向,面向连接的比特流. SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信.
  • protocol:由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了 下面是from WINSOCK.H的协议定义

#define IPPROTO_IP              0               /* dummy for IP */
#define IPPROTO_ICMP            1               /* control message protocol */
#define IPPROTO_IGMP            2               /* internet group management protocol */
#define IPPROTO_GGP             3               /* gateway^2 (deprecated) */
#define IPPROTO_TCP             6               /* tcp */
#define IPPROTO_PUP             12              /* pup */
#define IPPROTO_UDP             17              /* user datagram protocol */
#define IPPROTO_IDP             22              /* xns idp */
#define IPPROTO_ND              77              /* UNOFFICIAL net disk proto */

#define IPPROTO_RAW             255             /* raw IP packet */
#define IPPROTO_MAX             256

e.g.

sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

成功返回SOCKET文件描述符,失败返回-1

int bind(int sockfd, struct sockaddr *my_addr, int addrlen)

  • sockfd:是由socket调用返回的文件描述符.
  • addrlen:是sockaddr结构的长度.
  • my_addr:是一个指向sockaddr的指针. 在WINSOCK.H中有 sockaddr的定义 :
struct sockaddr {
        u_short sa_family;              /* address family */
        char    sa_data[14];            /* up to 14 bytes of direct address */
};

不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构(struct sockaddr_in) 来代替.在WINSOCK.H中有sockaddr_in的定义 :

/*
 * Socket address, internet style.
 */
struct sockaddr_in {
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};

我们主要使用Internet所以sin_family一般为AF_INET,sin_addr设置为INADDR_ANY表示可以和任何的主机通信,sin_port是我们要监听的端口号.sin_zero[8]是用来填充的. bind将本地的端口同socket返回的文件描述符捆绑在一起.成功是返回0,失败返回-1。

sin_port 是网络字节顺序,sin_addr.s_addr 也是的。在处理自己的 IP 地址和/或端口的 时候,有些工作是可以自动处理的。
      my_addr.sin_port = 0; /* 随机选择一个没有使用的端口 */
my_addr.sin_addr.s_addr = INADDR_ANY; /* 使用自己的IP地址 */
通过将0赋给 my_addr.sin_port,你告诉 bind() 自己选择合适的端 口。同样,将 my_addr.sin_addr.s_addr 设置为 INADDR_ANY,你告诉 它自动填上它所运行的机器的 IP 地址。

e.g.

localaddr.sin_addr.s_addr = inet_addr(ipaddr);
localaddr.sin_family = AF_INET;
localaddr.sin_port = htons(portNum);
bind(s_ , (struct sockaddr*)&localaddr , sizeof(localaddr)) ;

由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。

int listen (SOCKET s, int backlog);

  • s:bind后的SOCKET文件描述符
  • backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度.(允许监听多少个TCP连接)
  • listen函数将bind的文件描述符变为监听套接字.出错返回-1

e.g.

listen(sListen, 3);

 

SOCKET accept (SOCKET s, struct sockaddr *addr,  int *addrlen);

  • s:是listen后的SOCKET文件描述符
  • addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了. bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接. accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了. 失败时返回-1

e.g.

sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);

 

int connect (SOCKET s, const struct sockaddr *name, int namelen);

  • s:socket返回的文件描述符
  • name:储存了服务器端的连接信息.其中name是服务端的地址
  • namelen: name的长度
  • connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符失败时返回-1

e.g.

memset(&server, 0, sizeof(SOCKADDR_IN));
server.sin_family = AF_INET;
server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);
server.sin_port = htons(PORT);
connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));

 

int send(SOCKET s, const char * buf, int len, int flags);

  • s: SOCKET文件描述符
  • buf: 要发送的数据
  • len:发送的数据长度
  • flag:用来影响对端SOCKET的行为。一般设为0。(这里这里有flag的讨论)
  • 发送成功返回实际发送的长度,失败返回-1(SOCKET_ERROR)

      send函数仅仅是把buf中的数据成功copy到s的发送缓冲区里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。注意并不是send把s的发送缓冲区中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里。传输中遇到的网络错误会在下一个socket函数中返回SOCKET_ERROR。

      注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止

 

int recv (SOCKET s, char * buf, int len, int flags);

  • s: SOCKET文件描述符
  • buf: 读取的数据存放的缓冲buf
  • len:缓冲buf的最大长度
  • flags: 同上
  • 返回实际读入缓冲的数据的字节数。或者在错误的时候返回-1

     当应用程序调用recv函数时,recv先等待s的发送缓冲 中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数 据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到 协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0

 

int sendto( SOCKET s, const char * buf, int len, int flags,const struct sockaddr * to, int tolen);

  • s: SOCKET文件描述符
  • buf: 包含待发送数据的缓冲区
  • len:缓冲buf的最大长度
  • flags: 调用方式标志位
  • to:(可选)指针,指向目的套接口的地址
  • tolen:to所指地址的长度

      sendto()函数主要用于SOCK_DGRAM类型套接口向to参数(目的地的 IP 地址和端口信息)指定端的套接口发送数据报。对于SOCK_STREAM类型套接口,to和tolen参数被忽略;这种情况下sendto()等价于send()。

      对于数据报类套(SOCK_DGRAM)接口,必需注意发送数据长度不应超过通讯子网的IP包最大长度。IP包最大长度在WSAStartup()调用返回的WSAData的iMaxUdpDg元素中。如果数据太长无法自动通过下层协议,则返回WSAEMSGSIZE错误,数据不会被发送。

     若无错误发生,send()返回所发送数据的总数(请注意这个数字可能小于len中所规定的大小)。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码

 

int recvfrom( SOCKET s, char * buf, int len, int flags,struct sockaddr * from, int * fromlen);

  • s: SOCKET文件描述符
  • buf: 接收数据缓冲区
  • len:缓冲buf的最大长度
  • flags: 调用操作方式
  • from:(可选)指针,指向装有源地址的缓冲区
  • fromlen:(可选)指针,指向from缓冲区长度值

     对于SOCK_STREAM类型套接口,忽略from和fromlen参数。若from非零,且套接口为SOCK_DGRAM类型,则发送数据源的地址被复制到相应的sockaddr结构中。fromlen所指向的值初始化时为这个结构的大小,当调用返回时按实际地址所占的空间进行修改。

    如果没有数据待读,那么除非是非阻塞模式,不然的话套接口将一直等待数据的到来,此时将返回SOCKET_ERROR错误,错误代码是WSAEWOULDBLOCK。用select()或WSAAsynSelect()可以获知何时数据到达。

    若无错误发生,recvfrom()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

 

注:以上代码全部使用的是WINSOCK.H中的定义,与WINSOCK2.H中的略有不同。二者是区别如下:

  • winsock.h 使用 wsock32.lib  传说中的Berkeley socket,由Unix下的BSD Socket发展而来
  • winsock2.h 使用 with ws2_32.lib

       对于大多数windows应用程序,二者区别不大,WINSOCK2提供了新的函数针对新的协议,比如bluetooth。WINSOCK2还提供了LSP机制,The cool thing about this is that you can create your own layers which will in turn get called by the winsock implementation when any application calls the function you layered. Think of it as an easy way to hook any winsock function.

Winsock2是完全向后兼容的,WINSOCK基本上只有在你准备在一个不支持WINSOCK2的平台上搞开发时才应该被考虑使用。

 

摘自Wiki:

Microsoft implementations
  • Microsoft did not supply an implementation of Winsock 1.0.
  • Version 1.1 of Winsock was supplied in an add-on package (called Wolverine) for Windows for Workgroups (code named Snowball). It was an integral component of Windows 95 and Windows NT from versions 3.5 and onwards (the initial commercially available version of Windows NT, version 3.1, included only a proprietary and quite incomplete implementation of TCP/IP based on the AT&T UNIX System V "Streams" API[citation needed]).
  • Version 2.1 of Winsock was supplied in an add-on package for Windows 95. It was an integral component of Windows 98, Windows NT 4.0, and all subsequent Windows releases. (Microsoft did not supply implementations of Winsock 2 for Windows 3.x or Windows NT 3.x.)
  • Recent versions of Winsock 2.x have been delivered with new Windows releases or as part of service packs.
  • Winsock 2 is extensible by a mechanism known as a Layered Service Provider (LSP). Winsock LSPs are available for a wide range of useful purposes, including Internet parental controls, web content filtering, QoS etc. The layering order of all providers is kept in the Winsock Catalog. In previous versions of Windows, removing a buggy LSP could result in corruption of the Winsock catalog in the registry, potentially resulting in a loss of all network connectivity. Winsock in Windows XP Service Pack 2, Windows Server 2003 Service Pack 1 and all later Windows operating systems has the ability to self-heal after a user uninstalls such an LSP.

 

Reference:

网络socket编程指南》:这个文档是一个指南,而不是参考书。如果你刚开始 socket 编程并想找一本入门书,那么你是我的读者。但这不是一本完全的 socket 编程书。

Linux C编程一站式学->2. 基于TCP协议的网络程序->第 37 章 socket编程》:Mark一下以后详细看

UDP协议的两个主要方法sendto和recvfrom详解

posted on 2011-11-23 11:03 Jersey 阅读(...) 评论(...) 编辑 收藏

统计