赞
踩
Winsock接口实际上是微软提供的一些列API函数。它都包含在Winsock2.h中。使用的时候我们还要连接函数的导入库文件:WS2_32.lib。具体使用方法如下:
#include <Winsock2.h>
#pragma comment(lib,"ws2_32.lib")
实现网络通信,一般我们需要一个服务器端和一个客户端。
整个实现通信的一般过程如下:
服务器端:
1.调用WSAStartup函数初始化WSA
2.调用socket函数建立套接字,返回套接字句柄
3.调用bind函数,关联本地地址到套接字句柄
4.调用listen函数进入监听端口状态
5.调用accept函数,等待接受客户端连接
6.客户端连接,accept函数返回有效套接字句柄
7.调用recv或send函数接受或发送数据
8.调用closesocket函数,关闭客户端套接字
9.可循环5——8,进行数据的发送和接受
10.调用closesocket函数,关闭服务器端套接字
11.调用WSACleanup函数释放winsock库
客户端:
1.调用WSAStartup函数初始化WSA
2.调用socket函数建立套接字,返回套接字句柄
3.调用connect函数,和服务器连接
4.调用recv或send函数接受或发送数据
5.可循环3——4进行收发数据
6.调用closesocket函数,关闭套接字
7.调用WSACleanup函数释放winsock库
下面来详细的讲解一下:
第一步在调用Winsock函数之前必须先装载Winsock函数库,使用WSAStartup函数。失败返回SOCKET_ERROR
int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
第一个参数wVersionRequested是指定想要加载Winsock库的版本。第二个参数指向一个LPWSADATA结构体指针,保存返回的Winsock库的版本信息。
函数成功调用返回0.可以通过调用WSAGetLastError函数获取失败原因。
具体加载Winsock库的代码如下:
- WSADATA wsaData;
- WORD sockVersion=MAKEWORD(2,2);//版本是2.2
- if(WSAStartup(sockVersion,&wsaData)!=0)
- return 0;
介绍两个小知识点:
1.MAKEWORD函数
这个函数实际上是一个宏,作用是将两个byte类型合并成一个word类型,一个是在高8位,一个是在低8位。
2.套接字
套接字可以看做成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入套接字中,套接字将这段信息发送到另外一个套接字,应用程序通过套接字获取信息。
在完成Winsock库初始化后,我们需要调用socket函数来建立套接字
功能函数的原型:
SOCKET socket(int af,int type,int protocol);
参数af指定套接字使用的地址格式,这里只能用AF_INET。
type参数指定套接字的类型,具体可选有三种:
SOCK_STREAM:流套接字,使用TCP提供有连接的可靠传输
SOCK_DGRAM:数据报套接字,使用UDP提供的无连接不可靠传输
SOCK_RAW:原始套接字,Winsock接口并不使用某种特定的协议去封装它,而是由程序自行处理数据报和协议首部。
Protocol是配合type使用的,用来指定协议类型。使用TCP,值是IPPROTO_TCP;如果是UDP,使用IPPROTO_UDP。
函数成功,返回一个SOCKET句柄,失败返回INVALID_SOCKET
什么是句柄?
句柄实际上就是一个长整型数据(long),它是Windows用来标识应用程序中建立或使用的对象的一个唯一标识。它就是一个标识符,通过它我们就可以引用它所标识的对象。一般Windows函数总是返回句柄来提供我们对某些对象或资源的访问。
具体创建一个套接字的代码:
- SOCKET sListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
- if(sListen==INVALID_SOCKET)
- {
- printf("socket error!\n");
- return 0;
- }
建立完套接字后,对于服务器端来说,我们接下来要做的是调用bind函数将本地地址和端口绑定到套接字上。对于客户端,这不是必须的,客户端在建立完套接字后可以直接调用connect函数连接服务器,它会替我们自动绑定,指定一个随机的端口。这样的话我们可以不必要调用bind函数进行地址和端口绑定。
bind函数原型:
int bind(SOCKET s,const struct sockaddr FAR *addr,int namelen);
s指向一个有效的套接字句柄,可以是上面返回的sListen。
addr是一个sockaddr结构体指针,指向要关联的本地地址,结构体具体如下:
struct sockaddr_in
{
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
1.sin_family值必须设置成AF_INET
2.sin_port指定TCP或UDP通信服务的端口号
关于端口号的使用范围:
一般为避免与其他应用程序或系统应用程序使用的端口号冲突,一般编程使用1024-49151之间的端口号
0-1024由IANA管理,为公共服务保留
1024-49151是普通用户注册端口号
4951-65535是动态端口号
3.sin_addr存储一个32位IP地址。这个结构我们只用了解其中包含的一个联合类型S_un,联合中有一个u_long类型S_addr用来存储32为的二进制数所表示的IP地址。
使用inet_addr函数可以讲一个小数点的十进制IP转换成一个32位二进制IP
inet_ntoa,可以进行inet_addr的逆向转换,将一个32位的二进制数IP转换成小数点的十进制字符串IP表示。
最后一个参数namelen指定了sockaddr结构体的大小
4.sin_zero保留,还没有使用
函数成功返回0,失败返回SOCKET_ERROR。绑定本地ip的具体代码:
- sockaddr_in sin;
- sin.sin_family=AF_INET;
- sin.sin_port=htons(4500);//htons可以将主机的无符号短整型数转换成网络字节序列。
- sin.sin_addr.S_un.S_addr=INADDR_ANY;//表示所有可用地址
- if(bind(sListen,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR)
- {
- printf("bind error!");
- return 0;
- }
完成绑定后就是调用listen函数,进入监听状态。函数原型:
int listen(SOCKET s,int backlong);
s是套接字句柄
backlong表示监听队列中允许保持的尚未处理的最大连接数量。通俗说就是同时允许的最大连接数。
函数成功返回0,失败返回SOCKET_ERROR。
注意:
listen函数只支持连接的套接字,如SOCK_STREAM类型的套接字,基于TCP有链接的套接字。UDP的则不能使用。
具体监听代码:
- if(listen(sListen,5)==SOCKET_ERROR)
- {
- printf("listen error!");
- return 0;
- }
设置好套接字进入监听状态后,接下来要进入循环,可以使程序在结束一个连接后,继续处理其他连接。首先调用accept函数等待接收客户端连接。
SOCKET accept(SOCKET s,struct sockaddr* addr,int* addrlen);
s指向套接字句柄
addr指向sockaddr结构体,用来存放客户端地址信息。
addrlen指向sockaddr长度的指针。
如果是阻塞模式下,accept会一直等待,直到有一个连接到来后才继续往下执行。accept会返回一个连接客户端的套接字句柄。我们可以利用这个套接字句柄进行接收和发送数据。
通过调用send和recv函数
从套接字接收数据:
int recv(SOCKET s,char FAR *buf,int len,int flags);
向套接字发送数据
int send(SOCKET s,const char FAR *buf,int len,int flags);
可以看到接收和发送的函数的参数基本相同
s仍然是指向一个有效的套接字句柄
buf指向发送或接收数据的缓冲区
len指向数据缓冲区的长度
flags表示调用方式,一般为0
recv成功返回接收到的字节数,失败返回SOCKET_ERROR
接收或发送完数据后需要调用closesocket关闭accept返回的套接字句柄。这样就完成了一次和客户端的通信。循环完整代码:
- // 循环接受客户的连接请求
- sockaddr_in remoteAddr;
- SOCKET sClient;
- int nAddrLen = sizeof(remoteAddr);
- char revData[255];
- while(TRUE)
- {
- // 接受一个新连接
- sClient = accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen);
- //accept函数调用失败则继续等待连接。
- if(sClient == INVALID_SOCKET)
- {
- printf("accept() error");
- continue;
- }
- //打印出连接者的ip
- printf(" 接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
- //直到收到有效数据时才打印出来
- int ret=recv(sClient,revData,255,0);
- if(ret>0)
- {
- //为了防止打印出错,把字符串结尾设成0x00
- revData[ret]=0x00;
- printf(revData);
- }
- char *buff="\r\nzy_dreamer,server coming...\r\n";
- //发送数据
- send(sClient,buff,strlen(buff),0);
- // 关闭套接字句柄,结束会话
- closesocket(sClient);
- }
最后在退出服务器端程序时,需要调用closesocket函数关闭我们自己创建的socket句柄,然后调用WSACleanup函数释放Winsock库。
完整服务器端代码如下:
- #include "stdafx.h"
- #include <winsock2.h>
- #pragma comment(lib,"ws2_32")
- #include <stdio.h>
- int main(int argc, char* argv[])
- {
- WSADATA wsaData;
- WORD sockVersion = MAKEWORD(2, 2);
- //加载winsock库
- if(WSAStartup(sockVersion, &wsaData) != 0)
- return 0;
- // 创建套节字
- SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if(sListen == INVALID_SOCKET)
- {
- printf("socket error\n");
- return 0;
- }
- // 在sockaddr_in结构中装入地址信息
- sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons(4500); // htons函数 将主机的无符号短整形数转换成网络
- //字节顺序
- sin.sin_addr.S_un.S_addr = INADDR_ANY;
- // 使套接字和本地地址绑定
- if(bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
- {
- printf(" bind error \n");
- closesocket(sListen);
- return 0;
- }
- // 设置套接字进入监听模式
- if(listen(sListen, 5) == SOCKET_ERROR)
- {
- printf("listen error\n");
- closesocket(sListen);
- return 0;
- }
- // 循环接受客户的连接请求
- sockaddr_in remoteAddr;
- SOCKET sClient;//保存客户端套接字
- int nAddrLen = sizeof(remoteAddr);
- char revData[255];
- while(TRUE)
- {
- // 接受一个新连接,阻塞模式下,会一直等待
- sClient = accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen);
- //accept函数调用失败则继续等待下一次连接。
- if(sClient == INVALID_SOCKET)
- {
- printf("accept() error");
- continue;
- }
- //打印出连接者的ip
- printf(" 接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
- //直到收到有效数据时才打印出来
- int ret=recv(sClient,revData,255,0);
- if(ret>0)
- {
- //为了防止打印出错,把字符串结尾设成0x00
- revData[ret]=0x00;
- printf(revData);
- }
- char *buff="\r\nzy_dreamer,server coming...\r\n";
- //发送数据
- send(sClient,buff,strlen(buff),0);
- // 关闭套接字句柄,结束会话
- closesocket(sClient);
- }
- closesocket(sListen);
- WSACleanup();
- return 0;
- }
改进:可以将收发数据的功能放到单独的线程中执行,可以提高程序效率和友好性。
下一篇讲解客户端文章地址:http://blog.csdn.net/zy_dreamer/article/details/8979678
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。