1.我们先要明确,通信中我们传递的是什么?
数字信号:010101
模拟信号:高低电平
我们想要发送字符串“YYDS”
我们需要找到字符串和0101数字信号对应
字符串在储存时,需要转换成ASCII码,然后将ASCII储存。
“YYDS”
0x59 0x59 0x44 0x53
01011001 01011001 01000100 01010011
其中“1011001 1011001 1000100 1010011”便是YYDS的数字信号,当然,如果你想发一个int a = 188113491,那么你发的也是1011001……。
可以看出,不论你发字符串还是发整型,都是发的比特流。比特流只表示0101,如果不指明数据类型,其实它没有任何意义。
因此,你发送一个字符串或者整型,你只需要把你的数据转换成数字信号,并存放在发送缓存中(这一步大部分是不需要我们操作的,我们只需要将我们的数据按顺序放置在缓存中就可以了),接收方接收到数字信号后,需要按照数据类型,以及大端小端,将比特流拆分,整理,以相应的长度保存到相应变量中。
例如发送的是:char、int、int,char
那么接收:前8位比特通过ascii,转换为字符;9-24比特,直接转为整型;25-40比特,直接转为整型;41-48比特通过ascii,转为字符。
通信中传输的是比特流,你想发,你就想办法把数据灌到比特流中,你想收,你就将接收到的比特流按对方的协议,解析成能正确提取信息的结构。
2.c语言结构体定义及转换为字符串
定义:
typedef struct {
char test1; //seq: 1 分割符
int year; //seq: 2 年
} __attribute__ ((__packed__)) AV_DATA;
创建结构体指针(指向结构体首地址):
当然,你也可以直接用结构体,那样只是更费资源。
ARV_DATA *data0;
初始化结构体
data0 = (ARV_DATA *)malloc(sizeof(ARV_DATA));
memset(data0, 0, sizeof(ARV_DATA));
结构体赋值,这里用的结构体指针,所用直接使用“->”
void makedata(ARV_DATA *data)
{
data->test1 ='@';
data->year =2022;
}
说是转化成字符串,其实是将整个结构体的所有数据,按照一字节转为一字符分开。
结构体的数据:'@'2022
结构体的16进制:40 00 00 07 E6
结构体的数字信号:0010 1000 0000 0000 0000 0000 0000 0111 1110 0110
如果将其转化为字符串(这里只和大小端有关系,如果不考虑大小端)
char *buf;
buf = (char *)malloc(MAX_DATA_BUF);
memset(buf, 0, MAX_DATA_BUF);
memcpy(buf, data0, sizeof(ARV_DATA));
字符串的数字信号:0010 1000 0000 0000 0000 0000 0000 0111 1110 0110
如果按照字符显示:
‘@’,‘NUL’,‘NUL’,‘BEL’,‘æ’
这里如果用
printf("Char:%s\n",buf);
则输出
@
其他都是不可打印字符
3.Socket网络通信
我们调试Socket时,更多的情况下会用自己的电脑建立一个客户端,一个服务器端,自己给自己发。
这里,有一个比较好用的IP:127.0.0.1(保留地址),发给这个IP的。当IP层接收到目的地址为127.0.0.1的数据包时,不调用网卡驱动进行二次封装,而是立即转发到本机IP层进行处理,可实现计算机的自发自收。
这里以Linux的UDP为例子
1)不管发送端还是结构端,都需要创建一个结构体sockaddr_in
struct sockaddr_in localSockAddr;
用该结构来绑定IP和端口,或者给IP和端口发送数据
void init()
{
int localPortNum = 8006;
char localIpAddr[20] = "127.0.0.1";
memset(&localSockAddr, 0, sizeof(localSockAddr));
localSockAddr.sin_family=AF_INET;
localSockAddr.sin_addr.s_addr=inet_addr(localIpAddr);
localSockAddr.sin_port=htons(localPortNum);
}
2)接收端
①创建套接字描述符
int localSockfd = socket(AF_INET, SOCK_DGRAM, 0);
AF_INET:为使用常用的IPv4协议
SOCK_DGRAM:为创建UDP套接字
0:前面已经指明UDP套接字,后面默认
②绑定IP和端口号,其实绑定不是太准确,可以说是告诉程序,你需要去监听的ip和port。
如果你是一台电脑,绑定自己的IP或者127.0.0.1,如果你是有好几个网卡的服务端,你就需要选择绑定哪个IP了。
socklen_t g_sinSize = sizeof(localSockAddr);
int ret = bind(localSockfd, (struct sockaddr*)&localSockAddr, g_sinSize);
③监听
监听需要添加循环,让其一直处于监听状态。有了循环,如果你不用多线程那么,程序就会卡在循环中,因此需要为它独自建立一个线程,以让其循环不干扰其他程序的运行。
Ⅰ、建立接收buf
char socket_read_buf[1024];
Ⅱ、建立循环
while (1) {
memset(socket_read_buf, 0, sizeof(socket_read_buf));
memset(socket_read_buf, 0, sizeof(socket_read_buf));
if ((len = recvfrom(localSockfd, socket_read_buf, MAX_DATA_BUF, 0,(struct sockaddr *)&reSockAddr,&g_sinSize)) <= 0) {
usleep(100000); // 100ms
} else {
printf("Receive From Socket Success: %d from port :%d\n", len,reSockAddr.sin_port);
}
这里大家可能注意到了二郎写了一个
(struct sockaddr *)&reSockAddr
其一:建立一个新的sockaddr_in结构体
struct sockaddr_in reSockAddr;
其二:将该结构体的首地址强制转换成sockaddr指针
因为recvfrom需要这样的一个指针
其三:这里新建结构体是因为该结构体记录发送端的ip和port,而并非指明自己的ip和port
3)发送端
①创建套接字描述符
int localSockfd1 = socket(AF_INET, SOCK_DGRAM, 0);
②发送数据
这里为了测试,因此用的循环,正常使用按自己的需要来写
while(1)
{
rtn = sendto(localSockfd1, data0, tempMsgLen, 0,(struct sockaddr *)&localSockAddr,g_sinSize);
printf("Socket write size %d\n", rtn);
sleep(5);
}
这里写了localSockAddr,此处为接收端的套接字结构体,告诉sendto往哪个套接字去发送。