C/C++编程 零基础实现TCP协议实现网络通讯

文章目录

  • 一、TCP服务器
    • 1. 创建TCP服务器需要包含头文件WinSock2.h
    • 2. windows上使用网络功能需要开始网络权限
    • 3. 创建TCP服务端流程
      • 1.创建监听socket套接字
      • 2.给这个socket绑定一个端口号
      • 3.给这个socket开启监听属性
      • 4.等待客户端连接
      • 5.开始通讯
      • 6.关闭连接
    • 4.建立循环
  • 二、TCP客户端
    • 1.创建TCP客户端流程
      • 1. 创建socket套接字
      • 2. 连接服务器
      • 3. 开始通讯
      • 4. 关闭连接
    • 2.TCP客户端总代码
  • 三、多客户端访问
    • 1.使用多线程方式对服务器进行多客户端访问
    • 2.服务器完整代码


一、TCP服务器

1. 创建TCP服务器需要包含头文件WinSock2.h

#include

以及包含

#pragma comment(lib, "ws2_32.lib")
  1. #pragma comment(lib, "ws2_32.lib") 告诉 MSVC 编译器,在链接时包含ws2_32.lib 库。
  2. ws2_32.lib 是 Windows Sockets(Winsock2)库,提供了用于网络通信的函数和数据结构支持,例如:TCP/IP 协议和 UDP 协议。
  3. 通过指定ws2_32.lib,程序可以使用 Windows 的套接字函数,如socket、bind、listen、accept、send、recv 等。

2. windows上使用网络功能需要开始网络权限

WSADATA wasData;
WSAStartup(MAKEWORD(2, 2), &wasData);

3. 创建TCP服务端流程

1.创建监听socket套接字

listen_sock,socket函数创建套接字失败时,返回INVALID_SOCKET(值为(SOCKET)(~0)

		/*SOCKET socket(
			int af,			//协议地址簇	IPV4/IPV6	AF_INET/AF_INET6
			int type,		//类型	流式协议  帧式协议  SOCK_STREAM/SOCK_DGRAM
			int protocol	//保护协议  0
		);*/
		SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
		//如果创建失败时,打印错误值方便查询错误
		if (INVALID_SOCKET == listen_sock)
		{
			printf("create listen socket failed ! ! ! errcode: %d\n", GetLastError());
			return -1;
		}

2.给这个socket绑定一个端口号

/*struct sockaddr_in {
	ADDRESS_FAMILY sin_family;	//协议地址簇
	USHORT sin_port;			//端口号
	IN_ADDR sin_addr;			//IP地址
	CHAR sin_zero[8];			//保留字节
} ;*/
struct sockaddr_in local = { 0 };
local.sin_family = AF_INET;
//htons的功能:将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian)
//htonl()(Host to Network Long)函数用于将一个32位整数从主机字节顺序转换为网络字节顺序。
local.sin_port = htons(8080); 	//大小端问题	中间设备使用的是大端序(路由器)
//服务端  选项  网卡  127.0.0.1(本地环回)只接受哪个网卡的数据  一般写全0地址表示全都接收
//local.sin_addr.s_addr = htonl(INADDR_ANY);	//默认IP地址
local.sin_addr.s_addr = inet_addr("0.0.0.0");	//将字符串IP地址转换成整数IP

将端口号与listen_sock 绑定bind(listen_sock, (struct sockaddr*)&local, sizeof(local))

/*int bind(
		SOCKET s,
		const struct sockaddr * name,
		int namelen
);*/

/*struct sockaddr {
	ADDRESS_FAMILY sa_family;
	CHAR sa_data[14];
};*/
if (-1 == bind(listen_sock, (struct sockaddr*)&local, sizeof(local))) {
	printf("bind socket failed ! ! ! errcode: %d\n", GetLastError());
	return -1;
}

3.给这个socket开启监听属性

/*int listen(
		SOCKET s,
		int backlog
);*/
if (-1 == listen(listen_sock, 10)) {
	printf("start listen socket failed ! ! ! errcode: %d\n", GetLastError());
	return -1;
}

4.等待客户端连接

/*SOCKET accept(
		SOCKET s,					//监听socket
		struct sockaddr * addr,		//返回类型参数  返回的是 客户端的IP地址和端口号
		int * addrlen				//结构的大小
);*/
SOCKET client_socket = accept(listen_sock, NULL, NULL);

5.开始通讯

/*int recv(
			SOCKET s,	//客户端socket
			char* buf,	//接受的数据存到哪里
			int len,	//接受的长度
			int flags	//0
		);*/
int ret = recv(client_socket, buffer, 1024, 0);

6.关闭连接

closesocket(client_socket);

4.建立循环

将步骤4,5,6放入循环,即若客户端连接失败,则继续下一次连接

while (1)
{
	SOCKET client_socket = accept(listen_sock, NULL, NULL);
	if (INVALID_SOCKET == client_socket)
		continue;
	//将通讯放入循环,则客户端可多次与服务器进行通讯
	while (1)
	{
		//	5.开始通讯(B/S)
		char buffer[1024] = { 0 };
		/*int recv(
				SOCKET s,	//客户端socket
				char* buf,	//接受的数据存到哪里
				int len,	//接受的长度
				int flags	//0
		);*/
		//设置回显服务器,将服务器收到的消息再次返回,同时打印收到的消息
		int ret = recv(client_socket, buffer, 1024, 0);
		if (ret <= 0)break;
		printf("%llu : %s\n", client_socket, buffer);
		
		send(client_socket, buffer, (int)strlen(buffer), 0);
	}
	//	6.关闭连接
	closesocket(client_socket);
}

二、TCP客户端

1.创建TCP客户端流程

1. 创建socket套接字

SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == client_socket)
{
	printf("create socket failed ! ! !\n");
	return -1;

}

2. 连接服务器

struct sockaddr_in target;
target.sin_family = AF_INET;
target.sin_port = htons(8080);
target.sin_addr.s_addr = inet_addr("127.0.0.1");

if (-1 == connect(client_socket, (struct sockaddr*)&target, sizeof(target))) {
	printf("connect server failed ! ! !\n");
	closesocket(client_socket);
	return -1;
}

3. 开始通讯

while (1) {
	char sbuffer[1024] = { 0 };
	printf("please enter: ");
	scanf("%s", sbuffer);

	send(client_socket, sbuffer, (int)strlen(sbuffer), 0);

	char rbuffer[1024] = { 0 };
	int ret = recv(client_socket, rbuffer, 1024, 0);
	if (ret <= 0)break;
	printf("%s\n", rbuffer);
}

4. 关闭连接

closesocket(client_socket);

2.TCP客户端总代码

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include
#include
#pragma comment(lib, "ws2_32.lib")

int main()
{
	WSADATA wasData;
	WSAStartup(MAKEWORD(2, 2), &wasData);

	//1.创建socket套接字
	SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == client_socket)
	{
		printf("create socket failed ! ! !\n");
		return -1;
	}

	//2.连接服务器
	struct sockaddr_in target;
	target.sin_family = AF_INET;
	target.sin_port = htons(8080);
	target.sin_addr.s_addr = inet_addr("127.0.0.1");

	if (-1 == connect(client_socket, (struct sockaddr*)&target, sizeof(target))) {
		printf("connect server failed ! ! !\n");
		closesocket(client_socket);
		return -1;
	}

	//3.开始通讯 send recv 回显服务器
	while (1) {
		char sbuffer[1024] = { 0 };
		printf("please enter: ");
		scanf("%s", sbuffer);

		send(client_socket, sbuffer, (int)strlen(sbuffer), 0);

		char rbuffer[1024] = { 0 };
		int ret = recv(client_socket, rbuffer, 1024, 0);
		if (ret <= 0)break;
		printf("%s\n", rbuffer);
	}

	//4.关闭连接
	closesocket(client_socket);

	return 0;
}

三、多客户端访问

1.使用多线程方式对服务器进行多客户端访问

上述方式仅能实现单客户端对服务器进行访问,实现客户端对服务器进行访问时,常采用IO多路复用的方式,但目前学习阶段,本人能力有限,只能使用多线程方法。
建立一个函数指针,将client_socket传入

DWORD WINAPI thread_func(LPVOID lpThreadParameter)
{
	SOCKET client_socket = *(SOCKET*)lpThreadParameter;
	free(lpThreadParameter);
	while (1)
	{
		//	5.开始通讯(B/S)
		char buffer[1024] = { 0 };

		/*int recv(
				SOCKET s,	//客户端socket
				char* buf,	//接受的数据存到哪里
				int len,	//接受的长度
				int flags	//0
		);*/
		int ret = recv(client_socket, buffer, 1024, 0);
		if (ret <= 0)break;
		printf("%llu : %s\n", client_socket, buffer);

		send(client_socket, buffer, (int)strlen(buffer), 0);
	}

	printf("socket: %llu, disconnect.\n", client_socket);

	//	6.关闭连接
	closesocket(client_socket);
	return 0;
}

再创建线程,使用函数指针传输参数时,先使用malloc重新创建监听socket,避免进程运行时,client_socket还未被调用就被释放掉的情况。

SOCKET* sockfd = (SOCKET*)malloc(sizeof(SOCKET));
*sockfd = client_socket;

//创建线程
CreateThread(NULL, 0, thread_func, sockfd, 0, NULL);

2.服务器完整代码

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include
#include
#include
#pragma comment(lib, "ws2_32.lib")

DWORD WINAPI thread_func(LPVOID lpThreadParameter)
{
	SOCKET client_socket = *(SOCKET*)lpThreadParameter;
	free(lpThreadParameter);
	while (1)
	{
		//	5.开始通讯(B/S)
		char buffer[1024] = { 0 };

		/*int recv(
				SOCKET s,	//客户端socket
				char* buf,	//接受的数据存到哪里
				int len,	//接受的长度
				int flags	//0
		);*/
		int ret = recv(client_socket, buffer, 1024, 0);
		if (ret <= 0)break;
		printf("%llu : %s\n", client_socket, buffer);

		send(client_socket, buffer, (int)strlen(buffer), 0);
	}

	printf("socket: %llu, disconnect.\n", client_socket);

	//	6.关闭连接
	closesocket(client_socket);
	return 0;
}

int main()
{
	//windows上使用网络功能需要开始网络权限
	WSADATA wasData;
	WSAStartup(MAKEWORD(2, 2), &wasData);

	//服务端流程
	//	1.创建socket套接字
	/*SOCKET socket(
			int af,			//协议地址簇	IPV4/IPV6	AF_INET/AF_INET6
			int type,		//类型	流式协议  帧式协议  SOCK_STREAM/SOCK_DGRAM
			int protocol	//保护协议  0
		);*/

	SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	//printf("%d\n", (int)listen_sock);
	if (INVALID_SOCKET == listen_sock)
	{
		printf("create listen socket failed ! ! ! errcode: %d\n", GetLastError());
		return -1;
	}

	//	2.给这个socket绑定一个端口号
	/*struct sockaddr_in {
		ADDRESS_FAMILY sin_family;	//协议地址簇
		USHORT sin_port;			//端口号
		IN_ADDR sin_addr;			//IP地址
		CHAR sin_zero[8];			//保留字节
	} ;*/

	struct sockaddr_in local = { 0 };
	local.sin_family = AF_INET;
	local.sin_port = htons(8080);	//大小端问题	中间设备使用的是大端序(路由器)
	
	//服务端  选项  网卡  127.0.0.1(本地环回)只接受哪个网卡的数据  一般写全0地址表示全都接收
	//local.sin_addr.s_addr = htonl(INADDR_ANY);
	local.sin_addr.s_addr = inet_addr("0.0.0.0");	//将字符串IP地址转换成整数IP
	
	/*int bind(
			SOCKET s,
			const struct sockaddr * name,
			int namelen
	);*/

	/*struct sockaddr {
		ADDRESS_FAMILY sa_family;
		CHAR sa_data[14];
	};*/

	if (-1 == bind(listen_sock, (struct sockaddr*)&local, sizeof(local))) {
		printf("bind socket failed ! ! ! errcode: %d\n", GetLastError());
		return -1;
	}
	
	//	3.给这个socket开启监听属性
	/*int listen(
			SOCKET s,
			int backlog
	);*/
	if (-1 == listen(listen_sock, 10)) {
		printf("start listen socket failed ! ! ! errcode: %d\n", GetLastError());
		return -1;
	}
	
	//	4.等待客户端连接
	/*SOCKET accept(
			SOCKET s,					//监听socket
			struct sockaddr * addr,		//返回类型参数  返回的是 客户端的IP地址和端口号
			int * addrlen				//结构的大小
	);*/

	while (1)
	{
		SOCKET client_socket = accept(listen_sock, NULL, NULL);
		if (INVALID_SOCKET == client_socket)
			continue;

		printf("new connect. socket: %llu\n", client_socket);

		SOCKET* sockfd = (SOCKET*)malloc(sizeof(SOCKET));
		*sockfd = client_socket;

		//创建线程
		CreateThread(NULL, 0, thread_func, sockfd, 0, NULL);


		//while (1)
		//{
		//	//	5.开始通讯(B/S)
		//	char buffer[1024] = { 0 };

		//	/*int recv(
		//			SOCKET s,	//客户端socket
		//			char* buf,	//接受的数据存到哪里
		//			int len,	//接受的长度
		//			int flags	//0
		//	);*/
		//	int ret = recv(client_socket, buffer, 1024, 0);
		//	if (ret <= 0)break;
		//	printf("%llu : %s\n", client_socket, buffer);

		//	send(client_socket, buffer, (int)strlen(buffer), 0);
		//}
			6.关闭连接
		//closesocket(client_socket);
	}
}

你可能感兴趣的:(c语言,c++,tcp/ip)