1.TCP流程
服务端:
初始化Winsock--->创建socket--->bind--->listen--->accept--->接收发送--->关闭socket
客户端:
初始化Winsock--->创建socket--->connect--->接收发送--->关闭socket
TCP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt(); * 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、开启监听,用函数listen();
5、接收客户端上来的连接,用函数accept();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
8、关闭监听;
TCP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置要连接的对方的IP地址和端口等属性;
5、连接服务器,用函数connect();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
注* TIME_WAIT 状态最大保持时间是2 * MSL,也就是1-4分钟(MSL是最大分段生存期,指明TCP报文在Internet上最长生存时间)
2. 为什么TCP是可靠的?
1) 重传机制(序列号与确认号)
a、超时重传机制 b、快速重传机制
2) 流量控制(滑动窗口)
a、停等协议 b、后退n步协议 c、选择重传
3) 拥塞控制
a、慢启动 b、拥塞避免
c、拥塞解决 d、快速恢复
3. 举例
TCP.h
#pragma once
#include <thread>
#include <Winsock2.h> //windows socket的头文件
// 服务端
class CTCPServer
{
public:
CTCPServer() {}
~CTCPServer() {}
// TCP服务端
long TCPServer();
// 接收数据线程
long RecvData();
private:
SOCKET sockConn;
char recvBuf[100];
};
// 客户端
class CTCPClient
{
public:
CTCPClient() {}
~CTCPClient() {}
// TCP客户端
long TCPClient();
};
TCP.cpp
#include "stdafx.h"
#include "TCP.h"
#include <iostream>
#include <ios>
#include <string>
#pragma comment( lib, "ws2_32.lib" )// 链接Winsock2.h的静态库文件
// TCP服务端
long CTCPServer::TCPServer()
{
//初始化winsocket
WORD wVersionRequested;
WSADATA wsaData;
int nPort = 8888; //端口号
//第一个参数为低位字节;第二个参数为高位字节
wVersionRequested = MAKEWORD(1, 1);
//对winsock DLL(动态链接库文件)进行初始化,协商Winsock的版本支持,并分配必要的资源。
int err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return -1;
}
//LOBYTE()取得16进制数最低位;HIBYTE()取得16进制数最高(最左边)那个字节的内容
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -1;
}
//1.创建一个socket。 AF_INET表示在Internet中通信;SOCK_STREAM表示socket是流套接字,对应tcp;0指定网络协议为TCP/IP
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
// 设定目标地址
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htons(INADDR_ANY); //htonl用来将主机字节顺序转换为网络字节顺序(to network long) INADDR_ANY就是指定地址为0.0.0.0的地址
addrSrv.sin_family = AF_INET; //协议簇,一般用AF_INET表示TCP/IP协议
addrSrv.sin_port = htons(nPort); //htons用来将主机字节顺序转换为网络字节顺序(to network short)
//2.设置socket属性,用函数setsockopt(); 可选 设置recv超时
//int nNetTimeout = 1000;//1秒
//err = setsockopt(sockSrv, SOL_SOCKET, SO_RCVTIMEO, (const char*)&nNetTimeout, sizeof(nNetTimeout)); //接收时限
//err = setsockopt(sockSrv, SOL_SOCKET, SO_SNDTIMEO, (const char*)&nNetTimeout, sizeof(nNetTimeout)); //发送时限
//TIME_WAIT 状态最大保持时间是2 * MSL,也就是1-4分钟(MSL是最大分段生存期,指明TCP报文在Internet上最长生存时间),bind()之前添加setsockopt()函数,解除端口绑定
//如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket
BOOL bReuseaddr = TRUE;
setsockopt(sockSrv, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(BOOL));
//如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程
BOOL bDontLinger = FALSE;
setsockopt(sockSrv, SOL_SOCKET, SO_DONTLINGER, (const char*)&bDontLinger, sizeof(BOOL));
//3.绑定IP地址、端口等信息到socket上,用函数bind()
err = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
if (err != 0)
{
printf("tcp server bind failed\n");
return -1;
}
printf("tcp server bind success\n");
//4.开启监听,用函数listen()
err = listen(sockSrv, 10);
if (err != 0)
{
printf("tcp server listen failed\n");
return -1;
}
printf("tcp server listen success\n");
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while (true)
{
//5.接收客户端上来的连接,用函数accept()
sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len); //为一个连接请求提供服务。addrClient包含了发出连接请求的客户机IP地址信息;返回的新socket描述服务器与该客户机的连接
if (sockConn == SOCKET_ERROR) {
printf("tcp server accept client connect failed :%d\n", WSAGetLastError());
break;
}
printf("tcp server accept client ip is:[%s]\n", inet_ntoa(addrClient.sin_addr));
//6.收发数据,用函数send()和recv(),或者read()和write()
char sendBuf[50];
sprintf(sendBuf, "Welcome %s to here!", inet_ntoa(addrClient.sin_addr)); //inet_ntoa网络地址转换转点分十进制的字符串指针
int iSend = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
if (iSend == SOCKET_ERROR) {
printf("tcp server send data failed, data:%s\n", sendBuf);
break;
}
//接收数据,线程接收数据
std::thread th1(std::bind(&CTCPServer::RecvData, this));
th1.detach();
}
//7.关闭网络连接 关闭监听
closesocket(sockConn);
WSACleanup();
return 0;
}
// / 接收数据线程
long CTCPServer::RecvData()
{
while (true) {
memset(recvBuf, 0, sizeof(recvBuf));
int nRes = recv(sockConn, recvBuf, sizeof(recvBuf), 0);
if (nRes <= 0) //检测客户端退出
{
printf("client connect close, please check....\n");
break;
}
printf("data:%s,size:%d\n", recvBuf, nRes);
}
return 0;
}
// TCP客户端
long CTCPClient::TCPClient()
{
//初始化winsocket
WORD wVersionRequested;
WSADATA wsaData;
int nPort = 8888; //端口号
wVersionRequested = MAKEWORD(1, 1); //第一个参数为低位字节;第二个参数为高位字节
//对winsock DLL(动态链接库文件)进行初始化,协商Winsock的版本支持,并分配必要的资源。
int err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return -1;
}
//LOBYTE()取得16进制数最低位;HIBYTE()取得16进制数最高(最左边)那个字节的内容
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -1;
}
//1、创建一个socket,用函数socket()
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv; //需要包含服务端IP信息
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // inet_addr将IP地址从点数格式转换成网络字节格式整型。
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(nPort);
//2.设置socket属性,用函数setsockopt(); 可选 设置recv超时
//int nNetTimeout = 1000;//1秒
//err = setsockopt(sockSrv, SOL_SOCKET, SO_RCVTIMEO, (const char*)&nNetTimeout, sizeof(nNetTimeout)); //接收时限
//err = setsockopt(sockSrv, SOL_SOCKET, SO_SNDTIMEO, (const char*)&nNetTimeout, sizeof(nNetTimeout)); //发送时限
//3、连接服务器,客户机向服务器发出连接请求
int iSend = connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(addrSrv));
if (iSend == SOCKET_ERROR) {
printf("tcp client connect server failed: %d\n", WSAGetLastError());
return -1;
}
//4、收发数据,用函数send()和recv(),或者read()和write()
char recvBuf[50];
recv(sockClient, recvBuf, 50, 0);
printf("tcp client recv server data: %s\n", recvBuf);
//发送数据
char szBuf[100];
sprintf(szBuf, "Welcome %s to client!", inet_ntoa(addrSrv.sin_addr)); //inet_ntoa网络地址转换转点分十进制的字符串指针
send(sockClient, szBuf, strlen(szBuf), 0);
char buffs[100];
printf("cilent to server sent data, please input:\n");
//不断输入,然后发送
while (true)
{
std::cin.getline(buffs,100);
if (0 == std::strcmp(buffs, "q"))
{
printf("tcp client quit\n");
break;
}
int nLen = send(sockClient, buffs, strlen(buffs) + 1, 0);
if (nLen <=0) //检测服务端,后续补充断线重连机制
{
printf("tcp server connect close, please check...\n");
break;
}
}
//5、关闭网络连接
closesocket(sockClient);
printf("tcp cilent close\n");
WSACleanup();
return 0;
}
main 调用:
int main()
{
//测试TCP服务端
/*std::shared_ptr<CTCPServer> pTCPServer = std::make_shared<CTCPServer>();
pTCPServer->TCPServer();*/
//测试TCP客户端
std::shared_ptr<CTCPClient> pTCPClient = std::make_shared<CTCPClient>();
pTCPClient->TCPClient();
system("pause");
return 0;
}
运行结果:
补充:断线重连机制、select机制等功能后续完善,有问题,欢迎指出,一起学习!