利用winsock编写网络应用程序服务端的步骤简述如下
WSAStartup 初始化网络编程库
socket 创建套接字
bind 指定地址、端口,绑定套接字
listen 进入监听状态
accept 等待接收新连接
send/recv 收发数据
closesocket 关键套接字
WSAStartup 释放对动态库的使用
下面详细介绍各API
1. Winsock初始化
调用int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData )函数
WSAStartup,即WSA(Windows SocKNDs Asynchronous,Windows异步套接字)的启动命令。是Windows下的网络编程接口软件Winsock1 或 Winsock2 里面的一个命令(Ps:Winsock 是由Unix下的BSD Socket发展而来,是一个与网络协议无关的编程接口)。
为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。使用Socket的程序在使用Socket之前必须调用WSAStartup函数。
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested=MAKEWORD(2,2);
if(WSAStartup(wVersionRequested,&wsaData)!=0)
{
//初始化失败
}
if(wsaData.wVersion != wVersionRequested)
{
//版本不匹配
}
2. 创建套接字
int socket(int domain, int type, int protocol);
函数说明:
第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置AF_INET;
第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM、原始套接字SOCK_RAW(WinSock接口并不适用某种特定的协议去封装它,而是由程序自行处理数据包以及协议首部);
第三个参数指定应用程序所使用的通信协议。此参数可以指定单个协议系列中的不同传输协议。在Internet通讯域中,此参数一般取值为0,系统会根据套接字的类型决定应使用的传输层协议。
返回值:成功返回套接字描述符,否则返回INVALID_SOCKET。
例子:
if ((m_sk = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
//创建套接字失败
}
3. 绑定套接字
地址结构的定义:
/*
* Socket address, internet style.
*/
struct sockaddr_in {
short sin_family; //协议族
u_short sin_port; //端口号
struct in_addr sin_addr; //地址信息
char sin_zero[8];
};
/*
* Internet address (old style... should be updated)
*/
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr
/* can be used for most tcp & ip code */
/*
* Structure used by kernel to store most
* addresses.
*/
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};
注意:s_addr是一个宏,可以方便设置地址
#define s_addr S_un.S_addr
/* can be used for most tcp & ip code */
/*
* Structure used by kernel to store most
* addresses.
*/
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};
注意:s_addr是一个宏,可以方便设置地址
上述定义在winsock2.h中
绑定函数原型:
int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);
函数说明:
参数列表中,sockfd 表示已经建立的socket编号(描述符);
my_addr 是一个指向sockaddr结构体类型的指针;
参数addrlen表示my_addr结构的长度,可以用sizeof函数获得。
例子:
sockaddr_in addr;
addr.sin_family = AF_INET; //使用互联网际协议,即IP协议
addr.sin_port = htons(port);
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
IP地址参数为INADDR_ANY,则由系统内核来自动指定
port为0,则由系统自动指派一个1024~5000之间惟一的端口号
if (bind(m_sk, (sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR)
{
//绑定出错
}
4. 进入监听状态
int listen(int s, int backlog);
函数说明:
s:已绑定的套接字
backlog:同时能处理的最大连接数
Listen()并未开始接收连线, 只是设置socket 为listen 模式, 真正接收client 端连线的是accept(). 通常listen()会在socket(), bind()之后调用, 接着才调用accept()。
5. 接受连接
int accept(int s,struct sockaddr * addr,int * addrlen);
当有新的连接到来时,accept返回一个新的套接字描述符。连接请求端的相关信息存储在addr中。
参数sockfd
参数sockfd是监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。
参数addr
这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
参数len
也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。
如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信
6. 收发数据
int send(int s, const void *buf, int len, int flags);
int recv(int s, void *buf, int len, int flags);
函数返回实际收发的字节数。出错返回-1, 需要关闭此连接。
函数缺省是阻塞函数,直到发送/接收完成。
注意:如果send 函数返回值与参数len 不相等,则剩余的未发送信息需要再次发送
recv接收到数据之后不会在末尾添加’\0’。
7. 其它API
WSAGetLastError()
获取上次失败操作的错误状态
closesocket(int socket)
关闭套接字
WSACleanup()
终止Winsock 2 DLL (Ws2_32.dll) 的使用