工具:VS2015
学习前先稍微了解下系统提供的动态链接库(DLL),windows API中所有函数都包含在里面,这里不深入讲解,只需知道接下来的socket编程要用到里面的各种函数就行了,一般来说我们能学会调用就已经足够了,对应的头文件为 winsock2.h。
使用DLL前必须把DLL加载到当前程序,加载方式分为动态加载和静态加载两种,这里我使用静态加载做示范,需要用到 #pragma 命令,可以在编译时加载,形式为: #pragma comment(lib,"ws2_32.lib"),这是告诉编译器要加载一个库,而这个库的名字叫做“ws2_32.lib”。那么我们加载完了就可以直接使用了吗?答案时否定了,我们还需要对其进行初始化。初始化需要用到一个函数
int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData)。wVersionRequested 为 WinSock 规范的版本号,lpWSAData 为指向WSADATA结构体的指针。这里我们使用得版本号为2.2,于是这样写WSAStartup(MAKEWORD(2, 2), &wsadata);函数第一个参数类型为WORD,等价于unsigned short ,所以需要用MAKEWORD()进行转换,接下来我们定义一个WSADATA 指针放在第二位就搞定了。以上的代码总结为以下
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int main()
{
//初始化DLL
WSADATA wsadata;
WSAStartup(MAKEWORD(2, 2), &wsadata);
return 0;
}
因为我们进行的是TCP通信,所以要用到的传输方式为SOCK_STREAM,表示面向连接的可靠传输,对应得UDP使用的传输方式为SOCK_DGRAM,表示无连接的不可靠传输。先给出一个服务器端的代码,再一一分析。
//服务器端.cpp
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int main()
{
WSADATA wsadata;
WSAStartup(MAKEWORD(2, 2), &wsadata);
SOCKET serSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //用0填充每一个字节
sockAddr.sin_family = PF_INET; //使用IPv4地址
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的ip地址
sockAddr.sin_port = htons(1234); //具体的端口号
//绑定套接字
bind(serSock, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
listen(serSock, 20);
return 0;
}
先看SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);AF_INET代表使用IPv4地址,对应的IPv6用AF_INET6表示,AF是“Address Family”的缩写,INET是“intenet”的缩写;第二个参数SOCK_STREAM代表传输的方式是以字节流的形式;最后一个参数表示使用TCP协议。整个函数返回的是一个服务器端的套接字,用serSock表示。
接下来几行的功能是设置服务器的相关信息,然后使用bind函数将套接字serSock和设置的各种属性绑定,比较需要注意的是第二个参数,需要将sockaddr_in转化为SOCKADDR,这是因为这两个结构体的字节数相同,相互转化不会导致数据得丢失,其次,sockaddr已经将ip地址和端口号合并在一起,想要对两个进行赋值会相当麻烦,而sockaddr_in则把这两个分开,方便赋值,之后再强转化。最后通过listen函数进行监听,第二个参数表示请求队列的数量,具体多少根据情况设置。
完整的服务器端cpp
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib")
const int BUF_SIZE = 100;
int main()
{
WSADATA wsadata;
WSAStartup(MAKEWORD(2, 2), &wsadata);
SOCKET serSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //用0填充每一个字节
sockAddr.sin_family = PF_INET; //使用IPv4地址
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的ip地址
sockAddr.sin_port = htons(1234); //具体的端口号
//绑定套接字
bind(serSock, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
listen(serSock, 20);
//接受客户端请求
SOCKADDR clientAddr;
int clientAddr_size = sizeof(clientAddr);
SOCKET clientSock = accept(serSock, (SOCKADDR*)&clientAddr, &clientAddr_size);
//向客户端发送消息
char* str = "hello world!!";
send(clientSock, str, strlen(str)+sizeof(char),NULL);
//关闭套接字
closesocket(clientSock);
closesocket(serSock);
WSACleanup();
system("pause");
return 0;
}
accept()函数:当套接字处于监听状态,可以通过accept()接受客户端的请求。
send()函数:向客户端传输内容。
客户端跟服务端差不多
#include
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib")
const int BUF_SIZE = 100;
int main()
{
WSADATA wsadata;
WSAStartup(MAKEWORD(2, 2), &wsadata);
//需要连接服务端的信息
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(1234);
//创建套接字并连接
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
connect(sock, (SOCKADDR*)& sockAddr, sizeof(sockAddr));
char infoBuff[MAXBYTE] = { 0 };
recv(sock, infoBuff, MAXBYTE,NULL);
cout << "从服务器接受到得信息为:" << infoBuff << endl;
closesocket(sock);
WSACleanup();
system("pause");
return 0;
}
recv()函数,从服务端接受传来得信息并存在infoBuff数组里。
先运行服务端的cpp,此时服务端进入监听状态,再运行客户端cpp,便可在控制台上打印出服务端传来的文字。