前言:
本章是关于Soket编程的学习总结,通过概念+源码来进行学习记录。
若想了解使用到的技术,如数据传输的方式、MFC实现客户端、服务端双向通信。 较为详细的Socket的总结见下面两个链接:
C++实现Windows下服务端与客户端Socket通信(一)
(详细源码)C++ socket 传输不同类型数据的四种方式
实现MFC双向通信、protobuf数据传输的源码免费下载链接:
一、Socket编程介绍
1.什么是Socket
(1)Socket即套接字,用于描述地址和端口。应用程序通过Socket向网络发出请求或者回应。
(2)Socket就是操作系统提供给程序员操作【网络协议栈】的接口,我们可以通过Socket接口来控制协议栈工作,从而实现跨主机的网络通信。
2.Socket编程原理
(1)Socket编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW)。最常用的是SOCK_STREAM、SOCK_DGRAM。
(2)基于TCP的Socket编程采用的是SOCK_STREAM流式套接字,而基于UDP的Socket编程采用的是SOCK_DGRAM数据报套接字。
(3)SOCK_STREAM表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常用的HTTP协议就使用SOCK_STREAM传输数据,因为要确保数据的正确性,否则网页不能正常解析。
(4)SOCK_DGRAM表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为SOCK_DGRAM所做的校验工作少,所以效率比SOCK_STREAM高。
3.基于TCP的Socket编程
(1)TCP概念
TCP,Transmission Control Protocol,传输控制协议,基于字节流的传输层通信协议。
(2)TCP协议特点
①基于流的方式;
②面向连接;
③可靠通信方式;
④通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点;
⑤传输数据大小限制,一旦连接建立,双方可以按统一的格式传输数据。
(3)TCP的三次握手(建立连接)
①客户端主动向服务端发送一个连接请求,进入SYN_SEND状态。
②服务端收到请求后,回客户端一个响应,进入SYN_RECV状态。
③客户端收到服务端的响应后,回服务端一个确认信息,进入Established状态。
(4)TCP四次挥手(断开连接)
①(一次挥手)客户端主动向服务端发送一个释放连接的请求,进入FIN_WAIT_1状态。
②(二次挥手)服务端收到请求后,回客户端一个确认响应,进入CLOSE_WAIT状态。
③客户端收到服务器的确认回应后,客户端不再发送数据,但仍然会接收服务端的数据,进入FIN_WAIT_2状态。
④(三次挥手)服务端将最后的数据发送给客户端后,就向客户端发送释放连接请求,进入LAST_ACK状态。
⑤(四次挥手)客户端收到服务端释放连接请求后,回服务端一个确认信息后,进入TIME_WAIT状态。
注意:此时TCP连接还没有释放,必须再经过2 * MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
⑥服务端收到客户端的确认后,立即进入CLOSED状态。
(5)基于TCP的Socket通信模型
二、Socket通信程序实现
1.服务端程序
(1)初始化WSA
WORD sockVersion = MAKEWORD(2, 2);//调用2.2版本的socket
WSADATA wsaData;//WSA(Windows Sockets Asynchronous)异步套接字
//将指定版本的socket与应用程序绑定
if (WSAStartup(sockVersion, &wsaData) != 0)//返回为0则表示初始化成功
return 0;
(2)创接套接字
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket == INVALID_SOCKET)
{
cout << "socket error:" << WSAGetLastError() << endl;
WSACleanup();
return 0;
}
(3)绑定IP和端口
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8888);//htons用于更改IP或端口的字节顺序,htons:主机->网络;ntohs:网络->主机
serverAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if (::bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
cout << "bind error !" << endl;
closesocket(listenSocket);
WSACleanup();
return 0;
}
(4)开始监听
if (listen(listenSocket, 5) == SOCKET_ERROR)
{
cout << "listen error !" << endl;
closesocket(listenSocket);
WSACleanup();
return 0;
}
(5)循环接收客户端数据
while (true)
{
SOCKET sClient = INVALID_SOCKET;//初始化一个接受的客户端socket
sockaddr_in remoteAddr; //接收客户端的远程地址
int nAddrlen = sizeof(remoteAddr);
cout << "等待登录..." << endl;
sClient=accept(listenSocket,(SOCKADDR*)&remoteAddr, &nAddrlen);
if (sClient == INVALID_SOCKET)
{
cout << "accept error !" << WSAGetLastError() << endl;
closesocket(listenSocket);
WSACleanup();
return 0;
}
cout << "用户登录地址:" << inet_ntoa(remoteAddr.sin_addr) << endl;
//可增加多线程接收多个客户端数据
CreateThread(NULL, 0, ThreadFunc, (LPVOID)sClient, 0, NULL);
}
(6)关闭客户端连接
While(true)
{
//循环接收客户端数据
......
closesocket(listenSocket);
}
(7)释放套接字资源
while(true)
{
....
}
WSACleanup();
return 0;
2.客户端程序
(1)初始化WSA
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;//WSA(Windows Sockets Asynchronous)异步套接字
if (WSAStartup(sockVersion, &wsaData) != 0)
return;
(2)创建客户端Socket
m_clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_clientSocket == INVALID_SOCKET)
{
MessageBox("Invalid Socket!", "错误", MB_ICONERROR);
return;
}
(3)请求连接
//建立一个客户端地址
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.1.0.1");
//客户端向服务端请求连接
if (connect(m_clientSocket, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
MessageBox("Connect Error!!", "错误", MB_ICONERROR);
closesocket(m_clientSocket);
return;
}
(4)客户端发送数据
//与服务端连接成功,开始发送消息
char sendToServerBuff[1024] = "服务端,我来啦~";
send(m_clientSocket,*(&sendToServerBuff), sizeof(sendToServerBuff), 0);
(5)接收服务端数据
//接收服务端返回的消息
char buffFromServer[1024];
int recvDataLen = recv(m_clientSocket, buffFromServer, sizeof(buffFromServer), 0);
if (recvDataLen > 0)
{
MessageBox(buffFromServer);
}
(6)断开连接
closesocket(m_clientSocket);
(7)释放套接字资源
WSACleanup();