lpWSAData:一个WSADATA结构指针,该结构详细记录了Windows套接字的相关信息。
返回值:0成功
af:一个地址家族,通常为AF_INET。
type:套接字类型。如果为SOCK_STREAM,表示创建面向连接的流式套接字;为SOCK_DGRAM,表示创建面向无连接的数据报套接字;为SOCK_RAW,表示创建原始套接字。对于这些值,用户可以在Winsock2.h头文件中找到。
potocol:套接口所用的协议,如果前面已经指定为tcp或udp,可以设置为0。
返回值:创建的套接字句柄。
int connect (SOCKET s,const struct sockaddr FAR* name,int namelen );
s:一个套接字。
name:套接字s想要连接的主机地址和端口号。
namelen:name缓冲区的长度。
返回值:如果函数执行成功,返回值为0;否则为SOCKET_ERROR。用户可以通过WSAGETLASTERROR
得到其错误描述。
accept后会阻塞,直到有一个客户端请求连接,这时accept返回一个新的SOCKET s2,就用这个s2与客户端通信,即服务器端send、recv里的SOCKET参数填s2,一定不要用accept(s1, ...)中的那个s1与客户端通信。
s 表示一个套接字
buf 表示接收数据的缓冲区
len 表示buf的长度
flags 表示函数的调用方式,一般置0。
s 表示一个套接字
buf 表示存放要发送数据的缓冲区
len 表示缓冲区长度
flags 表示函数的调用方式
对于send和recv函数,如果服务器用这两个函数收发数据,则参数s应该是accept函数返回的新套接字的句柄,客户端则是创建时的套接字。
接收数据报,用于面向无连接模式的UDP套接字。
函数原型:
ssize_t recvfrom(
int sockfd,
void *buf,
int len,
unsigned int flags,
struct sockaddr *from,
socket_t *fromlen);
ssize_t 相当于 int,socket_t 相当于int ,这里用这个名字为的是提高代码的自说明性。
sockfd:标识一个已连接套接口的描述字。
buf:接收数据缓冲区。
len:缓冲区长度。
flags:调用操作方式。是以下一个或者多个标志的组合体,可通过or操作连在一起:
MSG_DONTWAIT:操作不会被阻塞。
函数说明:recvfrom()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数len为可接收数据的最大长度.参数flags一般设0,其他数值定义参考recv().参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数.参数fromlen为sockaddr的结构长度。如果正确接收返回接收到的字节数,失败返回-1.
向一指定目的地发送数据,sendto()适用于发送未建立连接的UDP数据报 (参数为SOCK_DGRAM)。
int PASCAL FAR sendto (
IN SOCKET s,
IN const char FAR * buf,
IN int len,
IN int flags,
IN const struct sockaddr FAR *to,
IN int tolen);
IN指传入型参数。
s 套接字
buff 待发送数据的缓冲区
size 缓冲区长度
Flags 调用方式标志位, 一般为0, 改变Flags,将会改变Sendto发送的形式
addr (可选)指针,指向目的套接字的地址
len addr所指地址的长度
如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR。
htons
该函数将一个16位的无符号短整型数据由主机排列方式转换为网络排列方式。
语法:
u_short htons (u_short hostshort );
hostshort:一个主机排列方式的无符号短整型数据。
返回值:16位的网络排列方式数据。
htonl
该函数将一个无符号长整型数据由主机排列方式转换为网络排列方式。
语法:
u_long htonl ( u_long hostlong);
Hostlong:一个主机排列方式的无符号长整型数据。
返回值:返回32位的网络排列方式数据。
inet_addr
该函数将一个由字符串表示的地址转换为32位的无符号长整型数据。
语法:
unsigned long inet_addr (const char FAR * cp);
cp:一个IP地址的字符串。
返回值:返回32位无符号长整数。
select
该函数用来检查一个或多个套接字是否处于可读、可写或错误状态。
语法:
int select (int nfds,fd_set FAR * readfds,fd_set FAR * writefds,fd_set FAR * exceptfds, const struct timeval FAR *timeout);
nfds 无实际意义,只是为了和UNIX下的套接字兼容
readfds 表示一组被检查可读的套接字
writefds 表示一组被检查可写的套接字
exceptfds 被检查有错误的套接字
timeout 表示函数的等待时间
WSACleanup
该函数用于释放Ws2_32.dll动态链接库初始化时分配的资源。
语法:
int WSACleanup (void);
返回值:函数执行成功返回0。
WSAAsyncSelect
该函数用于将网络中发生的事件关联到窗口的某个消息中。
语法:
int WSAAsyncSelect (SOCKET s, HWND hWnd,unsigned int wMsg,long lEvent);
s 表示套接字
hWnd 表示接收消息的窗口句柄
wMsg 表示窗口接收来自套接字中的消息
lEvent 表示网络中发生的事件
ioctlsocket
该函数用于设置套接字的I/O模式。
语法:
int ioctlsocket(SOCKET s,long cmd,u_long FAR* argp);
s:待更改I/O模式的套接字。
cmd:对套接字的操作命令。如果为FIONBIO,当argp为0时,表示禁止非阻塞模式;当argp为非0时,表示设置非阻塞模式。如果为FIONREAD,表示从套接字中可以读取的数据量。为SIOCATMARK,表示是否所有的带外数据都已被读入。这个命令仅适用于流式套接字,并且该套接字已被设置为可以在线接收带外数据(SO_OOBINLINE)。
argp:命令参数。
WSAGetOverlappedResult
该函数用于获取重叠I/O操作完成时的结果。
语法:
BOOL WSAGetOverlappedResult(SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer,
BOOL fWait, LPDWORD lpdwFlags);
s 标识一个套接字
lpOverlapped 标识一个I/O重叠操作的结构指针,函数以此来查找该操作的结果
lpcbTransfer 表示本次操作实际接收或发送的字节数
fWait 为TRUE,则表示除非I/O操作完成,否则函数不会返回;为FALSE,表示如果I/O操作进行中,则函数立即返回FALSE
lpdwFlags 表示一组标记,通常来自于WSARecv函数的lpFlags参数
CreateIoCompletionPort
该函数用于创建一个完成端口对象。
语法:
HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads);
FileHandle 表示完成端口关联对象。如果是创建完成端口,该参数为INVALID_HANDLE_VALUE
ExistingCompletionPort 表示关联FileHandle对象的完成端口,如果是创建完成端口,该参数为NULL
CompletionKey 表示端口的完成键值,用户可以指定一个自定义的结构指针,用于表示关联完成端口的附加数据
NumberOfConcurrentThreads 表示允许同时运行的用户线程的最大数量,通常设置为0,表示系统根据CPU的数量来确定
初学者很容易对二者有困惑的感觉,下面来讲一下二者的区别。
sockaddr是在头文件 /usr/include/bits/socket.h 中定义的,如下:
view sourceprint?
1.struct sockaddr
2.{
3.__SOCKADDR_COMMON (sa_); /* Common data: address family and length. 协议族*/
4.char sa_data[14]; /* Address data. 地址+端口号*/
5.};
而sockaddr_in是在头文件 /usr/include/netinet/in.h 中定义的,如下:
view sourceprint?
01./* Structure describing an Internet socket address. */
02.struct sockaddr_in
03.{
04.__SOCKADDR_COMMON (sin_); /* 协议族 */
05.in_port_t sin_port; /* Port number. 端口号 */
06.struct in_addr sin_addr; /* Internet address. IP地址 */
07.
08./* Pad to size of `struct sockaddr'. 用于填充的0字节 */
09.unsigned char sin_zero[sizeof (struct sockaddr) -
10.__SOCKADDR_COMMON_SIZE -
11.sizeof (in_port_t) -
12.sizeof (struct in_addr)];
13.};
14.
15./* Internet address. */
16.typedef uint32_t in_addr_t;
17.struct in_addr
18.{
19.in_addr_t s_addr;
20.};
二者的占用的内存大小是一致的,因此可以互相转化,从这个意义上说,他们并无区别。
sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息。是一种通用的套接字地址。而sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作。使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。下面是一个完整的例子。
view sourceprint?
01.#include <stdio.h>
02.#include <stdlib.h>
03.#include <sys/socket.h>
04.#include <netinet/in.h>
05.
06.int main(int argc,char **argv)
07.{
08.int sockfd;
09.struct sockaddr_in mysock;
10.
11.sockfd = socket(AF_INET,SOCK_STREAM,0); //获得fd
12.
13.bzero(&mysock,sizeof(mysock)); //初始化结构体
14.mysock.sin_family = AF_INET; //设置地址家族
15.mysock.sin_port = htons(800); //设置端口
16.mysock.sin_addr.s_addr = inet_addr("192.168.1.0"); //设置地址
17.bind(sockfd,(struct sockaddr *)&mysock,sizeof(struct sockaddr); /* bind的时候进行转化 */
18.... ...
19.return 0;
20.}
题外话,两个函数 htons() 和 inet_addr()。
htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)
inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值,用于sockaddr_in.sin_addr.s_addr。
inet_ntoa()作用是将一个sin_addr结构体输出成IP字符串(network to ascii)。比如:
view sourceprint?
1.printf("%s",inet_ntoa(mysock.sin_addr));
htonl()作用和htons()一样,不过它针对的是32位的,而htons()针对的是两个字节,16位的。
与htonl()和htons()作用相反的两个函数是:ntohl()和ntohs()。
下面是我利用win32 api简单写得一个客户端服务器程序,先保证数据联通,收发正常,后面多线程、异步可以扩展。
服务器
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include<WinSock2.h> #pragma comment (lib,"ws2_32.lib") //链接库文件 //date 2016.1.20 int _tmain(int argc, _TCHAR* argv[]) { WSADATA wsd; //定义WSADATA对象 if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) { WSACleanup(); return -1; }; //初始化套接字 SOCKET server = socket(AF_INET, SOCK_STREAM, 0); if (server == INVALID_SOCKET) { printf("error: %d",WSAGetLastError()); WSACleanup(); return -2; } sockaddr_in addr,remote_addr; memset((char*)&addr, 0, sizeof(addr)); int n = sizeof(addr); addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addr.sin_family = AF_INET; addr.sin_port = htons(5344); if (bind(server, (sockaddr*)&addr, n) == SOCKET_ERROR) { printf("error: %d", WSAGetLastError()); closesocket(server); WSACleanup(); return -3; } if (listen(server, 5) == SOCKET_ERROR) { printf("error: %d", WSAGetLastError()); closesocket(server); WSACleanup(); return -4; } SOCKET client; while (true) { printf("等待客户端连接...\n"); client=accept(server, (sockaddr*)&remote_addr, &n); if (client == INVALID_SOCKET) { wprintf(L"acceptfailedwitherror:%ld\n", WSAGetLastError()); closesocket(server); WSACleanup(); return -5; } else { wprintf(L"Client connected.\n"); char*msg = "hello"; send(client, msg, 6, 0); char buff[100] = { 0 }; int len=recv(client, buff, 100, 0); if (len > 0) printf("来自客户端:%s\n", buff); break; } } closesocket(server); WSACleanup(); system("pause"); return 0; }
// ConsoleApplication2.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include<WinSock2.h> #pragma comment (lib,"ws2_32.lib") //链接库文件 //date 2016.1.20 int _tmain(int argc, _TCHAR* argv[]) { WSADATA wsd; //定义WSADATA对象 if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) { WSACleanup(); return -1; }; //初始化套接字 SOCKET client = socket(AF_INET, SOCK_STREAM, 0); if (client == INVALID_SOCKET) { printf("error: %d", WSAGetLastError()); WSACleanup(); return -2; } sockaddr_in addr; addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addr.sin_family = AF_INET; addr.sin_port = htons(5344); if (connect(client, (sockaddr*)&addr, sizeof(addr)) != 0) { printf("error: %d", WSAGetLastError()); closesocket(client); WSACleanup(); return -3; } else { printf("连接服务器成功\n"); char buff[100] = { 0 }; int buflen=recv(client, buff, 100, 0); if (buflen > 0) printf("来自服务器:%s\n", buff); char*msg = "are you OK?"; send(client, msg, 11, 0); } closesocket(client); WSACleanup(); system("pause"); return 0; }
先运行服务器,再运行客户端,下面是成功时的截图