前言:前面讲了网络socket编程流程和温度上报的客户端,服务器端的功能和客户端有很多相同,我就不同的地方说明一下。
- 程序放到后台运行,并通过syslog记录程序的运行出错、调试日志;(服务器端)
- .程序能够捕捉kill信号正常退出;(服务器客户端都有)
- 服务器要支持多个客户端并发访问,可以选择多路复用、多进程或多线程任意一种实现;(我选择的是多线程,之后会有一篇博客讲解它们之间的区别)
- 服务器收到每个客户端的数据都解析后保存到数据库中,接收到的数据格式为: “ID/时间/温度”,如RPI0001/2019-01-05 11:40:30/30.0C”;(之前错写在客户端了,这次就不说了,下次单独写博客)
该数据库要求一个客户端创建一个表,表名为客户端树莓派的ID,如RPI0001, 表里面有三个字段,分别为ID(自动增长的主键)、时间、温度值。
先直接上部分代码(与客户端重复的不再介绍):
源码:https://gitee.com/lsj123456/temperature_project.git
int thread_start(pthread_t *thread_id,THREAD_BODY *thread_workbody,void *thread_arg)
{
int rv=-1;
pthread_attr_t thread_attr; //定义线程的属性变量,该属性的类型定义如下:
/*typedef struct
{
int detachstate; 线程的分离状态
int schedpolicy; 线程调度策略
struct sched_param schedparam; 线程的调度参数
int inheritsched; 线程的继承性
int scope; 线程的作用域
size_t guardsize; 线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void * stackaddr; 线程栈的位置
size_t stacksize; 线程栈的大小
}pthread_attr_t;*/
if(pthread_attr_init(&thread_attr))//对该属性设置前,先初始化它
{
printf("pthread_attr_init()failure:%s\n",strerror(errno));
pthread_attr_destroy(&thread_attr);
}
if(pthread_attr_setstacksize(&thread_attr,120*1024))//设置线程栈的大小为120K
{
printf("pthread_attr_setstacksize()failure:%s\n",strerror(errno));
pthread_attr_destroy(&thread_attr);
}
if(pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED))//设置线程的属性为分离状态
{
printf("pthread_attr_setdatachstate()failure:%s\n",strerror(errno));
pthread_attr_destroy(&thread_attr);
}
if(pthread_create(thread_id,&thread_attr,thread_workbody,thread_arg))//创建一个线程
{
printf("create thread failure:%s\n",strerror(errno));
pthread_attr_destroy(&thread_attr);
} //第一个参数是一个Pthread-t类型的指针,用来返回线程的ID
rv=0; //第二个参数是线程的属性
return rv; //第三个参数是创建的子线程要执行的任务
} //第四个参数arg就是传给了所调用的函数的参数,如果有多个参数需要传递给子线程则需要封装到一个结构体里面
void *thread_worker(void *ctx)//线程实现的功能
{
int clifd;
int rv;
char buf[1024];
int i;
clifd=(int)ctx;
printf("child thread start to communicate with socket client...\n");
while(!g_stop)
{
memset(buf,0,sizeof(buf));
rv=read(clifd,buf,sizeof(buf));
if(rv<0)
{
printf("read data from server by server[%d] failure:%s\n",clifd,strerror(errno));
close(clifd);
pthread_exit(NULL);
}
else if(rv==0)
{
printf("server_fd [%d] get disconnect\n",clifd);
close(clifd);
pthread_exit(NULL);
}
{
printf("read %d bytes data from server:%s\n",rv,buf);
}
rv=write(clifd,buf,rv);
if(rv<0)
{
printf("write to server by server_fd [%d] failure:%s\n",clifd,strerror(errno));
close(clifd);
pthread_exit(NULL);
}
printf("write to server by server_fd[%d] [%s]successfully!\n",clifd,buf);
}
}
注意对于多线程编程在编译时,一定要加上-lpthread 选项告诉链接器在链接的时候要连接pthread库:
iot@Public_RPi:~/liushoujin/temperature_project $ gcc temper.server2.c -lpthread
主函数部分:
int main(int argc,char **argv)
{
int opt=-1;
int server_fd=-1;
int rv=-1;
int port;
char buf[256];
float temp;
char datatime[32];
struct sockaddr_in servaddr;
struct sockaddr_in cliaddr;
char *servip=(char *)malloc(ip_len);
char *user="liushoujin";
char *progname=basename(argv[0]);
int clifd=-1;
int on=1;
int log_fd=-1;
socklen_t len;
pthread_t tid;
opt=getopt(argc,argv,"xy");
if(opt!='x' && opt!='y')
{
printf("argument parsing failure:%s\n",strerror(errno));
return -1;
}
if(opt=='x')
{
dns("master.iot-yun.com",&servip);
}
if(opt=='y')
{
dns("studio.iot-yun.com",&servip);
}
server_fd = socket( AF_INET, SOCK_STREAM, 0);
if(server_fd < 0)
{
printf("Fail to create a client socket [%d]: %s\n", server_fd, strerror(errno) );
return -1;
}
printf("creat a client socket[%d] successufully!\n",server_fd);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
if(opt == 'x')
{
servaddr.sin_port = htons(port=9033);
printf("Get temperature from [%d]master.iot-yun.com...\n",port);
}
if(opt == 'y')
{
servaddr.sin_port =htons(port=8034);
printf("Get temperature form studio.iot-yun.com...\n");
}
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
signal(SIGTERM,sig_handler);
signal(SIGALRM,sig_handler);
signal(SIGINT,sig_handler);
setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //解决服务器程序停止后立刻运行,出现(端口)被占用的问题
rv=bind(server_fd,(struct sockaddr *)&servaddr,sizeof(servaddr));
if(rv<0)
{
printf("socket[%d] bind on port[%d] failure:%s\n",server_fd,port,strerror(errno));
return -1;
}
listen(server_fd,13);
printf("start to listen port[%d]\n",port);
log_fd = open("temper.log",O_CREAT|O_RDWR, 0666);//建立日志系统
if (log_fd < 0)
{
printf("Open the logfile failure : %s\n", strerror(errno));
return 0;
}
dup2(log_fd, STDOUT_FILENO);
dup2(log_fd, STDERR_FILENO); //标准输出、标准出错重定向
if ((daemon(1, 1)) < 0)//守护进程,程序后台运行
{
printf("Deamon failure : %s\n", strerror(errno));
return 0;
}
while(!g_stop)
{
getdatatime(datatime);
ds18b20_get_temperature(&temp);
snprintf(buf,sizeof(buf),"%s/%s/%f",user,datatime,temp);
clifd=accept(server_fd,(struct sockaddr *)&cliaddr,&len);
if(clifd<0)
{
printf("accept new client failure:%s\n",strerror(errno));
continue;
}
printf("accept new clent[%s:%d] successfully!\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
thread_start(&tid,thread_worker,(void *)clifd);//线程开始工作
}
syslog(LOG_NOTICE,"program %s stop running...\n",progname);
closelog();
close(clifd);
}
主函数中其他函数的实现之前博客都有介绍
项目发现问题:
1.多线程存在线程之间共享一个资源的问题,有待解决
2. 尽管我增加了setsockopt()这个函数好像只解决端口被占用的问题,解决不了地址被占用的问题。因为服务器端一直在后台运行,当我修改服务器端程序在编译时,地址被占用。此时可以使用命令netstat -tlnp查看端口号被那个进程占用,使用命令kill -9 PID强制杀死进程。然后就可以修改编译执行服务器端。