c - 当我强制退出服务器(输入 clt + c)时,有什么办法可以做到这一点,客户端也会同时终止?
问题描述
编写两个独立的 C 程序,一个用于 TCP 服务器(处理单个用户的请求),另一个用于客户端。
在服务器端 - 创建一个套接字并侦听某个特定端口以处理客户端请求。
存在一个包含 n 行的默认文件,服务器应该能够处理来自客户端的 READX 和 WRITEX 请求。
- 服务器进程应该对从客户端接收到的字符串进行标记,该字符串可能包含以下格式的 READX 或 WRITEX 请求 -
- READX k- 从文件开头读取第 k 行并返回给客户端。
- WRITEX msg - 将 msg 字符串附加到服务器上存在的文件末尾并返回“SUCCESS!!” 给客户作为确认。
在客户端——
- 客户端进程应该从用户那里获取输入,无论是在服务器端读取还是写入。
- 然后它启动与服务器的连接并将查询转发到服务器。
- 从服务器接收输出并将其显示给用户。
我几乎处理了所有的错误处理。一切正常。
- 我的第一个查询是否强制终止服务器程序(例如通过输入 clt+ c),我希望客户端程序同时终止。我怎样才能做到这一点?
- 第二个查询:当我强行杀死客户端时,我的服务器程序会自动终止。但是,我不知道为什么会这样?一般来说。它不会发生。我没有找到我的代码的哪一行使这种情况发生。
服务器代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<unistd.h>
#define MYPORT "5000"
#define BACKLOG 3
char space[] = " ";
/*this funtion is counting total no of line present in input.txt file
whenever it is called */
int count_line()
{
FILE *ptr = fopen("input.txt", "r");
char * line = NULL;
int i = 0;
size_t count =0;
if (ptr == NULL)
{
perror("error: line no 21");
}
while(getline(&line,&count, ptr) > 0)
{
i++;
}
fclose(ptr);
return i;
}
/* This funtion will write the message what is given by client at the
end of the file */
int write_instruction(char *buffer,int max_length)
{
char final[255];
FILE *fp = fopen("input.txt", "a");
if(fseek(fp, 0, SEEK_END) != 0)
{
perror("fseek:error");
exit(EXIT_FAILURE);
}
if (count_line() == 250)
{
sprintf(final,"%d%s%s",count_line() +1, space, buffer);
}
else
sprintf(final,"%s%d%s%s","\n",count_line() +1, space, buffer);
fputs(final, fp);
fclose(fp);
return (1);
}
/* This function will fetch the exact line from input.txt what is instructed by READX in client and will return to client */
void read_instruction(int line_no, int max_length, int server_new_fd)
{
ssize_t no_read_byte;
/*error checking , if you enter out of bound line no*/
if ((line_no > count_line()) || (line_no <1))
{
if( ( no_read_byte = write(server_new_fd, "you are entering out of bound line no: TRY AGAIN", strlen("you are entering out of bound line no: TRY AGAIN")+1)) == -1)
{
perror("write:error");
exit(EXIT_FAILURE);
}
return;
}
char *line = NULL, final[max_length];
size_t count =0;
FILE *stream = fopen("input.txt", "r");
if (stream == NULL)
exit(EXIT_FAILURE);
for (int i = 0;i < line_no; ++i)
{
if(getline(&line, &count, stream) == -1)
{
perror("getline: error");
}
}
if( ( no_read_byte = write(server_new_fd, line, strlen(line)+1)) == -1)
{
perror("write:error");
exit(EXIT_FAILURE);
}
free(line);
fclose(stream);
return ;
}
void *get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main()
{
int status, server_sockfd, yes = 1, server_new_fd;
struct addrinfo hints, *ref, *p;
struct sockaddr_storage addr;
struct sockaddr_in m;
struct sockaddr_in6 n;
socklen_t addrlen;
ssize_t no_read_byte;
size_t count = 1024;
char ip_str[INET6_ADDRSTRLEN], buffer[1024], *readx, *writex;
memset(&hints, 0, sizeof hints);/* setting all bits of hints 0;*/
/* AF_UNSPEC mean any address family */
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
if ( (status = getaddrinfo(NULL, MYPORT, &hints, &ref)) != 0)
{
fprintf(stderr, "getaddrinfo : %s\n", gai_strerror(status));
return 2;
}
for ( p = ref; p != NULL; p = p->ai_next)
{
/*creating socket where passing ai_family , ai_socktype, ai_protocol as domain, type, protocol.
And chhecking one by one struct addrinfo from list returned by getaddrinfo(), to get first successful socket descriptor.*/
if ( (server_sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
{
perror("socket:error");
continue;
}
if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (int)) == -1)
{
perror("setsockopt : error");
exit(1);
}
if (bind(server_sockfd, p->ai_addr, p->ai_addrlen) == -1)
{
perror("bind : error");
close(server_sockfd);
continue;
}
/* I am getting out of this for loop because I already got successful socket fd, and successful binding.
I don't need to traverse the list anymore.
*/
break;
}
freeaddrinfo(ref);// I am done with struct addrinfo list.
/* I am listening here.*/
if (listen(server_sockfd, BACKLOG) == -1)
{
perror("listen : error");
exit(1);
}
printf("server: waiting for connection...........\n");
addrlen = sizeof(addr); /* If there are any client trying to connect, I accept here its connect request. */
if( (server_new_fd = accept(server_sockfd, (struct sockaddr *)&addr, &addrlen)) == -1)
{
perror("accept: error");
exit(EXIT_FAILURE);
}
inet_ntop(addr.ss_family, get_in_addr((struct sockaddr *)&addr), ip_str, sizeof ip_str);
printf("Server : %s is connected .\n", ip_str);
int i=0;
close(server_sockfd); /* WE are here just hanldling one client, so we do need to listen anymore. so we are closing server_sockfd */
while(1)
{
memset(&buffer, 0, 1024); /* Begining of every loop, we shall set '\0' to every byte of buffer */
/*we read here first time for every loop from server_new_fd and save it into buffer*/
if( (no_read_byte = read(server_new_fd, &buffer, count)) == -1)
{
perror("read failed");
exit(EXIT_FAILURE);
}
writex = buffer;
/*we are checking error here. when you will give just empty string, that will be detected here*/
if( (readx = strtok_r( writex, " ", &writex)) == NULL)
{
if( ( no_read_byte = write(server_new_fd, "you are entering invalid input", strlen("you are entering invalid input")+1) == -1))
{
perror("write:error");
exit(EXIT_FAILURE);
}
continue;
}/* here we are checking for shutdown condition . if you want to shutdown just enter -1*/
else if (strcmp(readx, "-1") == 0)
{
printf("we are terminating\n");
if( (no_read_byte = write(server_new_fd,"-1", strlen("-1")+1) ) == -1)
{
perror("write: error");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
/* if you enter just READX or WRITEX , then that will be detected here. */
else if ((atoi(writex) == 0) && (strcmp(readx, "READX") ==0) )
{
if( ( no_read_byte = write(server_new_fd, "you are entering invalid input", strlen("you are entering invalid input")+1)) == -1)
{
perror("write:error");
exit(EXIT_FAILURE);
}
continue;
}
else if ((writex[0] == 0) && (strcmp(readx, "WRITEX") ==0 ))
{
if( ( no_read_byte = write(server_new_fd, "you are entering invalid input", strlen("you are entering invalid input")+1)) == -1)
{
perror("write:error");
exit(EXIT_FAILURE);
}
continue;
}
/* this for READX formatted instruction */
else if( strcmp(readx, "READX") ==0)
{
char * str = strtok_r( writex, " ", &writex);
if (atoi(writex) != 0)
{
/* if you enter like READX 12 34 45, that will be detected here */
if( ( no_read_byte = write(server_new_fd, "you are entering invalid input", strlen("you are entering invalid input")+1)) == -1)
{
perror("write:error");
exit(EXIT_FAILURE);
}
continue;
}
else /* This for correct formatted READX instruction */
{
printf("Client is instructing to read and return line.\n");
read_instruction(atoi(str), 255, server_new_fd);
}
} /* this for correct WRITEX instruction */
else if (strcmp(readx, "WRITEX") ==0)
{
printf("Client is instructing to append given string to end of the file.\n");
if(write_instruction(writex, 255) == 1)
{
if( (no_read_byte = write(server_new_fd,"SUCCESS!!", strlen("SUCCESS!!")+1) ) == -1)
{
perror("write: error");
exit(EXIT_FAILURE);
}
}
}
else /* this for all other invalid error */
{
if( ( no_read_byte = write(server_new_fd, "you are entering invalid input 1", strlen("you are entering invalid input 1 ")+1)) == -1)
{
perror("write:error");
exit(EXIT_FAILURE);
}
continue;
}
}
close(server_new_fd);
}
客户端代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<unistd.h>
#define MYPORT "5000"
void *convert(struct sockaddr *sa)
{
if ( sa->sa_family == AF_INET)
return &(((struct sockaddr_in *)sa)->sin_addr);
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "you are entering wrong number of string\n" );
exit(EXIT_FAILURE);
}
struct addrinfo hints, *ref, *p;
int status, client_sockfd, yes = 1;
ssize_t no_read_byte;
size_t count=1024;
struct sockaddr_storage addr;
char ip_str[INET6_ADDRSTRLEN], buffer[1024];
memset(&hints, 0, sizeof hints);
if ( (status = getaddrinfo(argv[1], MYPORT, &hints, &ref)) != 0)
{
fprintf(stderr, "getaddrinfo : %s\n", gai_strerror(status));
return 2;
}
for (p = ref;p != NULL; p = p->ai_next)
{
if ( (client_sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
{
perror("client_socket: error");
continue;
}
if ( (connect(client_sockfd, p->ai_addr, p->ai_addrlen)) == -1)
{
perror("connect: error");
close(client_sockfd);
continue;
}break;
}
if (p == NULL) {
fprintf(stderr, "client: failed to connect\n");
exit(EXIT_FAILURE);
}
//inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), ip_str, sizeof ip_str);
inet_ntop(p->ai_family, convert((struct sockaddr *)p->ai_addr), ip_str, sizeof ip_str);
//printf("%s\n",convert((struct sockaddr *)p->ai_addr));
printf("client: connecting to %s\n", ip_str);
freeaddrinfo(ref);
while(1){
printf("please enter the instruction: ");
gets(buffer);
if ((no_read_byte = write(client_sockfd, buffer, strlen(buffer)+1)) == -1)
{
perror("write:error");
exit(EXIT_FAILURE);
}
bzero(buffer, 255);
if((no_read_byte = read(client_sockfd, buffer, 255)) == -1)
{
perror("read:error");
exit(EXIT_FAILURE);
}
else if (strcmp(buffer, "-1") == 0)
{
exit(EXIT_SUCCESS);
}
else
printf("%s\n",buffer );
bzero(buffer, 255);
}
close(client_sockfd);
}
解决方案
我不确定我是否正确阅读了您的代码,但在我看来,如果读取零字节(由客户端或服务器),则不会产生任何消息。 读取(2) 可以返回零:
返回值 如果成功,则返回实际读取的字节数。在读取文件结尾时,返回零。否则,返回 -1 并设置全局变量 errno 以指示错误。
文件结束不被视为错误。这是远程关闭连接的正常指示,当程序终止时会自动发生。
因此,对于任何具有未完成的读取操作(阻塞,等待来自对等方的数据)的进程,read (2) 将在对等方终止时返回零。如果您终止服务器并且客户端正在读取,则客户端将看到读取的字节为零,反之亦然。
您的客户端逻辑对这种情况没有做任何特别的事情。你测试 -1。如果不是 -1,则在(空)缓冲区中测试一堆东西。然后你退出。
显式检查零,并写一条服务器关闭连接的消息。我想你会看到客户端没有“自动终止”。它只是从尽头掉下来。
推荐阅读
- kubernetes - Kubernetes 节点消息 Unable to authentication the request due to an error: invalid bearer token
- mysql - 奇怪的行为优化查询索引(MariaDB + InnoDB)
- spring-integration - Spring 集成:如何拦截所有 spring 集成组件并获取属性值以记录
- arrays - 从json响应访问对象值反应本机
- java - 使用 for 循环嵌套下拉菜单会导致过时的元素引用错误
- c# - 如何在 C# 中执行通用 SQL 命令?
- rest - EnvelopeDefinition:EventNotification API 类型的基本身份验证
- ios - 在 XCODE 中使用 Distr 证书组装后,Iphone 无法安装我的应用程序
- java - 使用 Kotlin 属性访问语法设置 Java 字段是否安全
- arrays - 根据多个数组条件计算表中的行数