//MFC例子
//需要包含Winsock2.h头文件,并连接Ws2_32.lib库文件。
//【项目】-【属性】-【链接器】-【输入】-【附加依赖项】,进行编辑,添加 ws2_32.lib库。去掉从父级或项目默认设置继承的勾选
//先运行服务端,等着客户端连接
#include <iostream>
#include "Winsock2.h"
int main()
{
//①**************************************************************//
// ①、首先加载套接字库
//加载套接字库,进行套接字库版本协商。第一个参数,指定请求的版本号。
//wVersionRequested高位字节,指定所需的Winsock库副板本,低位字节是主版本。
//可用MAKEWORD(x,y)宏,获得wVersionRequested的正确值,x是高位,y是低位。
//第二个参数是,指向WSADATA结构的指针,加载库的版本有关信息填在此处。
//WSADATA结构的,第一个字段wVersion:大算使用的winsock库版本。高位winsock的副板本,低位主版本。
//第二个字段wHighVersion:现有winsock库的最高版本。高位winsock的副板本,低位主版本。
//szDescription:事实上并没有用。
//szSystemStatus:事实上并没有用。
//iMaxSockets:不要使用。同时打开多少套接字。然而,应用WSAEnumProtocols来查询,很大程度与内存有关。
//iMaxUdpDg:不要使用。数据报的最大长度。
//lpVendorInfo:任何win32平台都没使用这个字段。
//WSAStartup()成功调用后,最后要WSACleanUp()。msdn有调用的例子。
WORD wVersionRequested;
WSADATA wsaData;
int err;
//调用1.1版本的
wVersionRequested = MAKEWORD(1, 1);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
return 0;
}
//看返回值低字节是否等于1,高字节是否等于1。
if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) != 1) {
//如果不是请求的1.1版本,就清理返回。
WSACleanup();
return 0;
}
//②**************************************************************//
//②、接下来,创建套接字。
//SOCKET第一个参数,af:指定的地址簇,对于TCP/IP协议是AF_INET(也可写成PF_INET)
//第二个参数type:指定套接字类型。对于1.1版本,只有两个SOCK_STREAM流式套接字TCP,SOCK_DGREAM数据报UDP。
//第三个参数,与特定地址簇相关,设置0自动选择。
//如果调用成功,返回新的SOCKET数据类型的套接字描述符,如果失败返回INVALID_SOCKET,错误信息可通过WSAGetLastError函数
SOCKET sockSrv = socket(AF_INET,SOCK_STREAM,0);
//③**************************************************************//
//③、将套接字绑定,本地地址和端口上
//bind接受三个参数,第一个参数:要绑定的套接字。
//第二个参数:指定该套接字的本地地址信息,指向sockaddr结构的指针
//第三个参数:由于sockaddr为所有地址簇准备,所以需要指定该结构的长度。
//sockaddr结构有两个,sa_family:该地址家簇,这里设置AF_INET。
//第二个sa_data:表示要求一块内存分配区,起到占位作用。
//除了sa_family以外,sockaddr是用网络顺序表示,在tcp\ip中。
//可以用sockaddr_in结构替换sockaddr方便。
//sockaddr_in结构体,sin_family:对于IP地址,一直是AF_INET
//第二个sin_port:要分配给套接字的端口,要用网络字节顺序。
//第三个sin_addr:主机的IP地址,要用网络字节顺序。
//第四个sin_zero:只是填充数,使sockadd_in长度与sockaddr一样。
//其中sin_addr结构体:是个联合,通常将ip地址转化为u_long类型给S_addr赋值。
//如果成功,返回0。如果失败,返回SOCKET_ERROR,错误信息可通过WSAGetLastError函数返回。
//可将IP地址设置为INADDR_ANY,允许套接字向任何分配给本机的IP,发送或接受数据。有的机器有多个网卡,每个网卡IP。
//如果指定,多个IP中使用一个,用inet_addr()函数,需要字符串为参数定义点分十进制ip如192.168.0.13,
//inet_addr会返回一个,适合S_addr的u_long类型的数值。
//inet_ntoa会完成想反的转换,将in_addr结构体参数,返回一个点分十进制的ip地址字符串。
SOCKADDR_IN addrSrv;//定义地址结构体变量,saddr_in方便。
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//对地址结构体成员赋值,除了sa_family,都要网络字节序,用htonl()将u_long
//还要个htons,将u_short从主机字节序,转网络字节序。这里不转也许,INADDR_ANY这个宏是0.
addrSrv.sin_port = htons(6000);//端口是2个字节,要用htons转换。
addrSrv.sin_family = AF_INET;
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//绑定,参数需要sockaddr,我们定义的是sockaddr_in,所以要强制转换。
//指定地址结构的大小,用sizeof()。
//④**************************************************************//
//④、将套接字设置监听模式
//listen()第一个参数,套接字描述符
//第二个参数,等待连接队列的最大长度,如果设置SOMAXCONN,自动设合适。而不是一个端口同时可以进行连接的数。
//比如设置为2,这时有3个连接请求到来,头两个等待进队列,第三个被拒绝了。
listen(sockSrv, 5);//这里设置5个。
//⑤**************************************************************//
//⑤、设置一个死循环
//服务端,要不断等待客户端连接请求倒来,循环得以持续运行。
//accept()第一个参数:套接字描述符
//第二个参数:buffer指针,接受连接时,保存了发起连接的客户端IP地址和端口信息。
//第三个参数:包含返回的地址结构的长度。
//##accept()在接受了客户端的连接请求后,返回一个新的连接的套接字描述符,用新的套接字与客户端通信,
//旧的仍然继续监听,客户端的连接请求。
while (1)
{
SOCKADDR_IN addrClient;//定义地址结构变量,用来接收客户端地址信息。
int len = sizeof(SOCKADDR);//定义一个整型变量,赋一个初始值,就是结构体的长度。
//msdn中[IN][OUT],同时出现,需要赋一个初始值,然后会返回一个值。
//等待连接到来,并接受请求。
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);//addrClient接受客户端的地址信息。
//当客户端的连接请求到来,我们接受了这个连接,建立连接,同时返回一个新的套接字。
//⑥**************************************************************//
//⑥、接下来可以进行通信了,向客户端发送数据
//send()第一个参数:套接字
//第二个参数:buffer包含将要被传送的数据
//第三个参数:buffer中数据的长度
//第四个参数:flags将影响,send函数的调用行为。详细MSDN中说明。在这个应用设为0.
char sendBuf[100];//定义字符数组
sprintf_s(sendBuf,"welcome %s to my web",//调用sprintf将一个数据,格式化到buff当中。
inet_ntoa(addrClient.sin_addr));//将客户端地址放到发送的buffer中,inet_ntoa,将in_addr返回点分十进制IP字符串。
//接下来发送了,注意用新的返回的套接字,而不能用监听作用的套接字。
send(sockConn,sendBuf,sizeof(sendBuf)+1,0);//多发送一个字节,为了在接收端,为接受的数据增加个\0结尾。
//⑦**************************************************************//
//⑦、发送数据后,可以接收一个数据
//recv()第一个参数:建立连接后的套接字
//第二个:接受的buffer
//第三个:接收的长度
//第四个:flags与send一样,详见MSDN,这里设置0.
char recvBuf[100];
recv(sockConn,recvBuf,sizeof(recvBuf),0);//接收数据
printf("%s\r\n",recvBuf);//接收数据后,用printf打印出来。将recvBuf。
//⑧**************************************************************//
//⑧、通信完成后,需要关闭套接字,释放套接字资源。
//因为死循环,如果不是,需要关闭监听套接字,并且WSACleanUp()终止winsock库的使用。
closesocket(sockConn);
}
std::cout << "Hello World!\n" << std::endl;
system("PAUSE");
return 0;
}