C++——TCP的socket通信

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.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机制等功能后续完善,有问题,欢迎指出,一起学习!

发布了84 篇原创文章 · 获赞 2 · 访问量 5244

猜你喜欢

转载自blog.csdn.net/finghting321/article/details/105050584
今日推荐