1.多进程编程基础知识
(1)什么是进程?
在操作系统原理使用这样的术语来描述的:正在运行的程序及其占用的资源(CPU、内存、系统资源等)叫做进 程。站在程序员的角度来看,我们使用vim编辑生成的C文件叫做源码,源码给程序员来看的但机器不识别,这时我们需要使用 编译器gcc编译生成CPU可识别的二进制可执行程序并保存在存储介质上,这时编译生成的可执行程序只能叫做程序而不能叫进 程。而一旦我们通过命令(./a.out)开始运行时,那正在运行的这个程序及其占用的资源就叫做进程了。
(2)进程空间内存布局
栈:栈内存由编译器在程序编译阶段完成,进程的栈空间位于进程用户空间的顶部并且是向下增长,每个函数的每次调用 都会在栈空间中开辟自己的栈空间,函数参数、局部变量、函数返回地址等都会按照先入者为栈顶的顺序压入函数栈中, 函数返回后该函数的栈空间消失,所以函数中返回局部变量的地址都是非法的。
堆:堆内存是在程序执行过程中分配的,用于存放进程运行中被动态分配的的变量,大小并不固定,堆位于非初始化数据 段和栈之间,并且使用过程中是向栈空间靠近的。当进程调用 malloc 等函数分配内存时,新分配的内存并不是该函数的 栈帧中,而是被动态添加到堆上,此时堆就向高地址扩张;当利用 free 等函数释放内存时,被释放的内存从堆中被踢 出,堆就会缩减。因为动态分配的内存并不在函数栈帧中,所以即使函数返回这段内存也是不会消失。
非初始化数据段:通常将此段称为 bss 段,用来存放未初始化的全局变量和 static 静态变量。并且在程序开始执行之前, 就是在 main()之前,内核会将此段中的数据初始化为 0 或空指针。
初始化数据段:用来保已初始化的全局变量和 static 静态变量。 文本段也称代码段,这是可执行文件中由 CPU 执行的机器指令部分。正文段常常是只读的,以防止程序由于意外而修改 其自身的执行。
(3)四次握手模型
2.实现函数
(1)fork()系统调用
fork()系统调用会创建一个新的子进程,这个子进程是父进程的一个副本。这也意味着,系统在创建新的子进程成功后,会将 父进程的文本段、数据段、堆栈都复制一份给子进程,但子进程有自己独立的空间,子进程对这些内存的修改并不会影响父进程 空间的相应内存。这时系统中出现两个基本完全相同的进程(父、子进程),这两个进程执行没有固定的先后顺序,哪个进程先执 行要看系统的进程调度策略。如果需要确保让父进程或子进程先执行,则需要程序员在代码中通过进程间通信的机制来自己实 现。
(2)exec*()执行另外一个程序
在fork()之后紧接着调用exec*()系列的函数来让子进程去执行另外一个程序。其中exec*()是一些列的函数。
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
(3)vfork()系统调用
vfork()是另外一个可以用来创建进程的函数,他与fork()的用法相同,也用于创建一个新进程。 但vfork()并不将父进程的地址 空间完全复制到子进程中,因为子进程会立即调用exec或exit(),于是也就不会引用该地址空间了。不过子进程再调用exec()或 exit()之前,他将在父进程的空间中运行,但如果子进程想尝试修改数据域(数据段、堆、栈)都会带来未知的结果,因为他会影响 了父进程空间的数据可能会导致父进程的执行异常。此外,vfork()会保证子进程先运行,在他调用了exec或exit()之后父进程才 可能被调度运行。如果子进程依赖于父进程的进一步动作,则会导致死锁。
(4)system()与popen()函数
3.实现代码
源码链接
https://gitee.com/old_man_with_horse_face/temperature_sensor_project
(1)服务器端
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#define BACKLOG 10
#define MSG_STR "Hello, you have connected to the server successfully!\n\n"
int g_stop = 0;
void sig_stop(int signum)
{
if (SIGINT == signum)
{
printf("SIGINT signal detected\n");
g_stop = 1;
}
}
int main(int argc, char *argv[])
{
int rv;
int port = 8889;
int on = 1;
int server_fd = -1;
int client_fd = -1;
pid_t pid = -1;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t clientaddr_len = 20;
int daemon_start = 1;
int log_fd;
char sql[128];
char *errmsg;
sqlite3 *db;
if((sqlite3_open("temperature.db", &db)) != SQLITE_OK)
{
printf("Open database unsuccessfully : %s\n", sqlite3_errmsg(db));
exit(1);
}
else
{
printf("Open database successfully!\n");
}
if((sqlite3_exec(db, "create table if not exists temperature(infor varchar(100));", NULL, NULL, &errmsg)) != SQLITE_OK)
{
printf("Create table unsuccessfully : %s\n", errmsg);
exit(1);
}
else
{
printf("Create table successfully!\n");
}
signal(SIGINT, sig_stop);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0)
{
printf("Creat socket unsuccessfully : %s\n", strerror(errno));
return 0;
}
printf("Creat the server[%d] successfully!\n",server_fd);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
rv = bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (rv < 0)
{
printf("Bind server's port unsuccessfully:%s\n", strerror(errno));
return 0;
}
printf("The port[%d] has been binded by this server!\n", port);
listen(server_fd, BACKLOG);
if ( !daemon_start )
{
printf("Program is running in the background!\n");
log_fd = open("temperature.log", O_CREAT|O_RDWR, 777);
if (log_fd < 0)
{
printf("Open temperature.log unsuccessfully : %s", strerror(errno));
return 0;
}
dup2(log_fd, STDOUT_FILENO);
dup2(log_fd, STDERR_FILENO);
if((daemon(1,1)) < 0)
{
printf("Create daemon unsuccessfully : %s", strerror(errno));
return 0;
}
}
while (!g_stop)
{
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &clientaddr_len);
if (client_fd < 0)
{
printf("Accept request from client unsuccessfully : %s\n", strerror(errno));
continue;
}
printf("Accept request from client[%s:%d] successfully!\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
pid = fork();
if (pid < 0)
{
printf("Create child process unsuccessfully : %s.\n", strerror(errno));
close(client_fd);
continue;
}
else if (pid > 0)
{
close(client_fd);
continue;
}
else if (pid == 0)
{
char buf[1024];
close(server_fd);
while(1)
{
memset(buf, 0, sizeof(buf));
rv = read(client_fd, buf, sizeof(buf));
if (rv < 0)
{
printf("Read information from client unsuccessfully : %s\n", strerror(errno));
close(client_fd);
exit(0);
}
else if (rv == 0)
{
printf("The client[%d] get disconnected!\n", client_fd);
close(client_fd);
exit(0);
}
else
{
printf("The message recived from client[%s,%d] : \n%s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buf);
sprintf(sql,"insert into temperature values('%s');", buf);
if((sqlite3_exec(db, sql, NULL, NULL, &errmsg)) != SQLITE_OK)
{
printf("Insert data into table unsuccessfully : %s\n", errmsg);
}
else
{
printf("Insert data into table successfully!\n\n");
}
}
rv = write(client_fd, MSG_STR, strlen(MSG_STR));
if (rv < 0)
{
printf("Write to client unsuccessfully : %s\n", strerror(errno));
close(client_fd);
}
}
}
}
close(server_fd);
return 0;
}
(2)客户端
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<getopt.h>
#include<netdb.h>
#include<dirent.h>
#include<signal.h>
#include<syslog.h>
#include<time.h>
#include<strings.h>
int g_stop = 0;
void sig_handler(int signum);
void sig_code(int signum);
void dns(char *domain_name, char **ip);
int get_sys_time(char *datime);
int ds18b20_get_temperature(float *temp);
void sig_handler(int signum)
{
if(SIGTERM==signum)
{
printf("SIGTERM signal detected\n");
g_stop=1;
}
else if(SIGALRM==signum)
{
printf("SIGALRM signal detected\n");
g_stop=1;
}
else if(SIGINT==signum)
{
printf("SIGINT signal detected\n");
g_stop=1;
}
}
void sig_code(int signum)
{
if(SIGBUS==signum)
{
printf("SIGBUS signal detected\n");
}
else if(SIGILL==signum)
{
printf("SIGILL signal detected\n");
}
else if(SIGSEGV==signum)
{
printf("SIGSEGV signal detected\n");
}
exit(1);
}
int get_sys_time(char *datime)
{
time_t seconds;
struct tm *pTM;
time(&seconds);
pTM=localtime(&seconds);
sprintf(datime,"%04d-%02d-%02d %02d:%02d:%02d",pTM->tm_year+1900,pTM->tm_mon+1,pTM->tm_mday,pTM->tm_hour,pTM->tm_min,pTM->tm_sec);
return 0;
}
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;
int fd = -1;
char *ptr;
float value;
int found = 0;
if( !temp )
{
return -1;
}
if( (dirp = opendir(w1_path)) == NULL )
{
printf("opendir error : %s\n", strerror(errno));
}
while( (direntp = readdir(dirp)) != NULL )
{
if(strstr(direntp->d_name, "28-"))
{
strcpy(chip, direntp->d_name);
found = 1;
break;
}
}
closedir(dirp);
if( !found )
{
printf("Can not find ds18b20 in %s\n", w1_path);
return -3;
}
strncat(w1_path, chip, sizeof(w1_path) - strlen(w1_path));
strncat(w1_path, "/w1_slave", sizeof(w1_path) - strlen(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=");
if( !ptr )
{
printf("ERROR : Can not get temperature\n");
return -6;
}
ptr+=2;
*temp = atof(ptr)/1000.0;
close(fd);
return 0;
}
int main(int argc, char **argv)
{
int server_fd = -1;
int rv = -1;
struct sockaddr_in servaddr;
char *servip = "192.168.0.16";
int port = 8889;
char buf[1024];
int ch = -1;
float temp;
char datime[32];
char *user = "Linke";
int connected = 0;
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if(server_fd < 0)
{
printf("Create client socket unsuccessfully : %s\n", strerror(errno));
return -1;
}
printf("Create client socket[%d] successfully!\n", server_fd);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
inet_aton(servip, &servaddr.sin_addr);
signal(SIGTERM, sig_handler);
signal(SIGALRM, sig_handler);
signal(SIGINT, sig_handler);
signal(SIGBUS, sig_code);
signal(SIGILL, sig_code);
signal(SIGSEGV, sig_code);
while(!g_stop)
{
get_sys_time(datime);
ds18b20_get_temperature(&temp);
memset(buf, 0, sizeof(buf));
snprintf(buf,sizeof(buf),"%s/%s/%f",user,datime,temp);
if( !connected )
{
rv = connect(server_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if(rv < 0)
{
printf("Connect to server[%s:%d] unsuccessfully : %s\n", servip, port, strerror(errno));
sleep(5);
}
printf("Connect to server[%s:%d] successfully!\n", servip, port);
}
rv = write(server_fd, buf, strlen(buf));
if(rv < 0)
{
printf("Write to server[%d] unsuccessfully : %s\n", server_fd, strerror(errno));
return -1;
break;
}
printf("Write to server[%d] successfully!\n", server_fd);
memset(buf, 0, sizeof(buf));
rv = read(server_fd, buf, sizeof(buf));
if(rv < 0)
{
connected = 1;
printf("Read data from server[%d] unsuccessfully : %s\n", server_fd, strerror(errno));
sleep(5);
continue;
}
else if(rv == 0)
{
printf("Server[%d] get disconnected!\n", server_fd);
return -1;
break;
}
else if(rv > 0)
{
connected = 1;
printf("Read %d bytes data : %s", rv, buf);
}
sleep(10);
}
close(server_fd);
return 0;
}
4.总结
(1)由于第一次独立完成,代码过于冗长,各个功能模块的实现以及代码思路还不够清晰。
(2)在编程过程中遇到很多问题,例如在开启服务器端后,由于安装信号,停止服务器端运行后,无法释放端口,后通过netstat -tlnp查看端口被占用,后kill -9加进程号即可。