1服务器功能要求:
1 ,通过命令行指定监听的端口;
2,程序放到后台运行,并通过syslog记录程序的运行出错、调试日志;
3, 程序能够捕捉kill信号正常退出;
4, 服务器要支持多个客户端并发访问,可以选择多路复用、多进程或多线程任意一种实现;
5, 服务器收到每个客户端的数据都解析后保存到数据库中,接收到的数据格式为: “ID/时间/温度”,如RPI0001/2019-0105 11:40:30/30.0C”;
2代码:
(源码:https://gitee.com/yangjianing1/fork_server.git)
首先要了解服务器与客户端两者之间的关系:
首先判断端口连接是否正常
```c
int main(int argc, char *argv[])
{
int rv;
int ret;
int opt;
int idx;
int port;
int log_fd;
int ch = 1;
int daemon_run = 0;
int ser_fd = -1;
int cli_fd = -1;
pid_t pid = -1;
struct sockaddr_in ser_addr;
struct sockaddr_in cli_addr;
socklen_t cliaddr_len = 20;
/*
deamon:程序后台运行
port:指定服务器开设端口
help:打印帮助信息
*/
struct option opts[] = {
{"daemon", no_argument, NULL, 'd'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while ((opt = getopt_long(argc, argv, "dp:h", opts, &idx)) != -1)
{
switch(opt)
{
case 'd':
daemon_run = 1;
break;
case 'p':
port = atoi(optarg); //将字符串形式的端口号转换成整型
break;
case 'h':
print_usage(argv[0]); //打印帮助信息
return 0;
}
}
if (!port)
{
print_usage(argv[0]);
return 0;
}
建立日志
if (daemon_run)
{
printf("Program %s is running at the background now\n", argv[0]);
//建立日志系统
log_fd = open("receive_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;
}
}
建立socket连接
ser_fd = socket(AF_INET, SOCK_STREAM, 0); //第一个参数为协议域,这里使用AF_INET(32位ipv4地址、16位端口)
//第二个参数为type,指定socket类型,这里使用SOCK_STREAM
//第三个参数为指定协议,当其为0时,自动选择与type类型相对应的默认协议
if (ser_fd < 0)
{
printf("Creat socket failure:%s\n", strerror(errno));
return 0;
}
printf("Creat the ser_fd[%d]!\n",ser_fd);
memset(&ser_addr, 0, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(port); //调用htons(),将端口号由主机字节序转变成网络字节序
//设置服务器IP地址(INADDR_ANY,代表监听所有IP),并调用htonl(0,将其由主机字节序转变成网络字节序
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
setsockopt(ser_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&ch, sizeof(ch));
//绑定服务器地址(IP+端口),用于提供服务
rv = bind(ser_fd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
if (rv < 0)
{
printf("Bind server's port failure:%s\n", strerror(errno));
return 0;
}
printf("The port[%d] has been bind by this server!\n",port);
之后根据流程图用listen来接受客户端的请求(此处用多进程调用fork进行处理)
listen(ser_fd, BACKLOG);
//调用listen(),将主动类型的socket套接字变为被动类型(等待客户端连接)
//第一个参数为sockfd,指定所要监听的socket套接字,用于接收外部请求
//第二个参数为backlog, TCP连接是一个过程,内核会在进程空间中维护一个连接队列以跟踪与服务器建立连接但还未着手处理的连接
while (!g_stop)
{
//接受来自客户端的请求,并创建一个cli_fd与客户端进行连接
cli_fd = accept(ser_fd, (struct sockaddr *)&cli_addr, &cliaddr_len);
if (cli_fd < 0)
{
printf("Accept the request from client failure:%s\n", strerror(errno));
continue;
}
pid = fork(); //调用fork(),实现多进程
if (pid < 0)
{
printf("Creat child process failure:%s.\n", strerror(errno));
continue;
}
else if (pid > 0)
{
close(cli_fd); //父进程关闭cli_fd,保留ser_fd,等待与其他客户端连接
continue;
}
else if (pid == 0)
{
close(ser_fd); //子进程关闭ser_fd,保留cli_fd,保持与客户端连接
建立数据库储存信息
int a;
int len;
char *sn;
char *cut;
char *temp;
char buf[512];
char *datetime;
char *zErrMsg = NULL;
char sql1[128];
//描述创建数据库中表的信息
char *sql = "create table if not exists temperature(sn char(10), datetime char(50), temperature char(10))";
sqlite3 *db = NULL;
//调用sqlite3_open(),打开.db数据库文件。若没有,则创建
len = sqlite3_open("temper.db",&db);
if (len)
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
else
{
printf("You have opened a sqlite3 database named temper.db successfully!\n");
}
//创建temperature表,用来存放来自客户端的数据
ret = sqlite3_exec(db, sql, 0, 0, &zErrMsg);
if (ret != SQLITE_OK)
{
sqlite3_close(db);
printf("Creat table failure \n");
return 0;
}
printf("Create table successfully!\n");
printf("Child process start to communicate with client by cli_fd[%d]...\n", cli_fd);
printf("Now ready to connect the client and recive message from client...\n");
printf("\n");
while循环
while (1) //设置while循环,持续接收来自客户端的信息
{
memset(buf, 0, sizeof(buf));
a = read(cli_fd, buf, sizeof(buf)); //从buf中读数据(客户端写入)
if (a < 0) //返回值小于零,则表示接收数据出错
{
printf("Read information from client failure:%s\n", strerror(errno));
close(cli_fd);
exit(0);
}
else if (a == 0) //返回值等于0,则表示客户端主动断开连接
{
printf("The connection with client has broken!\n");
close(cli_fd);
exit(0);
}
else //返回值大于零,则表示接收数据成功
{
printf("The message recived from client[%s,%d] is \"%s\"\n",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
//对接收到的数据进行分割操作,便于存储至数据库
cut = strtok(buf, "/");
if (cut != NULL)
{
sn = cut;
//printf("%s\n", sn);
if ((cut = strtok(NULL, "/")) != NULL)
{
datetime = cut;
//printf("%s\n", datetime);
if ((cut = strtok(NULL, "/")) != NULL)
{
temp = cut;
//printf("%s\n", temp);
}
}
}
snprintf(sql1, 128, "insert into temperature values('%s', '%s', '%s');",
sn, datetime, temp);
sql1[127] = '\0';
printf("%s\n", sql1);
//将数据存储至temperature表中
ret = sqlite3_exec(db, sql1, 0 , 0, &zErrMsg);
if (ret != SQLITE_OK)
{
sqlite3_close(db);
printf("insert data failure ; %s!\n", zErrMsg);
return 0;
}
printf("insert data successfully!\n");
printf("\n");
}
}
}
}
close(ser_fd);
return 0;
}
oid print_usage(char *progname)
{
printf("-d(--daemon):let program run in the background.\n");
printf("-p(--port):enter server port.\n");
printf("-h(--help):print this help information.\n");
return ;
}
void sig_stop(int signum)
{
if (SIGUSR1 == signum) /判断捕捉到信号是否为SIGUSR1
{
g_stop = 1; //g_stop为1,while(!g_stop)循环结束,程序关闭
}
return ;
}
项目总结:
首先通过这个项目让自己对socket网络通信有了一定的基础,对TCP/IP有了一定的理解,TCP的网络连接中包括了一个四元组:源IP,目的IP,和源端口IP,目的端口,如同购买快递时的发件人和发件人地址,收件人和收件地址一样。之后会尝试时候多线程 ,多路复用去进行多个服务端的并发访问。