参考
- 《TCP/IP网络编程》 尹圣雨
基于Windows编写服务器端和客户端
Windows套接字(Winsock)大部分是参考BSD系列UNIX套接字设计的,所以很多地方与Linux套接字类似。因此,只需要更改Linux环境下编好的一部分网络程序内容,就能在Windows平台下运行
为什么同时学习Linux和Windows
大多数项目都在Linux系列的操作系统下开发服务器端,而多数客户端是在Windows平台下开发的。而且,有时程序还需要在两个平台之间相互切换
Windows套接字编程的预备
- 导入头文件WinSock2.h
- 链接ws2_32.lib库。VS:快捷键Alt+F7–>配置属性–>链接器–>输入–>附加依赖项
Winsock初始化
首先必须调用WSAStartup函数,设置程序中的Winsock版本,并初始化相应版本的库
#include <WinSock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
成功时返回0,失败时返回非零的错误代码值。wVersionRequested:要用的Winsock版本信息;lpWSAData:WSADATA结构体变量的地址
其中,WORD是unsigned short类型,高8位为副版本号,低8位为主版本号。例如,版本2.2,应传递0x0202。也可以借助MAKEWORD宏函数则能轻松构建WORD型版本信息,例如:
MAKEWORD(1, 2); //主版本为1,副版本为2,返回0x0201
另外,第二个参数lpWSAData的类型LPWSADATA是WSADATA的指针类型,调用完函数后,相应参数中将填充已初始化的库信息
Winsock编程的套路
int main()
{
WSADATA wsaData;
// ......
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ErrorHandling("WSAStartup() error!");
}
// ......
return 0;
}
如何注销库
利用WSACleanup()函数注销库,调用时,Winsock相关库将归还Windows操作系统,无法再调用Winsock函数时才调用该函数,但通常都在程序结束之前调用
#include <WinSock2.h>
int WSACleanup(void);
基于Windows的套接字相关函数
(1)socket函数,与Linux下的socket函数提供相同功能
#include <WinSock2.h>
SOCKET socket(int af, int type, int protocol);
成功时返回套接字句柄,失败时返回INVALID_SOCKET
(2)bind函数,调用其分配IP地址和端口号
#include <WinSock2.h>
int bind(SOCKET s, const struct sockaddr* name, int namelen);
成功时返回0,失败时返回SOCKET_ERROR
(3)listen函数,调用其使套接字可接收客户端连接
#include <WinSock2.h>
int listen(SOCKET s, int backlog);
成功时返回0,失败时返回SOCKET_ERROR
(4)accept函数,调用其受理客户端连接请求
#include <WinSock2.h>
SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);
成功时返回套接字句柄,失败时返回INVALID_SOCKET
(5)connect函数,调用其从客户端发送连接请求
#include <WinSock2.h>
int connect(SOCKET s, const struct sockaddr* name, int namelen);
成功时返回0,失败时返回SOCKET_ERROR
(6)closesocket函数,调用其关闭套接字
#include <WinSock2.h>
int closesocket(SOCKET s);
成功时返回0,失败时返回SOCKET_ERROR
Windows中的文件句柄和套接字句柄
Windows中通过调用系统函数创建文件时,返回句柄(handle),相当于Linux中的文件描述符。只不过Windows中要区分文句柄和套接字句柄
Winsock函数中的socket和accept函数的返回值类型SOCKET,就是为了保存套接字句柄整型值(unsigned short)的新数据类型,由typedef声明定义
创建基于Windows的服务器端
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
void ErrorHandling(char* message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hServSock, hClntSock;
SOCKADDR_IN servAddr, clntAddr;
int szClntAddr;
char message[] = "Hello World!";
if (argc != 2)
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) // 初始化套接字库
{
ErrorHandling("WSAStartup() error!");
}
hServSock = socket(PF_INET, SOCK_STREAM, 0); // 创建套接字
if (hServSock == INVALID_SOCKET)
{
ErrorHandling("socket() error");
}
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(atoi(argv[1]));
if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) // 给该套接字分配IP地址与端口号
{
ErrorHandling("bind() error");
}
if (listen(hServSock, 5) == SOCKET_ERROR) // 使创建的套接字成为服务器端套接字
{
ErrorHandling("listen() error");
}
szClntAddr = sizeof(clntAddr);
hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); // 受理客户端连接请求
if (hClntSock == INVALID_SOCKET)
{
ErrorHandling("accept() error");
}
send(hClntSock, message, sizeof(message), 0); // 向连接的客户端传输数据
closesocket(hClntSock);
closesocket(hServSock);
WSACleanup(); // 注销初始化的套接字库
return 0;
}
void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
创建基于Windows的客户端
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <Ws2tcpip.h>
void ErrorHandling(char* message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN servAddr;
char message[30];
int strLen;
if (argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) // 初始化Winsock库
{
ErrorHandling("WSAStartup() error!");
}
hSocket = socket(PF_INET, SOCK_STREAM, 0); // 创建套接字
if (hSocket == INVALID_SOCKET)
{
ErrorHandling("socket() error");
}
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
//servAddr.sin_addr.s_addr = inet_addr(argv[1]);
inet_pton(AF_INET, argv[1], &servAddr.sin_addr);
servAddr.sin_port = htons(atoi(argv[2]));
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) // 通过套接字向服务器发出连接请求
{
ErrorHandling("connect() error!");
}
strLen = recv(hSocket, message, sizeof(message) - 1, 0); // 接收服务器发来的数据
if (strLen == -1)
{
ErrorHandling("read() error!");
}
printf("Message from server: %s \n", message);
closesocket(hSocket);
WSACleanup(); // 注销Winsock库
return 0;
}
void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
基于Windows的I/O函数
Linux中套接字也是文件,因而可以通过文件I/O函数read和write进行数据传输。而Windows严格区分文件I/O函数和套接字I/O函数
(1)send()函数
#include <WinSock2.h>
int send(SOCKET s, const char* buf, int len, int flags);
成功时返回传输字节数,失败时返回SOCKET_ERROR。s:表示数据传输对象连接的套接字句柄值;buf:保存待传输数据的缓冲地址值;len:要传输的字节数;flags:传输数据时用到的多种选项信息
(2)recv()函数
#include <WinSock2.h>
int recv(SOCKET s, const char* buf, int len, int flags);
成功时返回接收的字节数(收到EOF时为0),失败时返回SOCKET_ERROR。s:表示数据传输对象连接的套接字句柄值;buf:保存待传输数据的缓冲地址值;len:要传输的字节数;flags:传输数据时用到的多种选项信息