WinSock网络编程基础
Winsock是Windows环境下的网络编程接口,它最初基于UNIX环境下的BSD Socket,是一个与网络协议无关的编程接口。在Visual Studio中可以使用WinSock API开发网络应用程序,实现计算机之间的通信。
构建WinSock应用程序框架
在使用WinSock 2.2实现网络通信功能时,需要引用头文件winsock2.h和库文件ws2_32.lib,代码如下:
#include <winsock.h>
#pragma comment(lib, "wsock32.lib")
WSADATA是一种数据结构,用来存储被WSAStart函数调用后返回的Windows Sockets数据,包含了系统所支持的Winsock版本信息
WSAStartup()函数用于初始化Windows Sockets,并返回WSADATA结构体。只有调用WSAStartup()函数后,应用程序才能调用其它Windows Sockets API 函数,实现网络通信
下面是使用WinSock2.2实现网络通信的应用程序框架
#include <winsock.h>
#pragma comment(lib, "wsock32.lib")
//主函数
int main()
{
WSADATA wsaData;
//初始化WinSock2.2
if (WSAStartup(MAKEWORD(2,2), &wsaData) !=0)
{
printf("初始化失败");
return 0;
}
//使用WinSock实现网络通信
//......
//最后应该做一些清除工作
if (WSACleanup() == SOCKET_ERROR)
{
printf("WSACleanup出错!");
return 0;
}
}
Socket函数
socket()函数
socket()函数用于创建与指定的服务提供者绑定嵌套字,函数原型如下:
SOCKET socket(
int af, //指定协议的地址家族,通常使用AF_INET
int type, //指定套接字的类型
int protocol //原始套接字,可以用于接收本机网卡上的数据帧或数据包
);
套接字类型 | 说明 |
---|---|
SOCK_STREAM | 提供顺序、可靠、双向和面向连接的字节流数据传输机制,使用TCP |
SOCK_DGRAM | 支持无连接的数据报,使用UDP |
SOCK_RAW | 原始套接字,可以用于接收本机网卡上的数据帧或者数据包 |
如果函数执行成功,则返回新Socket的句柄;如果调用失败,则返回INVALID_SOCKET
创建Socket代码如下:
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
//可以将 protocol 的值设为 0,系统会自动根据嵌套字类型推演出应该使用什么协议
if (s == INVALID_SOCKET)
{
printf("socket error!");
}
bind()函数
bind()函数可以将本地地址与一个Socket绑定在一起,函数原型如下:
int bind(
SOCKET s, //标识一个未绑定的Socket的描述符
const struct sockaddr FAR* name, //绑定到Sockets的sockaddr结构体地址
int namelen //参数name的长度
);
如果未发生错误,则函数返回0;否则返回SOCKET_ERROR
bind()函数应用在未连接的socket上,在调用connect()和listen()函数之前被调用。
使用bind()函数绑定Socket到本地地址的1234端口,代码如下:
//指定绑定地址
struct sockaddr_in addr;
//定义服务器地址
addr.sin_family = PF_INET; //使用IPv4地址
addr.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取IP地址
addr.sin_port = htons(1234); //端口
//绑定到Socket
bind(s, (SOCKADDR*)&addr, sizeof(SOCKADDR));
if ( errCode == SOCKET_ERROR)
{
printf("bind error!");
exit(1);
}
listen()函数
listen()函数可以将嵌套字设置为监听接入连接的状态,函数原型如下:
int listen(
SOCKET s, //指定一个已经绑定但尚未链接的套接字
int backlog //指定等待连接队列的最大长度
);
如果函数执行成功,则返回0;否则返回SOCKET_ERROR
accept()函数
在服务器端调用listen()函数监听接入连接后,可以调用accept()函数来等待接收连接请求,函数原型如下:
SOCKET accept(
SOCKET s, //通过调用listen()函数设置为监听状态的Socket
struct sockaddr FAR* addr, //输出参数,用于接收接入地址信息。可选参数,可以使用NULL
int FAR* addrlen //输出参数,指定接入地址的长度。可选参数
);
如果函数调用成功,则返回一个新建的Socket句柄,该Socket用于实现服务器和客户端之间的通信。如果调用失败,则返回INVALID_SOCKET
使用accept()函数在处于监听状态的套接字上接受接入连接,代码如下:
SOCKET sockAccept; //执行accept()函数后新建的用于实际通信的套接字
struct sockaddr_in from; //用于接收接入地址
int len = sizeof(from);
sockAccept = accept(s, (struct sockaddr *)&from,&len);
if (sockAccept ==INVALID_SOCKET)
{
printf("accept error");
}
connect()函数
connect()函数用于建立到Socket的连接,该Socket必须处于监听状态,函数原型如下:
int connect(
SOCKET s, //未连接的Socket句柄
const struct sockaddr FAR* name, //name指定要建立连接的Socket的名称
int namelen //指定Socket名的长度
如果没有发生错误,则connect()函数返回0;否则返回SOCKET_ERROR
recv()函数
调用recv()函数可以从已连接的Socket中接受数据,函数原型如下:
int recv(
SOCKET s, //已连接的Socket
char* buf, //用于接收数据的缓冲区
int len, //buf缓冲区的长度,单位为字节
int flags //用于影响函数的行为
);
如果函数调用成功,则返回接收数据的字节数;如果连接已经关闭,则返回0;否则返回SOCKET_ERROR
send()函数
调用recv()函数可以在已连接的Socket上发送数据,函数原型如下:
int send(
SOCKET s, //已连接的Socket
const char FAR* buf, //包含要发送数据的缓冲区
int len, //buf缓冲区的长度,单位为字节
int flags //用于影响函数的行为
如果函数调用成功,则返回发送数据的字节数;如果出现错误,则返回SOCKET_ERROR
在客户端使用recv()函数和send()函数发送和接收数据的代码如下:
//初始化WinSock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2), &wsaData) !=0)
{
printf("初始化失败");
return 0;
}
//创建连接到服务器的SOCKET对象
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == INVALID_SOCKET)
{
printf("socket error!");
WSACleanup();
return;
}
//构建地址信息
struct sockaddr_in addr;
//定义服务器地址
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(1234);
//连接到服务器
if (connect(s,(SOCKADDR*)&addr,sizeof(addr))==SOCKET_ERROR)
{
printf("fail to connect!");
WSACleanup();
return;
}
//声明和初始化变量
int bytesSent;
int bytesRecv = SOCKET_ERROR;
char sendbuff[32] = "Client: Sending data.";
char recvbuff[32] = " ";
//发送数据
bytesSent = send(s,sendbuff,strlen(sendbuff),0);
printf("Bytes Sent: %1d\n", bytesSent);
//接收数据
while(bytesRecv != SOCKET_ERROR)
{
bytesRecv = recv(s,recvbuff, 32,0);
if (bytesRecv == 0 || bytesRecv == WSAECONNRESET)
{
printf("连接关闭\n");
break;
}
printf("Bytes Recv: %1d\n", bytesRecv);
}
closesocket()函数
closesocket()函数用于关闭一个Socket,释放其占用的所有资源,函数原型如下:
int closesocket(
SOCKET s
);
如果没有发生错误,则closesocket()函数返回0;否则返回SOCKET_ERROR
shutdown()函数
shutdown()函数用于禁止在指定的Socket上发送和接收数据,函数原型如下:
int shutdown(
SOCKET s,
int how //指定禁用的操作
);
当how被设置为SD_RECEIVE时,不允许在Socket上再次调用recv()函数接收数据;当how被设置为SD_SEND时,不允许在Socket上再次调用send()函数发送数据;当how被设置为SD_BOTH时,在套接字s上禁止发送和接收数据。
如果没有发生错误,则shutdown()函数返回0;否则返回SOCKET_ERROR