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

news/2025/2/6 13:56:36/

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

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

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


网络编程的基本概念

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位,只适用于IPV4unsigned 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 <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>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 <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>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,实现了客户端/服务端的基本通信框架。


http://www.ppmy.cn/news/1067244.html

相关文章

Python数据分析与数据挖掘:解析数据的力量

引言&#xff1a; 随着大数据时代的到来&#xff0c;数据分析和数据挖掘已经成为许多行业中不可或缺的一部分。在这个信息爆炸的时代&#xff0c;如何从大量的数据中提取有价值的信息&#xff0c;成为了企业和个人追求的目标。而Python作为一种强大的编程语言&#xff0c;提供…

解决Android Studio中Plugin version和Gradle version不匹配的问题

生命中最艰难的那段路是要自己一个人走过来的&#xff0c;这样&#xff0c;学到更多的是坚强&#xff0c;而不是感动。 《红猪》 前言 导入一个百度云的Demo而已&#xff0c;居然遇到这么多问题&#xff0c;纠结了很久&#xff0c;也查了很多资料&#xff0c;弯弯绕绕了好多路…

跨足多领域:人脸美颜SDK在医疗、娱乐和安全中的应用案例

随着科技的不断发展&#xff0c;人脸美颜技术不再局限于满足用户的审美需求&#xff0c;而是在医疗、娱乐和安全领域展现出了广泛的应用前景。本文将深入探讨人脸美颜SDK 在这三个领域中的创新应用案例&#xff0c;展示其在不同场景中的独特价值和潜力。 一、医疗领域 1、皮…

nginx-获取客户端IP地址

上有服务器与客户端中间是有nginx代理服务器的&#xff0c;上游服务器如何获取客户端真实ip地址&#xff1f; nginx代理服务器设置X-Forwarded-For的header参数&#xff0c;代理服务器通过remote_addr获取客户端ip地址&#xff0c;将ip地址写入nginx代理服务器的X-Forwarded-Fo…

002微信小程序云开发API数据库-迁移状态查询/更新索引

文章目录 微信小程序云开发API数据库-迁移状态查询案例代码微信小程序云开发API数据库-更新索引案例代码 微信小程序云开发API数据库-迁移状态查询 在微信小程序中&#xff0c;云开发API数据库是一种方便快捷的数据库解决方案。但是&#xff0c;有时候我们可能需要将云开发数据…

java八股文面试[多线程]——有几种创建线程的方式

this逃逸问题&#xff1a;构造器中启动线程。 面试题&#xff1a; 用Thread和Runable创建线程的差别 一、Runnable和Thread的区别 继承性&#xff1a;Thread是一个类&#xff0c;因此如果继承Thread类&#xff0c;子类就不能再继承其他的类了&#xff0c;而实现Runnable接口…

github加速方式

windows系统访问github加速方式 第一步&#xff1a;查询dns地址第二步&#xff1a;host文件添加dns映射第三步&#xff1a;刷新dns 第一步&#xff1a;查询dns地址 查询工具&#xff1a;https://coding.tools/cn/nslookup 依次将以下网址的ip查询出来&#xff1a; github.com …

探究finally代码块是否执行

情况一&#xff1a;try代码块正常执行&#xff0c;无异常&#xff0c;finally代码块无retrun&#xff1b; 代码演示 public class Test38 {public static void main(String[] args) {int foo foo();System.out.println("foo:" foo);}public static int foo() {tr…