#include <time.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h> /* fork(), read函数、write函数和getpid函数*/
#include <syslog.h> /*int syslog(int priority, string message); 本函数将 message 字符串写到系统纪录中 */
#include <errno.h> // 出错码
#include <fcntl.h> /* fcntl.h定义了很多宏和open */
#include <stdarg.h> /* 让函数能够接收可变参数 */
#include <mysql/mysql.h>
#include <sys/wait.h> // 进程控制
#include <sys/stat.h> // 文件状态 2-> waitpid().
#include <signal.h> // 信号
#define BUFFER_SIZE 1024
#define LOCKFILE "/var/run/judged.pid" // 文件路径宏定义 /var/run/judged.pid
#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) // 所有者可以 r 和 w, g 和 o 可以 r 。
#define STD_MB 1048576
#define OJ_WT0 0 // Pending // 正等待...
#define OJ_WT1 1 // Pending_Rejudging
#define OJ_CI 2 // Compiling
#define OJ_RI 3 // Running_Judging
#define OJ_AC 4 // Accepted
#define OJ_PE 5 // Presentation Error
#define OJ_WA 6 // Wrong Answer
#define OJ_TL 7 // Time Limit Exceeded
#define OJ_ML 8 // Memory Limit Exceeded
#define OJ_OL 9 // Output Limit Exceeded
#define OJ_RE 10 // Runtime Error
#define OJ_CE 11 // Compilation Error
#define OJ_CO 12 // Compile_OK
static char host_name[BUFFER_SIZE];
static char user_name[BUFFER_SIZE];
static char password [BUFFER_SIZE];
static char db_name [BUFFER_SIZE];
static char oj_home [BUFFER_SIZE];
static char oj_lang_set[BUFFER_SIZE]; //
static int port_number;
static int max_running; // 可以运行的 judge_client 的数量上限。
static int sleep_time; // 数据库轮询间隔
static int sleep_tmp;
static int oj_tot; // 老式并发处理中总的 judged 数量
static int oj_mod; // 老式并发处理中,本 judged 负责处理 solution_id 按照 TOTAL 取模后余数为几的任务。
static bool STOP = false;
static MYSQL *conn; /************* 数据库 类型 *****************/
static MYSQL_RES *res;
static MYSQL_ROW row;
static char query[BUFFER_SIZE];
void call_for_exit(int s) {
STOP = true;
printf("Stopping judged...\n");
}
void write_log(const char *fmt, ...) {
va_list ap;
char buffer[4096];
// time_t t = time(NULL);
// int l;
sprintf(buffer,"%s/log/client.log",oj_home);
FILE *fp = fopen(buffer, "a+");
if (fp == NULL){
fprintf(stderr, "openfile error!\n");
system("pwd");
} va_start(ap, fmt);
vsprintf(buffer, fmt, ap);
fprintf(fp,"%s\n",buffer);
va_end(ap);
fclose(fp);
}
int after_equal(char * c){
int i=0;
for(; c[i] != '\0' && c[i] != '='; i++);
return ++i;
}
void trim(char * c){
char buf[BUFFER_SIZE];
char * start,*end;
strcpy(buf,c);
start=buf;
while(isspace(*start)) start++;
end=start;
while(!isspace(*end)) end++;
*end='\0';
strcpy(c,start);
}
bool read_buf(char * buf,const char * key,char * value){
if (strncmp(buf, key, strlen(key)) == 0) {
strcpy(value, buf + after_equal(buf));
trim(value);
return 1;
}
return 0;
}
void read_int(char * buf,const char * key,int * value){
char buf2[BUFFER_SIZE];
if (read_buf(buf, key, buf2))
sscanf(buf2, "%d", value);
}
// read the configue file
void init_mysql_conf() {
FILE *fp = NULL; // 文件指针类型 fp
char buf[BUFFER_SIZE];
host_name[0] = 0;
user_name[0] = 0;
password[0] = 0;
db_name[0] = 0;
port_number = 3306;
max_running = 3; // 最多允许 3 个 judge_client 同时存在。
sleep_time = 3;
oj_tot = 1; // 老式并发处理中总的 judged 数量
oj_mod = 0; // 老式并发处理中,本 judged 负责处理 solution_id 按照 TOTAL 取模后余数为几的任务。
strcpy(oj_lang_set, "0, 1");
fp = fopen("./etc/judge.conf", "r");
if(fp != NULL){
while(fgets(buf, BUFFER_SIZE - 1, fp)) {
read_buf(buf, "OJ_HOST_NAME", host_name);
read_buf(buf, "OJ_USER_NAME", user_name);
read_buf(buf, "OJ_PASSWORD", password);
read_buf(buf, "OJ_DB_NAME", db_name);
read_int(buf , "OJ_PORT_NUMBER", &port_number);
read_int(buf, "OJ_RUNNING", &max_running);
read_int(buf, "OJ_SLEEP_TIME", &sleep_time);
read_int(buf , "OJ_TOTAL", &oj_tot);
read_int(buf,"OJ_MOD",&oj_mod);
read_buf(buf,"OJ_LANG_SET", oj_lang_set);
}
sprintf(query,"SELECT solution_id FROM solution WHERE language in (%s) and result<2 and MOD(solution_id,%d)=%d ORDER BY result ASC,solution_id ASC limit %d", oj_lang_set, oj_tot, oj_mod, max_running*2);
sleep_tmp = sleep_time;
// fclose(fp);
}
}
void run_client(int runid, int clientid){
char buf[BUFFER_SIZE],runidstr[BUFFER_SIZE];
struct rlimit LIM;
LIM.rlim_max=800;
LIM.rlim_cur=800;
setrlimit(RLIMIT_CPU,&LIM);
LIM.rlim_max=80*STD_MB;
LIM.rlim_cur=80*STD_MB;
setrlimit(RLIMIT_FSIZE,&LIM);
LIM.rlim_max=STD_MB<<11;
LIM.rlim_cur=STD_MB<<11;
setrlimit(RLIMIT_AS,&LIM);
LIM.rlim_cur=LIM.rlim_max=200;
setrlimit(RLIMIT_NPROC, &LIM);
//buf[0]=clientid+'0'; buf[1]=0;
sprintf(runidstr, "%d", runid);
sprintf(buf, "%d", clientid);
execl("/usr/bin/judge_client", "/usr/bin/judge_client", runidstr, buf, oj_home, (char *)NULL);
}
int executesql(const char * sql) {
if (mysql_real_query(conn, sql, strlen(sql))) { // 成功,函数返回零
sleep(20);
conn = NULL;
return 1;
} else
return 0;
}
int init_mysql(){
if(conn == NULL){
conn = mysql_init(NULL); // init the database connection
/* connect the database */
const char timeout = 30;
mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
if(!mysql_real_connect(conn, host_name, user_name, password, db_name, port_number, 0, 0)) {
sleep(20);
return 1; // 连接失败,mysql_real_connect 返回为 0.
}
}
if (executesql("set names utf8"))
return 1; // 执行失败
return 0;
}
int _get_jobs_mysql(int * jobs) {
if (mysql_real_query(conn, query, strlen(query))) { // 成功返回 0, 读取未判的那些 solution_id 们。
sleep(20); // 不存在 pending 中的 solution_id , 进程挂起 20 秒。
return 0;
}
res = mysql_store_result(conn);
int i = 0;
int ret = 0;
while((row = mysql_fetch_row(res)) != NULL) {
jobs[i++] = atoi(row[0]);
}
ret = i;
while(i <= max_running*2) jobs[i++] = 0;
return ret;
}
int get_jobs(int * jobs) {
return _get_jobs_mysql(jobs);
}
bool _check_out_mysql(int solution_id, int result) { // workcnt < max_running && check_out(runid, OJ_CI)
char sql[BUFFER_SIZE];
sprintf(sql, "UPDATE solution SET result=%d,time=0,memory=0,judgetime=NOW() WHERE solution_id=%d and result<2 LIMIT 1"
,result, solution_id);
if (mysql_real_query(conn, sql, strlen(sql))) {
syslog(LOG_ERR | LOG_DAEMON, "%s",mysql_error(conn));
return false;
} else {
if(mysql_affected_rows(conn) > 0ul)
return true;
else
return false;
}
}
bool check_out(int solution_id, int result) {
return _check_out_mysql(solution_id, result);
}
int work() {
static int retcnt = 0;
int i = 0;
static pid_t ID[100]; // 声明 100 大小的pid数组。
static int workcnt = 0;
int runid = 0;
int jobs[max_running*2+1]; // max_running 是启动 judge_client 的上限
pid_t tmp_pid = 0;
/* get the database info */
if(!get_jobs(jobs)) retcnt = 0; // 检索出的 solution_id 们,全都存在 jobs[] 中。retcnt 为 pending的solution_id的数量。
/* exec the submit */
for (int j = 0; jobs[j] > 0; j++) {
runid = jobs[j];
if (workcnt >= max_running) { // if no more client can running
tmp_pid = waitpid(-1, NULL, 0); // wait 4 one child exit, pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样
workcnt--; retcnt++; // waitpid 返回收集到的子进程的进程ID, retcnt++, 这是hp?
for (i = 0; i < max_running; i++) // get the client id
if (ID[i] == tmp_pid) break; // got the client id
ID[i] = 0;
} else { // have free client
for (i = 0; i < max_running; i++) // find the client id
if (ID[i] == 0) break; // got the client id
}
if(workcnt < max_running && check_out(runid, OJ_CI)) { // 要判题了,将result置为 OJ_CI
workcnt++; // client 增加一个。
ID[i] = fork(); // start to fork
if (ID[i] == 0) {
run_client(runid, i); // if the process is the son, run it,儿子你去判题吧,父亲继续运行。
exit(0);
}
} else ID[i] = 0;
}
//下面回收运行完的进程号
while ( (tmp_pid = waitpid(-1, NULL, WNOHANG) ) > 0) { // 如果使用了WNOHANG(wait no hung)参数调用waitpid,即使没有子进程退出,它也会立即返回
workcnt--; retcnt++; // 可能是立即返回,子进程没有主动退出的,也会立即终止一个子进程。
for (i = 0; i < max_running; i++) // get the client id
if (ID[i] == tmp_pid) break; // got the client id
ID[i] = 0;
printf("tmp_pid = %d\n", tmp_pid);
}
mysql_free_result(res); // free the memory
executesql("commit");
//free(ID);
//free(jobs);
return retcnt;
}
int lockfile(int fd) {
struct flock fl;
fl.l_type = F_WRLCK; // 独占性写锁定
fl.l_start = 0;
fl.l_whence = SEEK_SET; // SEEK_SET 文件头
fl.l_len = 0;
return (fcntl(fd, F_SETLK, &fl)); // F_SETLK 设置文件锁定的状态, 如果无法锁定返回 -1.
}
int already_running(){
int fd;
char buf[16];
fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE); // "/var/run/judged.pid" (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)Permits the file's owner to read it.
if (fd < 0){
syslog(LOG_ERR|LOG_DAEMON, "can't open %s: %s", LOCKFILE, strerror(errno));
exit(1);
}
if(lockfile(fd) < 0){
if (errno == EACCES || errno == EAGAIN){
close(fd);
return 1;
}
syslog(LOG_ERR|LOG_DAEMON, "can't lock %s: %s", LOCKFILE, strerror(errno));
exit(1);
}
ftruncate(fd, 0); // 以写模式打开的文件,就理解为清空文件吧!先!
sprintf(buf, "%d", getpid()); // getpid() 返回当前进程标识,将当前进程标识写入到 /var/run/judged.pid
write(fd, buf, strlen(buf)+1);
return (0);
}
int daemon_init(void) {
pid_t pid;
if((pid = fork()) < 0) return(-1);
else if(pid != 0) exit(0); /* parent exit */
/* child continues */
setsid(); /* become session leader */
chdir(oj_home); /* change working directory */
umask(0); /* clear file mode creation mask */
close(0); /* close stdin */ // 切后台关闭这三个干嘛呀!
close(1); /* close stdout */
close(2); /* close stderr */
return(0);
}
int main(int argc, char** argv){
strcpy(oj_home, "/home/judge");
chdir(oj_home);
daemon_init(); // 切后台
if (strcmp(oj_home, "/home/judge") == 0 && already_running() ){
syslog(LOG_ERR|LOG_DAEMON, "This daemon program is already running!\n"); // err, daemon
return 1; // syslogd守护进程用于解决守护进程的日志记录问题,openlog、syslog和closelog,日志信息会写入syslog.conf文件指定的位置
}
init_mysql_conf(); // set the database info
signal(SIGQUIT, call_for_exit); // 输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
signal(SIGKILL, call_for_exit); // 无法处理和忽略。中止某个进程
signal(SIGTERM, call_for_exit); // 请求中止进程,kill命令缺省发送
int j = 1;
while (!STOP){ // start to run
while( j && (!init_mysql()) ){
j = work();
}
sleep(sleep_time); // judged 通过轮询数据库发现新任务,轮询间隔的休息时间,单位秒, 对应流程图。
j = 1;
}
return 0;
}
all: my_judged2.cc
g++ -Wall -c -I/usr/local/mysql/include/mysql -I/usr/include/mysql my_judged2.cc
g++ -Wall -o my_judged2 my_judged2.o -L/usr/local/mysql/lib/mysql -L/usr/lib/mysql -lmysqlclient
// Special thanks to teacher Zhang for helping me.