关于Socket通信过程中字节序
在网络编程里,网络字节序是big-endian的,而大部分的PC的系统都是X86处理器系列,X86采用的是little-endian,所以需要将网络数据流转换成本地数据流的话,需要进行字节序的转换。
标准库里提供了hlton()和nthl()两个函数来支持转换。
hston(unsigned short), hlton(unsigned long) 将本地字节序转换为网络字节序
ntohl(unsigned long), ntohs(unsigned short) 将网络字节序转换为本地字节序
但是对于64位的整数进行转换,标准库并没有提供相应的转换函数。
关于本系统是Big-endian还是Little-endian存储,可以写一个简单函数进行判断:
#include<stdio.h>
int main(){
int num = 0x1234;
int* p = #
if(*((char*)p) == 0x12){
printf("Big-endian\n");
}
else{
printf("Little-endian\n");
}
return 0;
}
另外,关于socket通信过程中客户端和服务端操作系统位数的问题,本人在Vmware中开启了三个Ubuntu(两个为32位操作系统,一个为64位操作系统,皆为小端存储),以一个32位系统运行socket通信的客户端,另外两个设置为服务端,通过修改客户端的访问目标ip地址观察不同位系统socket通信结果,发现32to32,一切正常可以运行,程序命令行传入参数可以顺利达到服务端经服务端数据处理函数处理之后返回至客户端并打印出来,期间在服务端打印出客户端传送过来的数据并打印客户端的地址、端口信息。
但在32to64通信过程中,服务端显示乱码,且不能正常返回处理后的数据。
查阅资料:在32位机器和64机器中int类型都占用4个字节。编译器可以根据自身硬件来选择合适的大小,但是需要满足约束:short和int型至少为16位,long型至少为32位,并且short型长度不能超过int型,而int型不能超过long型。
这即是说各个类型的变量长度是由编译器来决定的,而当前主流的编译器中一般是32位机器和64位机器中int型都是4个字节,总体而言,最大的不同点就是在long型和指针类型长度不一样,对于指针而言,64位机器可以寻址2^64,每个内存地址长度为64位,即8字节。
服务端程序:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define MAX_LINE 100
/* 处理函数,用于将大写字符转换为小写字符。参数为需要转换的字符串 */
void my_fun(char * p)
{
if(p == NULL) /* 空串 */
return;
for (; *p != '\0'; p++)
if(*p >= 'A' && *p <= 'Z') /* 判断字符并进行转换,也可以使用库函数 */
*p = *p -'A' + 'a';
}
int main(void)
{
struct sockaddr_in sin;
struct sockaddr_in cin;
int l_fd;
int c_fd;
socklen_t len;
char buf[MAX_LINE]; /* 存储传送内容的缓冲区 */
char addr_p[INET_ADDRSTRLEN]; /* 存储客户端地址的缓冲区 */
int port = 8000; /* 端口号,使用8000 */
int n; /* 读写字节数 */
bzero(&sin, sizeof(sin)); /* 清空地址结构 */
sin.sin_family = AF_INET; /* 使用IPv4通信域 */
sin.sin_addr.s_addr = INADDR_ANY; /* 服务器可以接受任意地址 */
sin.sin_port = htons(port); /* 端口号转换为网络字节序 */
l_fd = socket(AF_INET, SOCK_STREAM, 0); /* 创立套接字,使用TCP协议 */
bind(l_fd, (struct sockaddr*) &sin, sizeof(sin)); /* 将地址和套接字绑定 */
listen(l_fd, 10); /* 开始监听连接请求 */
printf("waiting ...\n");
while(1){/* 服务器程序多半是死循环 */
/* 接受连接请求,从此函数中返回后就可以开始通信了 */
c_fd = accept(l_fd, (struct sockaddr*) &cin, &len);
n = read(c_fd, buf, MAX_LINE); /* 读取客户端传来的信息 */
//inet_ntop(AF_INET, &cin.sin_addr, addr_p, sizeof(addr_p));
/* 将客户端地址转换为字符串 */
//printf("client IP is %s, port is %d\n", addr_p, ntohs(cin.sin_port)); /* 打印客户端地址和端口号 */
printf("content is : %s\n", buf); /* 打印客户端发送过来的字符串 */
my_fun(buf); /* 调用大小写转换函数 */
write(c_fd, buf, n); /* 将转换后的字串发给客户端 */
close(c_fd); /* 通信结束,关闭套接字,准备下一次通信 */
}
if(close(l_fd) == -1){ /* 通信结束,关闭套接字,准备下一次通信 */
perror("fail to close");
exit(1);
}
return 0; /* 不应该执行到这里 */
}
客户端程序:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define MAX_LINE 100
int main(void)
{
struct sockaddr_in sin;
char msg[100];
int l_fd;
char buf[MAX_LINE]; /* 存储传送内容的缓冲区 */
int port = 8000; /* 端口号,使用8000 */
bzero(&sin, sizeof(sin)); /* 清空地址结构 */
sin.sin_family = AF_INET; /* 使用IPv4通信域 */
inet_pton(AF_INET,"10.17.38.98", &sin.sin_addr);
sin.sin_port = htons(port); /* 端口号转换为网络字节序 */
l_fd = socket(AF_INET, SOCK_STREAM, 0); /* 创立套接字,使用TCP协议 */
connect(l_fd, (const struct sockaddr*)&sin, sizeof(sin));
printf("Input Message:\n");
scanf("%s", msg);
write(l_fd, msg, strlen(msg)+1);
sleep(5); //延时读取
read(l_fd, buf, MAX_LINE);
printf("Receive messaege from server: %s\n", buf);
//close(l_fd);
if(close(l_fd) == -1){ /* 通信结束,关闭套接字,准备下一次通信 */
perror("Fail to close !");
exit(1);
}
return 0;
}
对64位服务端的server.c程序屏蔽掉客户端IP、端口获取并打印的相关代码段,此时服务端、客户端均能能够正常运行。因此,在char型命令行参数传递过程中是没有问题的,32位和64位操作系统的主要区别在于指针以及结构体(内存对齐位数不同),而accept()的第二个参数是客户端的地址结构,由客户端的connect()函数发送给服务端,在这过程中存在位数不匹配的问题,因此不能进行正常通信。
另,根据上述原理,可以声明一个空类型指针用来判断系统的位数。
#include<iostream>
int main(){
void* p;
if(sizeof(p)==4){
std::cout << "This is a 32-bit machine." << std::endl;
}
else
std::cout << "This is a 64-bit machine." << std::endl;
return 0;
}
在32位和64位系统测试结果如下: