Winsock API简介

来源:互联网 发布:北京交大网络教育学院 编辑:程序博客网 时间:2024/06/10 01:19

Windows Sockets简介

Windows Sockets2是对1.1版本的一个扩展,通过WinSock2可以创建更高级的Internet、Intranet以及其他网络应用程序,利用这些程序可以在不依赖所使用的网络协议的情况下在网络上传输应用数据;本文主要介绍Winsock2的一些简单内容;

WinSock2简介

- 一个比较大的特点是,在WinSock2中,套接字句柄可以是文件句柄,这意味着我们可以将套接字句柄-  用于Windows的I/O 接口,如ReadFile/WirteFile等;- 可访问TCP/IP除外的其他协议(没用过...不了解)- 协议独立的名字解析能力:包含了一套标准的API用于现存的大量名字解析域名系统,如DNS,SAP等- (没用过...不了解)- 还有好多其他扩展...就不一一列出了,有兴趣的可以参考《Windows网络编程完全讲义》

常用函数说明

面向连接的通信(TCP)

1.创建socket原型:///< af 指定套接字协议簇,IPv4协议簇改值为AF_INET,IPv6 该值为 AF_INET6///< type 套接字类型 ///< protocol 指定上层(传输层)协议,TCP or UDP?SOCKET socket(int af, int type, int protocol);说明:成功则返回套接字描述符,否则返回INVALID_SOCKET2.绑定套接字bind原型:///> s 尚未捆绑到具体地址的套接字///> name 分配给套接字的地址,指向sockaddr结构的指针///> namelen 长度,单位字节int bind(SOCKET s, const struct sockaddr* name, int namelen);说明:用于将本地协议地址与指定的套接字关联起来,即将套接字绑定到本地协议地址(协议地址:32位的IP和16位的端口号的组合)对于IPv4而言,name参数结构如下:struct sockaddr_in {    short sin_family; // 协议簇    u_short sin_port; // 端口号    struct in_addr sin_addr; // IP地址    char sin_zero[8]; // 空字节}除了sin_family以外,该结构其他内容均以网络字节顺序存储(大端)3.监听 listen原型:///> s 用于标识一个已经绑定但未连接的套接字///> backlog 等待连接队列的最大长度int listen(SOCKET s, int backlog);说明:服务端一般调用bind以后,他就会紧跟着调用listen将套接字置于被动等待的模式以监听可能到来的连接请求。当有连接到来时,可通过调用accept接受该请求;为了管理所有到来的连接以及即将到来的连接,内核为每一个监听中的套接字维护两个队列:尚未完成连接队列和已经完成连接队列;前者维护的是尚未完成三次握手的套接字,而后者维护的是已经完成连接的套接字;后边介绍的accept接口实际就是从已经完成连接队列中去取出第一个然后返回的;注意:只有**面向连接**的套接字才在**服务端**使用listen函数;4.accept原型:///> s 处于监听状态的套接字描述符///> addr 指向缓冲区的可选指针,用于接收为通信层所知的连接实体的地址,addr的实际格式取决于通信使用     的地址簇协议///> addrlen 可选指针SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);说明:当服务端处于监听状态,所有远端连接(connect)到来,accept接收到来的连接请求;accept用于面向连接的套接字类型,如TCP.他从监听套接字完成三次握手的已经连接的队列中提取第一个连接,接着他创建新的套接字并将其句柄作为accept的返回值,新建的套接字而非s才是实际连接的套接字;而原来处于监听状态的套接字s仍处于监听状态,并等待其他新链接请求的到来;一定要区分这两个socket;5.send原型:///> s 已经连接套接字描述符///> buf 包含待发送数据的缓冲区///> len 缓冲区buf中数据的长度,单位字节///> flags 调用的执行方式int send(SOCKET s, const char* buf, int len, int flags);说明:对于UDP,需注意低层服务提供者支持的最大数据包大小;(没有很理解?是说UDP分包这块的工作要开发人员来控制?一会学习下...)6.recv原型:///> s 已经连接套接字描述符///> buf 用于接收数据的缓冲区///> len 缓冲区buf的长度,单位字节///> flags 指定调用方式int recv(SOCKET s, char* buf, int len, int flags);说明:用于从已经连接的或者已经绑定的套接字上接收数据。即可用于UDP也可用于TCP;对于面向连接的套接字或者无连接套接字,recv对可接收数据的地址有严格的限制:他仅从连接中指定的远程地址接收数据,而来自其他地址的数据则被丢弃。对于TCP套接字,recv尽力返回当前所有的可用数据,最大可达缓冲区的大小。而对于UDP套接字,recv将从由connection指定的目的地址中提取队列中的第一份数据报。如果数据报大小超过指定的缓冲区的大小,他将使用与缓冲区大小相同的数据报前端部分填充缓冲区,同时返回响应错误码,对于不可靠传输的UDP,其余数据将丢失;而对于可靠传输协议TCP,其余数据有服务提供者(是谁?)保留至应用程序使用足够大的缓冲区调用recv。7.connect原型:///> s 未连接的套接字描述符///> name sockaddr 结构中的名字,他包含将要连接的远程主机的地址信息///> namelen 名字长度,单位字节int connect(SOCKET s, const struct sockaddr* name, int namelen);说明:对于阻塞套接字,返回值直接就说明了连接成功与否;非阻塞套接字,若连接不能立即完成,则返回响应错误码,可以使用select通过检查套接字的可写性来确定连接请求是否完成,writefds结合报告其成功结果,exceptfds集合报告其失败结果;对于已经建立的TCP连接(UDP无连接),他由一个套接字对唯一标识,而每个套接字由IP地址和端口号共同标识,因此一个TCP连接有本地地址、本地端口、远程地址、远程端口四元组标识,只要四者中有一个不同,则表示不同的TCP连接;8.closesocketint closesocket(SOCKET s);说明:用于关闭已经打开的套接字;在调用clsesocket之前,应调用shutdown优雅的关闭连接。shutdown可以用于控制套接字上数据的接收和发送;

无连接通信(UDP)

1.sendto///> s 套接字描述符///> buf 指向缓冲区的指针///> len 数据长度///> to 可选,指向目的的套接字地址///> tolen to所指地址长度int sendto(SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen);说明:返回实际发送字节的长度,对于UDP套接字来说,发送的报文大小不能超过低层子网所能接收的最大分组大小(同上,这块不太明确),否则不会发送任何数据并且会报错;如果用于TCP套接字,则忽略to参数,等同于send,实际上,起源于Berkeley的内核实现中,sendto一般都是调用send来实现数据的发送,这就要求在sendto中先建立连接,然后调用send, 数据发送完以后随之断开连接,因此,在退出sendto之后仍然是无连接的;2.recvfrom///> s 已经连接套接字///> from 指向装有源地址的缓冲区int recvfrom(SOCKET s, char* buf, int len, int flags, struct sockaddr* from, int* from);说明:返回实际接收数据的字节数,他并不要求套接字处于连接状态,但套接字本机的地址必须已。对于服务端而言,必须使用bind绑定或使用accept隐式的解决绑定文帝,不过对于客户端而言,而不提倡显示绑定,通过调用sendto等函数可以将其隐式的绑定到本机地址;

套接字选项及IO控制

主要通过套接字选项及IO控制命令两种方式实现对套接字各种属性的控制;

套接字选项

getsockopt和setsockopt;

IO控制

IO控制主要是控制套接字的工作模式;
1.ioctlsocket控制函数
原型:
///> s 套接字的描述符
///> cmd 对套接字所要执行的命令
///> argp 执行cmd命令参数的指针
int ioctlsocket(SOCKET s, long cmd, u_long* argp);
说明:他可以用于处于任何状态的套接字;
主要有两个命令(从UNIX移植过来的)最为常用:
(1)FIONBIO命令:该命令用于设置套接字工作与阻塞还是非阻塞;默认情况下创建的套接字是阻塞
的;
(2)FIONREAD命令:该命令用于判定从套接字上可读数据的数量。

流式套接字编程实例

下面分别给出TCP服务端设计和客户端的设计代码;

TCP服务端设计

#include <winsock2.h>     #include <stdio.h>#include <stdlib.h>#pragma comment(lib, "ws2_32.lib")#define PORT 8888#define ADRR "127.0.0.1"int main(int argc, char* argv[]){    WSADATA wsock;    SOCKET listensocket, newconnection;    SOCKADDR_IN serAddr, cliAddr;    int cliAddrLen = sizeof(cliAddr);    int nRet = 0;    char buf[100];    // 初始化 WinSock2.2    if (WSAStartup(MAKEWORD(2,2), &wsock) != 0) {        printf("startup faild. err == %d\n", ::GetLastError());        exit(0);    }    // 创建监听Socket    if ((listensocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {        printf("create listen socket failed. err == %d\n", ::GetLastError());        WSACleanup();        return 0;    }    // 设置地址    serAddr.sin_family = AF_INET; // IPv4协议    serAddr.sin_port = htons(PORT); // 转为网络字节序    serAddr.sin_addr.S_un.S_addr = inet_addr(ADRR); // 存储IP地址    // 绑定套接字    if (bind(listensocket, (SOCKADDR*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) {        printf("bind socket failed. err == %d\n", ::GetLastError());        closesocket(listensocket);        WSACleanup();        return 0;    }    // 监听连接    // 那个数字5 是说三次握手队列中最多存放5个已经链接的Socket,之前说过内核维护了两个队列.    if (listen(listensocket, 5) == SOCKET_ERROR) {        printf("listen socket failed. err == %d\n", ::GetLastError());        closesocket(listensocket);        WSACleanup();        return 0;    }    // 接收连接    // cliAddr 是客户端携带过来的,newconnection 是新创建的Socket,而listensocket仍旧处于监听状if ((newconnection = accept(listensocket, (SOCKADDR*)&cliAddr, &cliAddrLen)) ==     INVALID_SOCKET) {        printf("listen socket failed. err == %d\n", ::GetLastError());        closesocket(listensocket);        WSACleanup();        return 0;    }    // 此时可以继续监听新的连接,或者停止监听进行数据收发    closesocket(listensocket);    memset(buf, 0, sizeof(buf));    for (int i = 0; ;++i) {        if ((nRet = recv(newconnection, buf, sizeof(buf), 0)) == SOCKET_ERROR) {            printf("recv failed. err == %d\n", ::GetLastError());            closesocket(newconnection);            WSACleanup();            return 0;           }        // 显示收到的数据        printf(buf);        printf("\n");        // "exit"退出        if (strncmp(buf, "exit", sizeof("exit")) == 0) {            printf("exit receiving...\n");            break;        }        // 将收到的数据返回去        if ((nRet = send(newconnection, buf, strlen(buf), 0)) == SOCKET_ERROR) {            printf("send failed. err == \n", ::GetLastError());        }    }    // 关闭已经连接的套接字    closesocket(newconnection);    WSACleanup();    return 0;}// 本例较简单,只接收一个客户端请求,这里只为演示每个接口的调用// 一般来讲,一个服务端不可能只处理一个客户端请求,后续会说到其他服务模型如,select等

TCP客户端设计

#include <winsock2.h>     #include <stdio.h>#include <stdlib.h>#pragma comment(lib, "ws2_32.lib")#define PORT 8888#define ADRR "127.0.0.1"int main(int argc, char* argv[]){    WSADATA wsock;    SOCKET sconnection;    SOCKADDR_IN serAddr;    int nRet = 0;    char buf[100];    // 初始化 WinSock2.2    if (WSAStartup(MAKEWORD(2,2), &wsock) != 0) {        printf("startup faild. err == %d\n", ::GetLastError());        exit(0);    }    if ((sconnection = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {        printf("create listen socket failed. err == %d\n", ::GetLastError());        WSACleanup();        return 0;    }    serAddr.sin_family = AF_INET;    serAddr.sin_port = htons(PORT);    serAddr.sin_addr.S_un.S_addr = inet_addr(ADRR);    // 可以拿到服务端地址    if (connect(sconnection, (SOCKADDR*)&serAddr, sizeof(serAddr) == SOCKET_ERROR) {        printf("connect socket failed. err == %d\n", ::GetLastError());        closesocket(sconnection);        WSACleanup();        return 0;    }    for (int i = 0; ; ++i) {        memset(buf, 0, sizeof(buf));        printf("\nMessage to be send:");        gets(buf);        // send不要指定服务端地址,已经与服务端创建连接        if ((nRet = send(sconnection, buf, strlen(buf), 0)) == SOCKET_ERROR) {            printf("send socket failed. err == %d\n", ::GetLastError());            closesocket(sconnection);            WSACleanup();            return 0;        }        printf("The data was send to %s successfully.\n", inet_ntoa(serAddr.sin_addr));        if (strncmp(buf, "exit", sizeof("exit")) == 0) {            break;        }        memset(buf, 0, sizeof(buf));        if ((nRet = recv(sconnection, buf, sizeof(buf), 0)) == SOCKET_ERROR) {            printf("recv socket failed. err == %d\n", ::GetLastError());        }        // 输出服务端返回的数据        printf(" recv from %s successfully.\n", inet_ntoa(serAddr.sin_addr));        printf(buf);    }    closesocket(sconnection);    WSACleanup();    return 0;}
0 0
原创粉丝点击