1.结构体对齐
记得之前面试的时候被问过这个问题【汗】
这个结构体占多大
struct sExample { char c; int n; };
占8字节,问有没有办法让它占5个字节?
有
#pragma pack(push) //保存对齐状态 #pragma pack(1) //设定为1字节对齐 struct sExample { char c; int n; }; #pragma pack(pop) //恢复对齐状态
为什么要加保存和恢复对齐状态?为了不影响别人
#pragma pack(2) #pragma pack(push) //保存对齐状态 #pragma pack(1) //设定为1字节对齐 struct sExample { char c; int n; }; #pragma pack(pop) //恢复对齐状态 struct sDemo { char c; int n; };
原来是按2字节对齐的,对sExample设置完后,不要改变原来的对齐方式
这里sizeof(sExample)
是5,sizeof(sDemo)
是6
(反思,为什么当时学c语言的时候没学过这个?)
2.组播
组播和单播不一样,所以为什么叫组播呢?这个问题其实当时在学校写计算机网络课设的时候就碰到了,当时写的聊天室,实验要求是用组播,但我用的单播,课设也过了,所以这个问题不了了之。
组播技术指的是单个发送者对应多个接收者的一种网络通信。 IP 组播技术有效地解决了单点发送多点接收的问题,实现了 IP 网络中点到多点的高效数据传送,能够大量节约网络带宽、降低网络负载。
组播地址是分类编址的IPV4地址中的D类地址,又叫多播地址,他的前四位必须是1110,所以网络地址的取值范围是224-239(11100000-11101111)
224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其他地址供路由协议使用 224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet 224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效
一个例子:
服务端:
#include <iostream> #include <winsock2.h> #include <WS2tcpip.h> #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 512 using namespace std; int main() { //Declare and initialize variables WSADATA wsaData = { 0 }; int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { wprintf(L"WSAStartup failed: %d\n", iResult); return -1; } //创建socket SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock == INVALID_SOCKET) { wprintf(L"socket function failed with error = %d\n", WSAGetLastError()); WSACleanup(); return -1; } sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(8889); iResult = bind(sock, (sockaddr*)(&addr), sizeof(addr)); if (iResult == SOCKET_ERROR) { wprintf(L"bind function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return -1; } struct ip_mreq imr; imr.imr_multiaddr.s_addr = inet_addr("234.2.2.2"); imr.imr_interface.s_addr = INADDR_ANY; //加入组播组 iResult = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&imr, sizeof(imr)); if (iResult == SOCKET_ERROR) { wprintf(L"setsockopt function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return -1; } char recvbuf[DEFAULT_BUFLEN] = ""; int recvbuflen = DEFAULT_BUFLEN; sockaddr_in client; int clientLen; clientLen = sizeof(client); memset(&client, 0, clientLen); iResult = recvfrom(sock, recvbuf, recvbuflen, 0, (sockaddr*)(&clientLen), &clientLen); if (iResult > 0) printf("Bytes received: %d\n%s\n", iResult, recvbuf); else if (iResult == 0) printf("Connection closed\n"); else printf("recv failed: %d\n", WSAGetLastError()); closesocket(sock); WSACleanup(); system("pause"); return 0; }
客户端:
#include <iostream> #include <winsock2.h> #include <WS2tcpip.h> #include <stdio.h> #pragma comment (lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 512 using namespace std; int main() { //Declare and initialize variables WSADATA wsaData = { 0 }; int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { wprintf(L"WSAStartup failed: %d\n", iResult); return -1; } //创建socket SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock == INVALID_SOCKET) { wprintf(L"socket function failed with error = %d\n", WSAGetLastError()); WSACleanup(); return -1; } sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("234.2.2.2"); addr.sin_port = htons(8889); const char *sendbuf = "hello, server"; iResult = sendto(sock, sendbuf, (int)strlen(sendbuf), 0, (sockaddr*)(&addr), sizeof(addr)); if (iResult == SOCKET_ERROR) { wprintf(L"send failed with error: %d\n", WSAGetLastError()); closesocket(sock); WSACleanup(); return -1; } printf("send: %d\n", iResult); closesocket(sock); WSACleanup(); system("pause"); return 0; }
3.网络传输中大小端问题
大小端也是面试中经常被问到的问题。
网络传输中确实需要注意大小端的问题,最近在极客时间上看的网络编程实战中也提到了这个问题。发送端发送时需要将发送的内容转成网络字节序的形式,因为你也不知道接受的主机是大端还是小端,如果你俩都是小端机,万事大吉。但如果发和收的字节序不一样就会出现问题,因此双方都应该以网络字节序交流。
那怎么理解这个事呢?比如现在要发送0x0102,占两个字节,在我电脑(小端机)上内存存放如下:
先发送低地址的数据0x02,再发送高地址的数据0x01,但如果我的电脑是大端机,则会先发送0x01再发送0x02,效果截然不同。因此,系统中提供了一组函数用来转换:
uint16_t htons (uint16_t hostshort) uint16_t ntohs (uint16_t netshort) uint32_t htonl (uint32_t hostlong) uint32_t ntohl (uint32_t netlong)
函数中的 n 代表的就是 network,h 代表的是 host,s 表示的是 short,l 表示的是 long,分别表示 16 位和 32 位的整数。
值得关注的是,占一个字节的char类型不需要转,因为它只占一个字节,而字节是最小的存储单位。
//一种判断主机大小端的方法: short j = 0x1234; if (reinterpret_cast<char &>(j) == 0x12) { cout << "The byte order is big-endian" << endl; } else { cout << "The byte order is little-endian" << endl; }
4.get_value
template<typename T> T getValue(const char *buf, T offset) { return *((T*)(buf + offset)); } int main() { const char *buf = "123"; char value = getValue<char>(buf, 0); short sValue = getValue<short>(buf, 1); cout << value << endl; cout << sValue << endl; }
输出:
1 13106
5.QByteArray
QByteArray byteArray("gaoyuelong"); qDebug() << byteArray.size();
输出:10
QByteArray byteArray; QDataStream dataStream(&byteArray, QIODevice::WriteOnly); dataStream << "gaoyuelong"; //byteArray的size为15 包含开头用来记录长度的四字节 qDebug() << byteArray.size(); QDataStream dataStream1(&byteArray, QIODevice::ReadOnly); int iLength = 0; dataStream1 >> iLength; qDebug() << iLength; //data() + 4跳过开头的四字节 char *data = byteArray.data() + 4; while (*data) { qDebug() << "[" << *data << "]"; ++data; }
输出:
15 11 [ g ] [ a ] [ o ] [ y ] [ u ] [ e ] [ l ] [ o ] [ n ] [ g ]
需要注意使用dataStream1
读取时,开头有四个字节表示长度。