C++网络编程之Socket编程
Socket(套接字)简述:
套接字是由美国伯克利大学提出并设计的一种在网络中不同主机之间进行数据传输的通信桥梁。在我们的实际生活中,我们一般所用到的通信软件的通信功能基本上都是基于Socket套接字作为通信桥梁的实现。所以,套接字在网络编程中,有着非常重要的作用。
1.1寻址方式
因为套接字需要在各种网络协议中使用,所以为了区分程序所使用的网络协议必须使用统一的寻址方式。例如在TCP/IP协议中,用户使用的IP地址和端口号进行通信双方,而在其他的协议中不一定也使用盖房时确定通信双方。
在Winsock中,用户可以使用TCP/IP地址家族中统一的套接字地址结构解决寻址冲突问题:
/*
值得注意的是:
sin_family指定该地址的地址家族,我们一般使用的是TCP/IP地址家族,所以这里必须设置为AF_INET;
sin_zero在实际引用中有点“然并卵”的意思,也就是说没啥用,不过唯一值得关心的是它的设定是为了与第一个版本的套接字地址结构大小相匹配(相等)而设置的,其值一般设为0即可。
*/
//套接字地址结构
struct sockaddr_in
{
short sin_family;//指定地址家族(地址格式)
unsigned short sin_port;//指定端口号
struct sin_addr;//指定IP地址
char sin_zero[8];//留作备用,需要指定为0
};
//其中sin_addr表示的是32位的IP地址结构体,如下
struct in_addr
{
union
{
struct
{
//看得出来啊,IP表示使用的是u_char类型
unsigned char s_b1, s_b2, s_b3, s_b4;
}s_un_b;
struct
{
//使用u_short表示IP地址
unsigned short s_w1, s_w2;
};s_un_w;
//使用u_long类型表示IP地址
unsigned long s_addr;
}s_un;
};
1.2 字节顺序
在socket套接字编程中,传输数据的的排列顺序以网络字节顺序和主机字节顺序为主。所以一般情况下,我们在网络编程中,如果使用网络数据发送时,需要将数据转换为网络字节顺序排列,不然可能会导致数据丢失等。如果我们接收网络数据到本地时,我们需要将数据转换成以主机字节序列排列的数据。从存储的角度来讲:网络字节顺序的数据是将重要的字节先行存储;主机字节序列是将不重要的字节先行存储。
字节转换函数:
//将一个u_short类型的IP地址从主机字节顺序转换到网络字节顺序
u_short htons(u_short hostshort);
//将一个u_long类型的IP地址从主机字节顺序转换到网络字节顺序
u_long htonl(u_long hostlong);
//将一个u_short类型的IP地址从网络字节顺序转换到网主机字节顺序
u_short ntohs(u_short netshort);
//将一个u_long类型的IP地址从网络字节顺序转换到主机字节顺序
u_long ntohl(u_long netlong);
//将一个字符串IP转换成一个网络字节顺序的IP地址
unsigned long inet_addr(const char FAR * cp);
//将一个网络字节顺序的IP地址转换成一个字符串IP地址
char FAR * inet_ntoa(struct in_addr in);
1.3 Winsock简单实例
首先,我们先介绍一下Winsock编程流程以及其用到的函数:
STEP 1:初始化和释放套接字库
a:包含库信息,显示添加动态链接库WS2_32.DLL
b:使用WSAstartup()函数初始化库
//函数成功返回0,否则函数调用失败
int WSAstartup(WORD wVersionRequested, LPWSADATA lpWSAData);
参数1:表示当前套接字库的版本号,比如当前版本号为2.0,则
WORD wVersionRequested = MAKEWORD(2, 0);
参数2:表示获取到套接字库的详细信息,其中WSAData结构体定义如下
typedef struct WSAData
{
//库文件建议应用程序使用的版本号
WORD wVersion;
//库文件支持的最高版本号
WORD wHighVersion;
//描述库文件的字符串
char szDescription[WSADESCRIPTION_LEN+1];
//系统状态字符串
char szSystemStatus[WSASYS_STATUS_LEN+1];
//同时支持的最大套接字数
unsigned short iMaxSockets;
//已废弃
unsigned short iMaxUdpDg;
//已废弃
char FAR * lpVendorInfo;
}WSADATA, FAR * LPWSADATA;
STEP 2:创建套接字句柄
创建套接字句柄函数为socket(),函数原型为:
SOCKET socket(
int af,//指定套接子的地址格式(一般为AF_INET)
int type,//套接字类型
int protocol//如果套接字类型(type)已经设定为TCP/UDP,那么该参数可以设定为0
);
函数执行成功返回套接字句柄,否则返回INVALID_SOCKET。
值得注意的是参数2:type的值
套接字类型取值 | 含义 |
---|---|
SOCK_STREAM | 创建流式套接字、TCP方式 |
SOCK_DGRAM | 创建数据报套接字、UDP方式 |
SOCK_RAW | 创建原始套接字 |
STEP 3:绑定地址信息
对于服务器而言,套接字创建成功后,还应该将套接字与地址结构信息进行相关联。使用到的函数为:
bind(
SOCKET s, //套接字句柄
const struct sockaddr FAR * name,//地址结构信息
int numelen//地址结构大小
);
函数调用成功返回0,否则失败。
服务器程序将套接字句柄与地址信息绑定后,则需要开启监听服务,使用到的函数为:
int listen(
SOCKET s,//实现监听功能的套接字句柄
int backlog//监听连接的最大数量
);
STEP 4:连接
客户端需要与服务器端连接,所使用到的函数为:
int connect (
SOCKET s, //套接字句柄
const struct sockaddr FAR * name,//连接服务器的地址结构指针
int numelen//地址结构大小
);
当服务器端接收到客户端请求时,用到的函数为:
SOCKET accept(
SOCKET s, //套接字句柄
struct sockaddr FAR * addr,//获取连接客户端的地址信息
int FAR * addrlen//地址大小
);
STEP 5:数据收发
当客户端与服务端连接成功后,就可以进行数据交互了,所使用到的函数为:
TCP:
//数据发送函数
int send(SOCKET s, const char FAR * buf, int len, int flags);
//数据接收函数
int recv(SOCKET s, char FAR * buf, int len, int flags);
其中有个使用要点:如果服务器端使用上面的函数时,参数s为监听函数返回的套接字句柄;客户端使用的则是自己创建的套接字句柄。
UDP:
int sendto(
SOCKET s, //套接字句柄
const char FAR * buf, //数据缓冲区
int len, //数据长度
int flags, //一般设置为0
const struct sockaddr FAR * to, //目标地址结构信息
int tolen //目标地址结构大小
);
int recvform(
SOCKET s, //套接字句柄
const char FAR * buf, //数据缓冲区
int len, //数据长度
int flags, //一般设置为0
const struct sockaddr FAR * from, //接收数据发送方地址结构信息
int fromlen //地址结构大小
);
STEP 6:关闭套接字
俗话说的好啊:走前要收尾,所以程序模块完成后,我们还应该关闭套接字,所使用到的函数为:
int closeSocket(SOCKET s);
//释放套接字库
::WSACleanup();
其次,简单实例,仅供参考:
TCP:
//server服务端
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#pragma comment(lib, "WS2_32.lib")
using namespace std;
int main1(void)
{
//STEP 1;init socket lib
WSADATA data;
WORD wVersionRequested = MAKEWORD(2, 0);
::WSAStartup(wVersionRequested, &data);
//STEP 2:create socket handle
SOCKET s, s2; //s2 is accept socket handle
s = ::socket(AF_INET, SOCK_STREAM, 0); //TCP socket
//init socket address struct
sockaddr_in addr, addr2; //addr2 is accept socket address
int addrSize = sizeof(addr2); //get the address struct size
addr.sin_family = AF_INET;
addr.sin_port = htons(75); //set port
addr.sin_addr.S_un.S_addr = INADDR_ANY; //set address tpye: in any address
//STEP 3:bind socket address to socket, and start listen
::bind(s, (sockaddr*)&addr, addrSize);
::listen(s, 5); //set max connect number is 5
cout << "Server is started!" << endl;
//create a string to send to client
char sztext[] = "Hello! TCP connection!\n";
//STEP 4: start listen client accept
while (true)
{
s2 = ::accept(s, (sockaddr*)&addr2, &addrSize);
if (s2 != NULL)
{
cout << inet_ntoa(addr2.sin_addr) << "is connected!" << endl;
::send(s2, sztext, sizeof(sztext), 0);
::closesocket(s2);
::closesocket(s);
::WSACleanup();
break;
}
else
{
::Sleep(100);
}
}
return 0;
}
//client客户端
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#pragma comment(lib, "WS2_32.lib")
using namespace std;
int main1(void)
{
//STEP 1;init socket lib
WSADATA data;
WORD wVersionRequested = MAKEWORD(2, 0);
::WSAStartup(wVersionRequested, &data);
//STEP 2:create socket handle
SOCKET s;
s = ::socket(AF_INET, SOCK_STREAM, 0); //TCP socket
//init socket address struct
sockaddr_in addr; //addr2 is accept socket address
int addrSize = sizeof(addr); //get the address struct size
addr.sin_family = AF_INET;
addr.sin_port = htons(75); //set port
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
cout << "Client is started!" << endl;
//create a string to recive server reqest
char sztext[32] = { 0 };
//STEP 3: connect to server
::connect(s, (sockaddr*)&addr, addrSize);
//recive server request
::recv(s, sztext, sizeof(sztext), 0);
cout << sztext << endl;
//STEP 4: clear resource
::closesocket(s);
::WSACleanup();
return 0;
}
测试运行结果:
UDP:
//server服务端
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#pragma comment(lib, "WS2_32.lib")
using namespace std;
int main(void)
{
//STEP 1;init socket lib
WSADATA data;
WORD wVersionRequested = MAKEWORD(2, 0);
::WSAStartup(wVersionRequested, &data);
//STEP 2:create socket handle
SOCKET s;
s = ::socket(AF_INET, SOCK_DGRAM, 0); //UDP socket
//init socket address struct
sockaddr_in addr, addr2; //addr2 is accept socket address
int addrSize = sizeof(addr2); //get the address struct size
addr.sin_family = AF_INET;
addr.sin_port = htons(6000); //set port
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //set address tpye: in any address
//STEP 3:bind socket address to socket, and start listen
::bind(s, (sockaddr*)&addr, addrSize);
cout << "UDP Server is started!" << endl;
//create a string to send to client
char sztext[] = "Hello! UDP connection!\n";
//recive string from client
char getBuf[32] = { 0 };
//STEP 4: recv client send buffer
if (::recvfrom(s, getBuf, sizeof(getBuf), 0, (sockaddr*)&addr2, &addrSize) != SOCKET_ERROR)
{
cout << "recive address buffer from " << inet_ntoa(addr2.sin_addr) << endl;// " and get text:" << getBuf << endl;
::sendto(s, sztext, sizeof(sztext), 0, (sockaddr*)&addr2, addrSize);
}
else
{
::Sleep(100);
}
::closesocket(s);
::WSACleanup();
return 0;
}
//client客户端
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#pragma comment(lib, "WS2_32.lib")
using namespace std;
int main(void)
{
//STEP 1;init socket lib
WSADATA data;
WORD wVersionRequested = MAKEWORD(2, 0);
::WSAStartup(wVersionRequested, &data);
//STEP 2:create socket handle
SOCKET s;
s = ::socket(AF_INET, SOCK_DGRAM, 0); //UDP socket
//init socket address struct
sockaddr_in addr; //addr2 is accept socket address
int addrSize = sizeof(addr); //get the address struct size
addr.sin_family = AF_INET;
addr.sin_port = htons(6000); //set port
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
cout << "Client is started!" << endl;
//create a string to send to client
char sztext[] = "I am client! UDP connection!\n";
//recive string from client
char getBuf[32] = { 0 };
//STEP 3: recv server send buffer
if (::sendto(s, sztext, sizeof(sztext), 0, (sockaddr*)&addr, addrSize) != SOCKET_ERROR)
{
::recvfrom(s, getBuf, sizeof(getBuf), 0, (sockaddr*)&addr, &addrSize);
cout << "recive Server buffer from " << inet_ntoa(addr.sin_addr) << " and get text:" << getBuf << endl;
}
::closesocket(s);
::WSACleanup();
return 0;
}
测试运行结果: