windows套接字编程

Windows系统提供的套接字函数通常封装在Ws2_32.dll动态链接库中,其头文件Winsock2.h提供了套接字函数的原型,库文件Ws2_32.lib提供了Ws2_32.dll动态链接库的输出节。在使用套接字函数前,用户需要引用Winsock2.h头文件,并链接Ws2_32.lib库文件。例如:
#include "winsock2.h" //引用头文件
#pragma comment (lib,"ws2_32.lib") //链接库文件
此外,在使用套接字函数前还需要初始化套接字,可以使用WSAStartup函数来实现。例如:
WSADATA wsd; //定义WSADATA对象
WSAStartup(MAKEWORD(2,2),&wsd); //初始化套接字
下面介绍网络程序开发中经常使用的套接字函数。

WSAStartup

该函数用于初始化Ws2_32.dll动态链接库。在使用套接字函数之前,一定要初始化Ws2_32.dll动态链接库。
语法:
int WSAStartup ( WORD wVersionRequested,LPWSADATA lpWSAData );
wVersionRequested:调用者使用的Windows Socket的版本,高字节记录修订版本,低字节记录主
版本。例如,如果Windows Socket的版本为2.1,则高字节记录1,低字节记录2。

lpWSAData:一个WSADATA结构指针,该结构详细记录了Windows套接字的相关信息。

返回值:0成功


socket

该函数用于创建一个套接字。
语法:
SOCKET socket ( int af,int type, int protocol );

af:一个地址家族,通常为AF_INET。
type:套接字类型。如果为SOCK_STREAM,表示创建面向连接的流式套接字;为SOCK_DGRAM,表示创建面向无连接的数据报套接字;为SOCK_RAW,表示创建原始套接字。对于这些值,用户可以在Winsock2.h头文件中找到。
potocol:套接口所用的协议,如果前面已经指定为tcp或udp,可以设置为0。
返回值:创建的套接字句柄。

connect

该函数用于发送一个连接请求以连接远端服务器。仅用于客户端。
语法:

int connect (SOCKET s,const struct sockaddr FAR* name,int namelen );
s:一个套接字。
name:套接字s想要连接的主机地址和端口号。
namelen:name缓冲区的长度。
返回值:如果函数执行成功,返回值为0;否则为SOCKET_ERROR。用户可以通过WSAGETLASTERROR
得到其错误描述。


bind

该函数用于将套接字绑定到指定的端口和地址上。通常客户端不需要。
语法:
int bind (SOCKET s,const struct sockaddr FAR* name,int namelen );
s:套接字标识。
name:一个sockaddr结构指针,该结构中包含了要结合的地址和端口号。
namelen:确定name缓冲区的长度,即sizeof(*name)。
返回值:如果函数执行成功,返回值为0;否则为SOCKET_ERROR。

listen

该函数用于将套接字设置为监听模式,并设置接受队列大小。对于流式套接字,必须处于监听模式才能够接收客户端套接字的连接。仅用于服务器端。
语法:
int listen ( SOCKET s, int backlog);
s:套接字标识。
backlog:等待连接的最大队列长度。例如,如果backlog被设置为2,此时有3个客户端同时发出连接请求,那么前2个客户端连接会放置在等待队列中,第3个客户端会得到错误信息。
返回值:如无错误发生,listen()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

accept

该函数用于接受客户端的连接。在流式套接字中,必须处于监听状态才能接受客户端的连接。仅用于服务器端。
语法:
SOCKET accept ( SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen );
s:一个套接字,它应处于监听状态。
addr:out型参数,是一个sockaddr_in结构指针,包含一组客户端的端口号、IP地址等信息,不要与bind里的 sockaddr弄混淆
addrlen:用于接收参数addr的长度。
返回值:返回一个新的套接字,它对应于已经接受的客户端连接,对于该客户端的所有后续操作,都应使用这个新的套接字。

accept后会阻塞,直到有一个客户端请求连接,这时accept返回一个新的SOCKET s2,就用这个s2与客户端通信,即服务器端send、recv里的SOCKET参数填s2,一定不要用accept(s1, ...)中的那个s1与客户端通信。

recv

该函数用于从面向连接的套接字中接收数据。
语法:
int recv (SOCKET s,char FAR* buf,int len,int flags);

s 表示一个套接字
buf 表示接收数据的缓冲区
len 表示buf的长度
flags 表示函数的调用方式,一般置0。

返回值:实际buf中得到的的字节数


send

该函数用于在面向连接方式的TCP套接字间发送数据。
语法:
int send (SOCKET s,const char FAR * buf, int len,int flags);

s 表示一个套接字
buf 表示存放要发送数据的缓冲区
len 表示缓冲区长度
flags 表示函数的调用方式



对于send和recv函数,如果服务器用这两个函数收发数据,则参数s应该是accept函数返回的新套接字的句柄,客户端则是创建时的套接字。


recvform

接收数据报,用于面向无连接模式的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

向一指定目的地发送数据,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。


closesocket

该函数用于关闭套接字。
语法:
int closesocket (SOCKET s);
s:标识一个套接字。如果参数s设置有SO_DONTLINGER选项,则调用该函数后会立即返回,但
此时如果有数据尚未传送完毕,会继续传递数据,然后才关闭套接字。


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;
}

报错的话项目->属性->c/c++->预处理器->预处理器定义:_WINSOCK_DEPRECATED_NO_WARNINGS

先运行服务器,再运行客户端,下面是成功时的截图





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值