C++学习笔记(51)

三、IP 地址和通讯端口

在计算机中,IPv4 的地址用 4 字节的整数存放,通讯端口用 2 字节的整数(0-65535)存放。

例如:192.168.190.134 3232284294 255.255.255.255

192 168 190 134

大端:11000000 10101000 10111110 10000110

小端:10000110 10111110 10101000 11000000

四、如何处理大小端序

在网络编程中,数据收发的时候有自动转换机制,不需要程序员手动转换,只有向 sockaddr_in 结

体成员变量填充数据时,才需要考虑字节序的问题。

344、万恶的结构体

一、sockaddr 结构体

存放协议族、端口和地址信息,客户端和 connect()函数和服务端的 bind()函数需要这个结构体。

struct sockaddr {

unsigned short sa_family; // 协议族,与 socket()函数的第一个参数相同,填 AF_INET。

unsigned char sa_data[14]; // 14 字节的端口和地址。

};

二、sockaddr_in 结构体

sockaddr 结构体是为了统一地址结构的表示方法,统一接口函数,但是,操作不方便,所以定义了

等价的 sockaddr_in 结构体,它的大小与 sockaddr 相同,可以强制转换成 sockaddr。

struct sockaddr_in {

unsigned short sin_family; // 协议族,与 socket()函数的第一个参数相同,填 AF_INET。

unsigned short sin_port; // 16 位端口号,大端序。用 htons(整数的端口)转换。

struct in_addr sin_addr; // IP 地址的结构体。192.168.101.138

unsigned char sin_zero[8]; // 未使用,为了保持与 struct sockaddr 一样的长度而添加。

};

struct in_addr { // IP 地址的结构体。

unsigned int s_addr; // 32 位的 IP 地址,大端序。

};

三、gethostbyname 函数

根据域名/主机名/字符串 IP 获取大端序 IP,用于网络通讯的客户端程序中。

struct hostent *gethostbyname(const char *name);

struct hostent {

char *h_name; // 主机名。

char **h_aliases; // 主机所有别名构成的字符串数组,同一 IP 可绑定多个域名。

short h_addrtype; // 主机 IP 地址的类型,例如 IPV4(AF_INET)还是 IPV6。

short h_length; // 主机 IP 地址长度,IPV4 地址为 4,IPV6 地址则为 16。

char **h_addr_list; // 主机的 ip 地址,以网络字节序存储。

};

#define h_addr h_addr_list[0] // for backward compatibility. 转换后,用以下代码把大端序的地址复制到 sockaddr_in 结构体的 sin_addr 成员中。

memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);

四、字符串 IP 与大端序 IP 的转换

C 语言提供了几个库函数,用于字符串格式的 IP 和大端序 IP 的互相转换,用于网络通讯的服务端程

序中。

typedef unsigned int in_addr_t; // 32 位大端序的 IP 地址。

// 把字符串格式的 IP 转换成大端序的 IP,转换后的 IP 赋给 sockaddr_in.in_addr.s_addr。

in_addr_t inet_addr(const char *cp);

// 把字符串格式的 IP 转换成大端序的 IP,转换后的 IP 将填充到 sockaddr_in.in_addr 成员。

int inet_aton(const char *cp, struct in_addr *inp);

// 把大端序 IP 转换成字符串格式的 IP,用于在服务端程序中解析客户端的 IP 地址。

char *inet_ntoa(struct in_addr in);

五、demo5.cpp

/*

* 程序名:demo5.cpp,此程序用于演示 socket 的客户端

*/

#include <iostream>

#include <cstdio>

#include <cstring>

#include <cstdlib>

#include <unistd.h>

#include <netdb.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

using namespace std;

int main(int argc,char *argv[])

{

if (argc!=3)

{

cout << "Using:./demo5 服务端的 IP 服务端的端口\nExample:./demo5 192.168.101.138

5005\n\n";

return -1;

}

// 第 1 步:创建客户端的 socket。

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

if (sockfd==-1)

{

perror("socket"); return -1;

}

// 第 2 步:向服务器发起连接请求。

struct sockaddr_in servaddr; // 用于存放协议、端口和 IP 地址的结构体。

memset(&servaddr,0,sizeof(servaddr));

servaddr.sin_family = AF_INET; // ①协议族,固定填 AF_INET。

servaddr.sin_port = htons(atoi(argv[2])); // ②指定服务端的通信端口。

struct hostent* h; // 用于存放服务端 IP 地址(大端序)的结构体的指

针。

if ( (h = gethostbyname(argv[1])) == nullptr ) // 把域名/主机名/字符串格式的 IP 转换成结构

体。

{

cout << "gethostbyname failed.\n" << endl; close(sockfd); return -1;

}

memcpy(&servaddr.sin_addr,h->h_addr,h->h_length); // ③指定服务端的 IP(大端序)。

//servaddr.sin_addr.s_addr=inet_addr(argv[1]); // ③指定服务端的 IP,只能用 IP,不能用域名

和主机名。

if (connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1) // 向服务端发起

连接清求。

{

perror("connect"); close(sockfd); return -1;

}

// 第 3 步:与服务端通讯,客户发送一个请求报文后等待服务端的回复,收到回复后,再发下一

个请求报文。

char buffer[1024];

for (int ii=0;ii<10;ii++) // 循环 3 次,将与服务端进行三次通讯。

{

int iret;

memset(buffer,0,sizeof(buffer));

sprintf(buffer,"这是第%d 个超级女生,编号%03d。",ii+1,ii+1); // 生成请求报文内容。

// 向服务端发送请求报文。

if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0)

{

perror("send"); break;

}

cout << "发送:" << buffer << endl;

memset(buffer,0,sizeof(buffer));

// 接收服务端的回应报文,如果服务端没有发送回应报文,recv()函数将阻塞等待。

if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0)

{

cout << "iret=" << iret << endl; break;

}

cout << "接收:" << buffer << endl;

sleep(1);

}

// 第 4 步:关闭 socket,释放资源。

close(sockfd);

}

六、demo5.cpp

/*

* 程序名:demo6.cpp,此程序用于演示 socket 通信的服务端

*/

#include <iostream>

#include <cstdio>

#include <cstring>

#include <cstdlib>

#include <unistd.h>

#include <netdb.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

using namespace std;

int main(int argc,char *argv[])

{

if (argc!=2)

{

cout << "Using:./demo6 通讯端口\nExample:./demo6 5005\n\n"; // 端口大于 1024,

不与其它的重复。

cout << "注意:运行服务端程序的 Linux 系统的防火墙必须要开通 5005 端口。\n";

cout << " 如果是云服务器,还要开通云平台的访问策略。\n\n";

return -1;

}

// 第 1 步:创建服务端的 socket。

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

if (listenfd==-1)

{

perror("socket"); return -1;

}

// 第 2 步:把服务端用于通信的 IP 和端口绑定到 socket 上。

struct sockaddr_in servaddr; // 用于存放协议、端口和 IP 地址的结构体。

memset(&servaddr,0,sizeof(servaddr));

servaddr.sin_family=AF_INET; // ①协议族,固定填 AF_INET。

servaddr.sin_port=htons(atoi(argv[1])); // ②指定服务端的通信端口。

servaddr.sin_addr.s_addr=htonl(INADDR_ANY); // ③如果操作系统有多个 IP,全部的 IP 都可

以用于通讯。

//servaddr.sin_addr.s_addr=inet_addr("192.168.101.138"); // ③指定服务端用于通讯的 IP(大

端序)。

// 绑定服务端的 IP 和端口。

if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1)

{

perror("bind"); close(listenfd); return -1;

}

// 第 3 步:把 socket 设置为可连接(监听)的状态。

if (listen(listenfd,5) == -1 )

{

perror("listen"); close(listenfd); return -1;

}

// 第 4 步:受理客户端的连接请求,如果没有客户端连上来,accept()函数将阻塞等待。

int clientfd=accept(listenfd,0,0);

if (clientfd==-1)

{

perror("accept"); close(listenfd); return -1;

}

cout << "客户端已连接。\n";

// 第 5 步:与客户端通信,接收客户端发过来的报文后,回复 ok。

char buffer[1024];

while (true)

{

int iret;

memset(buffer,0,sizeof(buffer));

// 接收客户端的请求报文,如果客户端没有发送请求报文,recv()函数将阻塞等待。

// 如果客户端已断开连接,recv()函数将返回 0。

if ( (iret=recv(clientfd,buffer,sizeof(buffer),0))<=0)

{

cout << "iret=" << iret << endl; break;

}

cout << "接收:" << buffer << endl;

strcpy(buffer,"ok"); // 生成回应报文内容。

// 向客户端发送回应报文。

if ( (iret=send(clientfd,buffer,strlen(buffer),0))<=0)

{

perror("send"); break;

}

cout << "发送:" << buffer << endl;

}

// 第 6 步:关闭 socket,释放资源。

close(listenfd); // 关闭服务端用于监听的 socket。

close(clientfd); // 关闭客户端连上来的 socket。

}

相关推荐
zmd-zk几秒前
flink学习(3)——方法的使用—对流的处理(map,flatMap,filter)
java·大数据·开发语言·学习·flink·tensorflow
daily_23332 分钟前
数据结构——小小二叉树第三幕(链式结构的小拓展,二叉树的创建,深入理解二叉树的遍历)超详细!!!
数据结构·c++·算法
laimaxgg26 分钟前
C++特殊类设计(不能被拷贝的类、只能在堆上创建对象的类、不能被继承的类、单例模式)
c++·单例模式
SUN_Gyq35 分钟前
什么是 C++ 中的模板特化和偏特化? 如何进行模板特化和偏特化?
开发语言·c++·算法
愿天垂怜1 小时前
【C++】C++11引入的新特性(1)
java·c语言·数据结构·c++·算法·rust·哈希算法
垂杨有暮鸦⊙_⊙1 小时前
阅读《先进引信技术的发展与展望》识别和控制部分_笔记
笔记·学习
大帅哥_1 小时前
访问限定符
c语言·c++
特种加菲猫1 小时前
初阶数据结构之栈的实现
开发语言·数据结构·笔记
小林熬夜学编程1 小时前
【Linux系统编程】第五十弹---构建高效单例模式线程池、详解线程安全与可重入性、解析死锁与避免策略,以及STL与智能指针的线程安全性探究
linux·运维·服务器·c语言·c++·安全·单例模式
明明真系叻1 小时前
第二十二周机器学习笔记:动手深度学习之——线性代数
笔记·深度学习·线性代数·机器学习·1024程序员节