实现功能

  • Socket网络编程系列的核心系统调用
  • 服务器对于客户端正常结束的识别处理
  • 客户端基于命令行指令的退出实现方式
  • 服务器基于SIGINT信号的退出方式
  • SIGPIPE信号产生与识别处理方式
  • 多进程并发服务器与客户端的Socket编程核心系调用模式
  • 多进程应用程序如何有效规避僵尸进程的产生;
  • 简单的应用层PDU设计,构建,与解析
  • 文件的读写应用

单进程服务器端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <semaphore.h>
#include <sys/wait.h>
#include <sys/types.h>

#define MAX_MES 1024
#define MAX_CMD_STR 100
#define bprintf(fp, format, ...) \
if (fp == NULL) \
{ \
printf(format, ##__VA_ARGS__); \
} \
else \
{ \
printf(format, ##__VA_ARGS__); \
fprintf(fp, format, ##__VA_ARGS__); \
fflush(fp); \
}

#define COM_ADD struct sockaddr
int echo_rqt(int sockfd);
void sig_pipe(int signo);
int sig_type = 0, sig_to_exit = 0;

int install_sig_handlers();
void sig_int(int signo);

int main(int argc, char const *argv[])
{

if (argc != 3)
{
printf("Usage:%s <IP> <PORT>\n", argv[0]);
return -1;
}

install_sig_handlers();

FILE *fd;
fd = fopen("stu_srv_res_p.txt", "wb"); //父进程日志文件。

struct sockaddr_in srv_addr_v4, cli_addr;
socklen_t cli_addr_len;
cli_addr_len = sizeof(cli_addr); //用信号量解决最大并发数问腿。最大并发数由argv[]指定,包含父进程。第二个参数大于0表示进程共享。等于0表示线程共享。
int port = atoi(argv[2]); //argv[]指定port
inet_pton(AF_INET, argv[1], &srv_addr_v4.sin_addr); //argv[]指定ip
srv_addr_v4.sin_family = AF_INET;
srv_addr_v4.sin_port = htons(port);
bprintf(fd, "[srv] server[%s:%d] is initializing\n",argv[1], port);

int listenfd, confd;
listenfd = socket(AF_INET, SOCK_STREAM, 0);

int res = bind(listenfd, (COM_ADD *)&srv_addr_v4, sizeof(srv_addr_v4));
if (res)
{
printf("[srv] bind failed\n");
}

res = listen(listenfd, 5);
if (res)
{
printf("[srv] listen failed\n");
}

while (sig_to_exit != -1)
{
char h_addr[16] = {0};
int h_port;

confd = accept(listenfd, (COM_ADD *)&cli_addr, &cli_addr_len);

if ( confd ==-1 && errno == EINTR)
{
if (sig_type == SIGINT)
break;
}

inet_ntop(AF_INET, &cli_addr.sin_addr, h_addr, sizeof(h_addr));

h_port = ntohs(cli_addr.sin_port);
bprintf(fd, "[srv] client[%s:%d] is accepted\n", h_addr, h_port);

char child_log_filename[20];
memset(child_log_filename, 0, sizeof(child_log_filename));

sprintf(child_log_filename, "stu_srv_res_%d.txt", getpid());
fd = fopen(child_log_filename, "wb"); //子进程日志文件。
if (!fd)
{
printf("[srv] child exits, failed to open file \"stu_srv_res_%d.txt\"!\n", getpid());
exit(-1);
}

echo_rqt(confd);

close(confd);
bprintf(fd, "[srv] connfd is closed\n");
fclose(fd);
continue;
}

close(listenfd);
bprintf(fd, "[srv] listenfd is closed!\n");
bprintf(fd, "[srv] server is going to exiting!\n");

fclose(fd);
return 0;
}

int echo_rqt(int sock_confd)
{
int n_len;
int h_len;



// sprintf(send_data + 8,"hello word\n");
// sprintf(send_data1 + 8,"hello word\n");

do
{
int res;
res = read(sock_confd, &n_len, 4);
if(res == 0)
{
return -1;
}

h_len = ntohl(n_len);

char *rcv_data = malloc(h_len);
memset(rcv_data,0,sizeof(rcv_data));
do
{

memset(rcv_data, 0, sizeof(rcv_data));
res = read(sock_confd, rcv_data, h_len);


while (res > 0 && res < h_len)
{
res += read(sock_confd, rcv_data + res, h_len - res);
}

if (res < 0)
{
if (errno == EINTR)
{
if (sig_type == SIGINT)
{
return -1;
}
continue;
}
return -1;
}
if (!res)
{
return -1;
}

break;


} while (1);

//TODO 解析客户端echo_rqt数据并写入res文件;注意缓冲区起始位置哦
printf("[echo_rqt] %s\n", rcv_data);


write(sock_confd,&n_len,4);
res = write(sock_confd, rcv_data, h_len);

free(rcv_data);

//TODO 用write发送echo_rep数据并释放buf:
// break;
} while (1);
}

void sig_pipe(int signo)
{
// TODO 记录本次系统信号编号到sig_type中;通过getpid()获取进程ID,按照指导书上的要求打印相关信息,并设置sig_to_exit的值
sig_type = signo;
fprintf(stdout, "[srv] SIGPIPE is coming!\n");
}

void sig_child(int signo)
{
sig_type = signo;
pid_t pid_child;
fprintf(stdout, "[srv] SIGCHILD is coming!\n");
int stat;
while ((pid_child = waitpid(-1, &stat, WNOHANG)) > 0)
{
printf("[srv](%d) child terminated\n", pid_child);
};
}
void sig_int(int signo)
{
sig_type = signo;
sig_to_exit = -1;
fprintf(stdout, "[srv] SIGINT is coming!\n");
}

int install_sig_handlers()
{
int res;
struct sigaction sigact_pipe, old_sigact;
sigact_pipe.sa_handler = sig_pipe; //sig_pipe(),信号处理函数
sigact_pipe.sa_flags = 0;
sigact_pipe.sa_flags |= SA_RESTART; //设置受影响的慢系统调用重启
sigemptyset(&sigact_pipe.sa_mask);
res = sigaction(SIGPIPE, &sigact_pipe, &old_sigact);
if (res)
{
return -1;
}
// TODO 安装SIGCHLD信号处理器,若失败返回-2.这里可直接将handler设为SIG_IGN,忽略SIGCHLD信号即可,
// 注意和上述SIGPIPE一样,也要设置受影响的慢系统调用重启。也可以按指导书说明用一个自定义的sig_chld
// 函数来处理SIGCHLD信号(复杂些)

// TODO 安装SIGINT信号处理器,若失败返回-3
struct sigaction sigact_int;
sigact_int.sa_flags = 0;
sigact_int.sa_handler = sig_int;
sigemptyset(&sigact_int.sa_mask);
res = sigaction(SIGINT, &sigact_int, &old_sigact);
if (res)
{
return -3;
}
return 0;
}

单进程客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <semaphore.h>
#include <wait.h>
#define MAX_MES 1024
#define MAX_CMD_STR 100
#define bprintf(fp, format, ...) \
if (fp == NULL) \
{ \
printf(format, ##__VA_ARGS__); \
} \
else \
{ \
printf(format, ##__VA_ARGS__); \
fprintf(fp, format, ##__VA_ARGS__); \
fflush(fp); \
}

#define COM_ADD struct sockaddr
int echo_rqt(int sockfd);

int main(int argc, char const *argv[])
{

if (argc != 3)
{
printf("Usage:%s <IP> <PORT> \n", argv[0]);
return 0;
}

struct sockaddr_in cli_addr_v4;
int port = atoi(argv[2]); //argv[]指定port
inet_pton(AF_INET, argv[1], &cli_addr_v4.sin_addr); //argv[]指定ip
cli_addr_v4.sin_family = AF_INET;
cli_addr_v4.sin_port = htons(port);

int clip_sock_confd;
clip_sock_confd = socket(AF_INET, SOCK_STREAM, 0);

if (clip_sock_confd == -1)
{
printf("[cli] fail to create socket!\n");
}

int stat = connect(clip_sock_confd, (COM_ADD *)&cli_addr_v4, sizeof(cli_addr_v4));
if (stat == -1)
{
printf("[cli] wrong serve connection:%d\n%s", errno, strerror(errno));
clip_sock_confd = socket(AF_INET, SOCK_STREAM, 0);

}

else if (stat == 0)
{
printf("[cli] server[%s:%d] is connected!\n", argv[1], port); //argv[]指定
echo_rqt(clip_sock_confd);

}

close(clip_sock_confd);
printf("[cli] connfd is closed!\n");
printf("[cli] client is exiting!\n");
return 0;
}

int echo_rqt(int sock_confd)
{

int n_len;
int h_len;

char data[MAX_CMD_STR + 1] = {0};

while (fgets(data, MAX_CMD_STR, stdin))
{

if (strncmp(data, "exit", 4) == 0)
{
break;
}

h_len = strnlen(data, MAX_CMD_STR);
data[h_len - 1] = '\0';

char rcv_data[MAX_CMD_STR + 1];
memset(rcv_data, 0, sizeof(rcv_data));

n_len = htonl(h_len);
write(sock_confd, &n_len, 4);
write(sock_confd, data, h_len);

read(sock_confd, &n_len, 4);
h_len = ntohl(n_len);

int state = read(sock_confd, rcv_data, h_len);
while (state < h_len)
{
state += read(sock_confd, rcv_data + state, h_len - state);
}

printf("[echo_rep] %s\n", rcv_data);
}

return 0;
}