C++实现客户端/服务端通信(一)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

C++实现客户端/服务端通信(一)

  • 网络编程的基本概念
    • 1. 客户端/服务器通信模型:
    • 2. socket函数:
    • 3. 主机字节序和网络字节序:
    • 4. sockaddr 结构体
    • 5. sockaddr_in 结构体
    • 6. 字符串IP与大端序IP的转换
  • 实现简单的客户端
  • 实现简单的服务端
  • 总结


网络编程的基本概念

1. 客户端/服务器通信模型:

C++实现客户端/服务端通信(一)_第1张图片

2. socket函数:

int socket(int domain, int type, int protocol);
1)domain:通讯协议族
PF_INET: IPV4互联网协议族
PF_INET6: IPV6互联网协议族
PF_LOCAL: 本地通信协议族
PF_PACKET: 内核底层的协议族
PF_IPX: IPX Novell协议族
2)type:数据传输类型
SOCK_STREAM: 面向连接的socket
SOCK_DGRAM: 无连接的socket
3)protocol:最终使用的协议
在IPV4互联网协议族中,传输类型为SOCK_STREAM的协议只有IPPROTO_TCP,数据传输方式为SOCK_DGRAM的协议类型只有IPPROTO_UDP
可以填0(编译器自动识别)

3. 主机字节序和网络字节序:

多个字节组成的整数存放涉及到字节序。
大端序:低位字节存在高位,高位字节存在低位(如0X123456内存中存放为12 34 56)
小端序:低位字节存在低位,高位字节存在高位(如0X123456内存中存放为56 34 12),如Intel

为了解决不同字节序的计算机之间传输数据的问题,约定采用网络字节序(大端序)
主机字节序与网络字节序之间的转换:
uint16_t htons(uint16_t hostshort);
uint32_t htonl(uint32_t hostlong);
uint16_t ntohs(uint16_t netshort);
uint32_t ntohl(uint32_t netlong);

4. sockaddr 结构体

存放协议族、端口和地址信息

struct sockaddr {
    unsigned short sa_family;  // 协议族
    unsigned char sa_data[14]; // 14字节的端口和地址
};

5. sockaddr_in 结构体

sockaddr结构体为了统一地址结构的表示方法,统一接口函数,但是操作不方便,所以定义了等价的sockaddr_in结构体,其大小与sockaddr相同,可以强制转换为sockaddr

struct in_addr {
    unsigned int s_addr;       // IP地址,大端序
};

struct sockaddr_in {
    unsigned short sa_family;  // 协议族
    unsigned short sin_port;   // 端口号,大端序
    struct in_addr sin_addr;   // IP地址,32位,只适用于IPV4
    unsigned char sin_zero[8]; // 未使用,为了与sockaddr大小相同
};

之所以搞两个结构体,可能是因为sockaddr可以用于IPV4,后续也可以用于IPV6,sockaddr_in是为了IPV4操作方便(根据sin_addr存放的位数看)。

6. 字符串IP与大端序IP的转换

typedef unsigned int in_addr_t;  // 大端序IP地址

// 将字符串格式的IP转换为大端序IP
in_addr_t inet_addr(const char* cp);
int inet_aton(const char* cp, struct in_addr* inp);

// 将大端序IP转换为字符串格式IP
char *inet_ntoa(struct in_addr in);

实现简单的客户端

功能需求:能够与服务端建立连接,并发送、接收三次信息

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int main(int argc, char** argv)
{
	if (argc < 3) {
		cout << "Error: you should enter server ip and port" << endl;
		cout << "Usage: ./djclient [IP ADDR] [PORT]" << endl;
		return 0;
	}

	int client_socket = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	struct hostent* server_ip = gethostbyname(argv[1]);   // gethostname支持域名、主机名、字符串
	if (server_ip == nullptr) {
		cout << "gethostbyname failed " << argv[1] << endl;
		close(client_socket);
		return -1;
	}
	memcpy(&server_addr.sin_addr, server_ip->h_addr, server_ip->h_length);

	int ret = connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if (ret == -1) {
		cout << "connect failed." << endl;
		close(client_socket);
		return -1;
	}

	char buf[1024] = {0};

	// 接受发送消息3次
	for (int i = 0; i < 3; i++) {
		memset(buf, 0, sizeof(buf));
		sprintf(buf, "这是发送的第%d个消息.", i);
		ret = write(client_socket, buf, strlen(buf));
		if (ret == -1) {
			cout << "ERROR: 第 " << i << " 次发送信息失败" << endl;
			break;
		}
		cout << "INFO: 第 " << i << " 次发送了 " << ret << " 个字节,内容为:" << buf << endl;

		memset(buf, 0, sizeof(buf));
		ret = read(client_socket, buf, sizeof(buf) - 1);
		if (ret == -1) {
			cout << "ERROR: 第 " << i << " 次接收信息失败" << endl;
			break;
		}
        
        if (ret == 0) {
 
        }
		cout << "INFO: 第 " << i << " 次接收了 " << ret << " 个字节,内容为:" << buf << endl;
	}

	close(client_socket);

	cout << "通信结束!!!" << endl;
	return 0;
}

实现简单的服务端

功能需求:实现简单的服务端,接收客户端连接,打印接收和连接信息

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int main(int argc, char** argv)
{
	int server_socket = socket(AF_INET, SOCK_STREAM, 0);
	
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));

	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	server_addr.sin_port = htons(3560);

	int ret = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if (ret != 0) {
		cout << "ERROR: bind() 执行失败!!!" << endl;
		close(server_socket);
		return -1;
	}

	ret = listen(server_socket, 5);
	if (ret != 0) {
		cout << "ERROR: listen() 执行失败!!!" << endl;
		close(server_socket);
		return -1;
	}

	// int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); 
	struct sockaddr_in cliaddr;
	memset(&cliaddr, 0, sizeof(cliaddr));
	socklen_t addrlen = sizeof(cliaddr);

	cout << "等待连接..." << endl;
	int client_socket = accept(server_socket, (struct sockaddr*)&cliaddr, &addrlen);

	char* client_ip = inet_ntoa(cliaddr.sin_addr);

	cout << "INFO: 来自客户端的连接为:" << client_ip << ":"
		 << ntohs(cliaddr.sin_port) << endl;


	char buf[1024] = {0};
	int i = 0;
	while (true) {
		memset(buf, 0, sizeof(buf));
		ret = read(client_socket, buf, sizeof(buf) - 1);
		if (ret == -1) {
			cout << "ERROR: 第 " << i << " 次接收信息失败" << endl;
			break;
		}

		if (ret == 0) {
			cout << "INFO: 客户端断开连接" << endl;
			break;
		}
		cout << "INFO: 第 " << i << " 次接收了 " << ret << " 个字节,内容为:" << buf << endl;

		strcpy(buf, "success!!!");
		ret = write(client_socket, buf, strlen(buf));
		if (ret == -1) {
			cout << "ERROR: 第 " << i << " 次发送信息失败" << endl;
			break;
		}
		cout << "INFO: 第 " << i << " 次发送了 " << ret << " 个字节,内容为:" << buf << endl;
		
		i++;
	}

	close(client_socket);
	close(server_socket);
	return 0;
}

执行结果如下所示:

# 客户端
[root@localhost code]# g++ djclient.cpp -o djclient
[root@localhost code]# ./djclient 
Error: you should enter server ip and port
Usage: ./djclient [IP ADDR] [PORT]
[root@localhost code]# ./djclient 192.168.66.124 3560
INFO: 第 0 次发送了 29 个字节,内容为:这是发送的第0个消息.
INFO: 第 0 次接收了 10 个字节,内容为:success!!!
INFO: 第 1 次发送了 29 个字节,内容为:这是发送的第1个消息.
INFO: 第 1 次接收了 10 个字节,内容为:success!!!
INFO: 第 2 次发送了 29 个字节,内容为:这是发送的第2个消息.
INFO: 第 2 次接收了 10 个字节,内容为:success!!!
通信结束!!!
[root@localhost code]# 

# 服务端
[root@centos server]# g++ djserver.cpp -o djserver
[root@centos server]# ./djserver 
等待连接...
INFO: 来自客户端的连接为:192.168.91.153:14098
INFO: 第 0 次接收了 29 个字节,内容为:这是发送的第0个消息.
INFO: 第 0 次发送了 10 个字节,内容为:success!!!
INFO: 第 1 次接收了 29 个字节,内容为:这是发送的第1个消息.
INFO: 第 1 次发送了 10 个字节,内容为:success!!!
INFO: 第 2 次接收了 29 个字节,内容为:这是发送的第2个消息.
INFO: 第 2 次发送了 10 个字节,内容为:success!!!
INFO: 客户端断开连接
[root@centos server]# 

总结

C++实现客户端/服务端通信(一)基于socket通信的基本API,实现了客户端/服务端的基本通信框架。

你可能感兴趣的:(c++,开发语言)