写在前面的话:学习编程两年多一直是输入型的学习,未曾真正自己写过代码,现在开始要改变自己啦。记录一下自己编程过程中遇到的问题和重拾之前学过但被自己忘记的函数们Orz。
第一次编写时间:2018.10.17
实现目标:将从树莓派的温度传感器上获取的温度从客户端发送给服务器,并从服务器上获取温度反应到客户端上。
准备工作:了解学习SOCKET的相关基础知识。详见:https://www.cnblogs.com/jiangzhaowei/p/8261174.html
补充小知识点:
1.端口号的作用:定位到应用程序
2.IP的作用:具体定位到某台计算机
3.区分客户端和服务器端:
发起者为客户端,接受请求为服务器端
!!浏览器属于客户端!!
4.域名通过DNS解析为IP地址
5.TCP与UDP的区别:
UDP:面向无连接(不会建立连接,双方不可相互通信),限制传送为64K,是不可靠协议。
TCP:面向连接的协议,三次握手后才会建立连接,使用字节流传输,但效率没有UDP高,是可靠协议。
6.HTTP协议底层使用TCP协议进行连接
7.网络通信中的SOCKET中传输的数据必须是大端字节序。
编程开始,服务器端完整代码如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc , char **argv)
{
int sock_fd,connect_fd;
struct sockaddr_in serv_addr; /*sockaddr_in为IPV4对应的结构体 serv_addr为结构名*/
char buf[2048]; /*定义一个缓存区*/
if((sock_fd=socket(AF_INET,SOCK_STREAM,0))<0) /*socket返回一个描述符,若小于0则创建失败,最后一个参数是protocol(指定协议),当protocol为0时会自动选择type(第二个参数)类型对应的默认协议*/
{
printf("create socket failure : %s \n",strerror(errno));
return -1;
}
printf("create socket fd[%d] \n",sock_fd); /*打印描述符以便运行时查看哪一步出问题*/
memset(&serv_addr,0,sizeof(serv_addr)); /*利用memset函数将结构体初始化清理,以便后续填入数据*/
serv_addr.sin_family=AF_INET; /*服务器端的地址族*/
serv_addr.sin_port=htons(5555); /*服务器端的端口号*/
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); /*INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址或者所有地址(任意地址) */
/* sin_port 和 sin_addr 都必须是网络字节序,所有使用htons 和 htonl将主机字节序转化成网络字节序*/
if((bind(sock_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)))<0) /*bind绑定IP和端口号*/
{
printf("create socket blind failure: %s \n",strerror(errno));
return -2;
}
printf("create socket blind \n",sock_fd);
listen(sock_fd,13); /*开始监听,listen是阻塞函数,若无客户端发来连接请求过来则一直处于监听状态直到有客户端发送连接请求,第二个参数为相应socket可以排队的最大连接个数*/
printf("socket is listenning...\n",sock_fd);
while(1)
{
printf("start accept ..\n",sock_fd);
if((connect_fd=accept(sock_fd,NULL,NULL))<0) /*接受连接请求,第一个参数是客户端socket描述符,第二个这是一个结果参数addr,它用来接受一个返回值,这返回值指定客户端的地址,第三个参数用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数*/
{
printf("accept new socket failure %s \n ",strerror(errno));
return -3;
}
printf("accept new socket complecte \n",connect_fd);
memset(buf,0,sizeof(buf)); /*初始化清零缓存区*/
read(connect_fd,buf,sizeof(buf)); /*读取客户端发送过来的数据*/
printf("read '%s' from client \n",buf); /*服务器端打印从客户端读取到的数据*/
sleep(1);
close(connect_fd);
}
close(sock_fd);
}
客户端的代码:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "get_temperature.h"
int main (int charc, char **argv)
{
int conn_fd=-1;
struct sockaddr_in serv_addr; /*用于填写服务器端的资料*/
char buf[4096];
float temp;
conn_fd=socket(AF_INET,SOCK_STREAM,0); /*建立客户端的socket*/
if(conn_fd<0)
{
printf("client create socket failure : %s \n",strerror(errno));
return -1;
}
memset(&serv_addr,0,sizeof(serv_addr)); /*初始化结构体*/
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(5555);
inet_aton("127.0.0.1",&serv_addr.sin_addr); /*因为是本机跟本机连接,所以此处的服务器端IP地址为127.0.0.1*/
if((connect(conn_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)))<0)
{
printf("client connect to serv failure : %s \n",strerror(errno));
return -2;
}
if(ds18b20_get_temperature(&temp)<0) /*调用获取温度的函数*/
{
printf("ERROR: ds18b20 get temperature failure \n");
return -3;
}
printf("get temperature is %f \n",temp);
memset(buf,0,sizeof(buf));
sprintf(buf,"%f",temp); /*将温度数值转换为字符串存到buf中*/
printf("buf is %s \n",buf); /*打印buf*/
write(conn_fd,buf,strlen(buf));
read(conn_fd,buf,sizeof(buf));
printf("read %s from client \n",buf);
sleep(1);
close(conn_fd);
}
客户端程序中调用到的从树莓派上获取温度的代码(须另外声明一个头文件,将头文件添加至客户端代码段):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <errno.h>
int ds18b20_get_temperature(float *temp)
{
char w1_path[50] = "/sys/bus/w1/devices/";
char chip[20];
char buf[128];
DIR *dirp;
struct dirent *direntp;
float value;
int found = 0;
if( !temp )
if((dirp = opendir(w1_path)) == NULL)
{
printf("opendir error: %s\n", strerror(errno));
return -2; }
while((direntp = readdir(dirp)) != NULL) {
if(strstr(direntp->d_name,"28-"))
{
strcpy(chip,direntp->d_name); found = 1;
}
}
closedir(dirp);
if( !found )
{
printf("Can not find ds18b20 in %s\n", w1_path);
return -3;
}
strncat(w1_path, chip, sizeof(w1_path));
strncat(w1_path, "/w1_slave", sizeof(w1_path));
if( (fd=open(w1_path, O_RDONLY)) < 0 )
{
printf("open %s error: %s\n", w1_path, strerror(errno)); return -4;
}
if(read(fd, buf, sizeof(buf)) < 0)
{
printf("read %s error: %s\n", w1_path, strerror(errno));
return -5;
}
ptr = strstr(buf, "t=")+2; if( !ptr )
{
printf("ERROR: Can not get temperature\n"); return -6;
}
*temp = atof(ptr)/1000; return 0;
}
编写过程中出现的问题:
1.编译服务器端代码时出现 "Permission denied"的情况
原因:1024以下的端口是众所周知的端口,只有root权限才能运行。
解决办法:改端口号或用root权限运行
2.服务器端运行出现:Address already in use
原因:由 TCP 套接字状态 TIME_WAIT 引起。该状态在套接字关闭后约保留 2 到 4 分钟。在 TIME_WAIT 状态退出之后,套接字被删除,该地址才能被重新绑定而不出问题。
解决办法: 1. netstat -nap 查找该进程的PID
2.kill -9 pid (eg. kill -9 18760) 杀死该进程
心得体会:
时隔很久第一次写代码犯了好多好多小错误,printf打印值类型与参数类型不匹配造成segmentation fault错误;在有返回值的函数调用时,为了方便使用if条件句少加一个括号,忽略了符合优先级顺序;写函数忘加头文件的,等等。
但是也通过这些错误重新回顾了一遍之前学习的知识,也算很有收获啦。
本来还想整理一下代码中一些函数的原型用法及注意事项,但是今天太累了T-T,还是等有时间的时候再更新一波。