新手请教一个Linux下一个C语言socket编程的问题

抽搐人偶师 2020-04-19 05:27:48
想要使用socket实现一个双向通信。然后任意一方输入quit就可以退出,最开始我实现的是单向的服务器发送消息客户端接收消息,我的功能可以正常实现退出。后来我使用fork实现双向通信以后,输入quit就不能正常退出了,会卡住。只会显示连接关闭但是没办法回到命令提示符,这时候如果继续输入就会卡住,对方也不会接收到这段消息,但是如果我把对方的shell关掉,那么我就会回到命令行下,刚才输入的内容就会被逐行当作命令执行,到底是怎么回事呢??求个解释,截图如下:

代码如下:
server.c
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<unistd.h>

#define MYPORT 3490 //侦听端口号
#define BACKLOG 100 //侦听队列长度
#define MAXDATASIZE 1024 //一次可以读的最大的字节数

int main(int argc, char* argv[])
{
int serverfd; //服务器socket套接字描述符
int communicationfd = 0; //双方通信描述符
int link = 0; //连接状态,根据accept函数返回值改变,初始未连接时为0
struct sockaddr_in serveraddr; //服务器地址信息
struct sockaddr_in clientaddr; //客户端地址信息
int clientsinsize; //客户端地址信息结构体大小
//1获得服务器套接字描述符
if((serverfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Server Socket Failed!");
exit(1);
}
//2构造服务器的地址信息sockaddr_in
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(MYPORT); //使用网络字节序端口号
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //网络字节序IP
bzero(&(serveraddr.sin_zero), 8);
//3绑定侦听端口
if(bind(serverfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)) == -1)
{
perror("Server Bind Failed!");
exit(1);
}
//4监听端口
if(listen(serverfd, BACKLOG) == -1)
{
perror("Server Listen Failed!");
exit(1);
}
while(link == 0)
{
//5接受客户端的连接请求
printf("等待连接中......\n");
clientsinsize = sizeof(struct sockaddr_in);
communicationfd = accept(serverfd, (struct sockaddr*)&clientaddr, &clientsinsize);
if(communicationfd == -1)
{
perror("Server Accept faild");
exit(1);
}
if(communicationfd > 0) //accept函数返回值大于0时表示接收了连接请求,返回的即是通信所需要的文件描述符
link = 1;
}
int clientip = clientaddr.sin_addr.s_addr;
printf("Got Connection From %d.%d.%d.%d\n", clientip&255,(clientip>>8)&255,(clientip>>16)&255,(clientip>>24)&255);
printf("现在可以开始通信了!\n");
//主循环,接受连接请求后link值变为1
pid_t fpid; //创建子进程,让它处理和父进程不同的工作
fpid = fork();
while(link==1)
{
if(fpid > 0)
{
//6父进程读取消息
char buf[MAXDATASIZE];
int numbytes = recv(communicationfd, buf, MAXDATASIZE, 0);
if(numbytes == -1)
{
perror("Receive Failed!");
exit(1);
}
else if(numbytes > 0)
{
buf[numbytes] = '\0';
printf("对方(Client)发来的消息: \n");
printf("*****");
printf("%s\n", buf);
}
else
{
printf("对方已经关闭连接!\n");
link = 0;
}
}
else
{
//7子进程发送消息
char msg[1024];
char judge[1024] = "quit";
scanf("%s", msg);
if(strcmp(msg, judge)==0)
{
link = 0;
close(serverfd);
close(communicationfd);
printf("连接已经被关闭!\n");
return 0;
}
else
{
printf("我(Server)发送的消息:\n");
printf("*****");
printf("%s\n", msg);
if(send(communicationfd, msg, 1024, 0) == -1)
{
perror("Send Failed");
continue;
}
}
}
}
return 0;
}

client.c如下:
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<unistd.h>

#define PORT 3490
#define MAXDATASIZE 1024 //一次可以读的最大的字节数

int main(int argc, char* argv[])
{
int clientfd, numbytes; //客户端socket描述符和接收到数据大小
int link = 1 ; //表示连接状态,由connect函数赋值,连接时为0,未连接时为1。
char buf[MAXDATASIZE]; //读取的缓冲区
struct sockaddr_in serveraddr; //服务器地址信息结构体
//1创建客户端socket,并获取客户端socket描述符
if((clientfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Client Socket Failed!");
exit(1);
}
//2构造服务器的地址信息sockaddr_in结构
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT); //使用网络字节序端口号
serveraddr.sin_addr.s_addr = inet_addr("192.168.1.101"); //将点分十进制IP地址转化为网络字节序IP
bzero(&(serveraddr.sin_zero), 8);
//3向服务器发起连接
printf("等待服务器响应连接请求......\n");
link = connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr));
if(link == -1)
{
perror("Connect Failed!");
exit(1);
}
if(link==0)
printf("已经成功连接至服务器!\n");
else
printf("与服务器连接失败!\n");
//将connect函数返回值赋给link,为0则表示连接成功,开始读取缓冲区内容
pid_t fpid; //创建子进程,让它处理和父进程不同的工作
fpid = fork();
while(link==0)
{
if(fpid > 0)
{
//4父进程读取消息
numbytes = recv(clientfd, buf, MAXDATASIZE, 0);
if(numbytes == -1)
{
perror("Receive Failed!");
exit(1);
}
else if(numbytes > 0)
{
buf[numbytes] = '\0';
printf("对方(Server)发来的消息: \n");
printf("*****");
printf("%s\n", buf);
}
else
{
printf("对方已经关闭连接!\n");
link = 1;
}
}
else
{
//5子进程用来发送消息
char msg[1024];
char judge[1024] = "quit";
scanf("%s", msg);
if(strcmp(msg, judge)==0)
{
close(clientfd);
printf("连接已经被关闭!\n");
link = 1;
}
else
{
printf("我(Client)发送的消息:\n");
printf("*****");
printf("%s\n", msg);
if(send(clientfd, msg, 1024, 0) == -1)
{
perror("Send Failed");
continue;
}
}
}
}
return 0;
}
...全文
281 10 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
抽搐人偶师 2020-04-22
  • 打赏
  • 举报
回复
引用 9 楼 bo_self_effacing 的回复:
稍微调整下不就好了
我昨天搞得头头昏了,把ppid函数写成pid了,现在改对了,多谢这几天的指导
tobybo 2020-04-21
  • 打赏
  • 举报
回复
client.c

//5子进程用来发送消息
char msg[1024];
char judge[1024] = "quit";
scanf("%s", msg);

//if(strcmp(msg, judge)==0)
if(strcmp(msg, judge)==0 || getppid() == 1) //改为这样
{
    close(clientfd);
    printf("连接已经被关闭!\n");
    exit(0);
}
else
{   
    printf("我(Client)发送的消息:\n");
    printf("*****");
    printf("%s\n", msg);
    if(send(clientfd, msg, 1024, 0) == -1)
    {
        perror("Send Failed");
        continue;
    }
}
解决办法: 你两边代码都这样改试试,因为我没调试过; 原理是 父进程退出后,子进程的父进程id会被设置为1, 子进程通过这个来判断父进程是否退出。 延伸思考: 另外,我查了下资料才清楚,我之前觉得你的父进程退出,子进程不应该受影响,应该一直在scanf那里等待数据输入; 翻阅了 <<UNIX环境高级编程>> 第三版 孤儿进程组那一节 后才似乎清楚: 书上的原话是: “父进程终止后,进程组 ( 我的注释:'进程有一个组id,为父进程的id,虽然子进程的父进程id被设置为1,但是进程组id并没有改变' ) 包含一个停止的进程,进程组成为孤儿进程组,POSIX.1 要求向新孤儿进程组中处于停止状态的没一个进程发送挂断信号(SIGHUP),接着又向其发送继续信号(SIGCONT) 在处理了挂断信号后,子进程继续......” 再结合 http://www.cocoachina.com/articles/84766 这个博客的测试结果来分析; 先是SIGCONT信号唤醒了卡在 scanf 的子进程,然后子进程向下执行的过程中,执行了你的三句printf,之后被SIGHUP信号终止进程。 这里又有个问题了,SIGHUP 为什么没有在执行 printf 之前就终止进程呢, 我猜测是 内核响应SIGCONT信号后,得到了继续运行的命令,然后就继续执行,然后内核处理第二个信号 SIGHUP ,就是说两个信号处理之间是有间隔的,这间隔 和 SIGCONT的默认处理机制【继续运行】的合作 就让你的三句printf 执行了。 你这个小问题让我翻了好几页书,不错不错。
tobybo 2020-04-21
  • 打赏
  • 举报
回复
稍微调整下不就好了
tobybo 2020-04-21
  • 打赏
  • 举报
回复
我调试下你代码,你可以先子进程 添加 SIGCONT的处理函数 执行退出
抽搐人偶师 2020-04-21
  • 打赏
  • 举报
回复
引用 5 楼 bo_self_effacing 的回复:
client.c

//5子进程用来发送消息
char msg[1024];
char judge[1024] = "quit";
scanf("%s", msg);

//if(strcmp(msg, judge)==0)
if(strcmp(msg, judge)==0 || getppid() == 1) //改为这样
{
    close(clientfd);
    printf("连接已经被关闭!\n");
    exit(0);
}
else
{   
    printf("我(Client)发送的消息:\n");
    printf("*****");
    printf("%s\n", msg);
    if(send(clientfd, msg, 1024, 0) == -1)
    {
        perror("Send Failed");
        continue;
    }
}


解决办法:
你两边代码都这样改试试,因为我没调试过;
原理是 父进程退出后,子进程的父进程id会被设置为1, 子进程通过这个来判断父进程是否退出。

延伸思考:
另外,我查了下资料才清楚,我之前觉得你的父进程退出,子进程不应该受影响,应该一直在scanf那里等待数据输入;

翻阅了 <<UNIX环境高级编程>> 第三版 孤儿进程组那一节 后才似乎清楚:
书上的原话是:
“父进程终止后,进程组 ( 我的注释:'进程有一个组id,为父进程的id,虽然子进程的父进程id被设置为1,但是进程组id并没有改变' ) 包含一个停止的进程,进程组成为孤儿进程组,POSIX.1 要求向新孤儿进程组中处于停止状态的没一个进程发送挂断信号(SIGHUP),接着又向其发送继续信号(SIGCONT)
在处理了挂断信号后,子进程继续......”
再结合 http://www.cocoachina.com/articles/84766 这个博客的测试结果来分析;

先是SIGCONT信号唤醒了卡在 scanf 的子进程,然后子进程向下执行的过程中,执行了你的三句printf,之后被SIGHUP信号终止进程。
这里又有个问题了,SIGHUP 为什么没有在执行 printf 之前就终止进程呢,
我猜测是
内核响应SIGCONT信号后,得到了继续运行的命令,然后就继续执行,然后内核处理第二个信号 SIGHUP ,就是说两个信号处理之间是有间隔的,这间隔 和 SIGCONT的默认处理机制【继续运行】的合作 就让你的三句printf 执行了。

你这个小问题让我翻了好几页书,不错不错。
还是不行,没有解决
抽搐人偶师 2020-04-21
  • 打赏
  • 举报
回复
引用 5 楼 bo_self_effacing 的回复:
client.c

//5子进程用来发送消息
char msg[1024];
char judge[1024] = "quit";
scanf("%s", msg);

//if(strcmp(msg, judge)==0)
if(strcmp(msg, judge)==0 || getppid() == 1) //改为这样
{
    close(clientfd);
    printf("连接已经被关闭!\n");
    exit(0);
}
else
{   
    printf("我(Client)发送的消息:\n");
    printf("*****");
    printf("%s\n", msg);
    if(send(clientfd, msg, 1024, 0) == -1)
    {
        perror("Send Failed");
        continue;
    }
}


解决办法:
你两边代码都这样改试试,因为我没调试过;
原理是 父进程退出后,子进程的父进程id会被设置为1, 子进程通过这个来判断父进程是否退出。

延伸思考:
另外,我查了下资料才清楚,我之前觉得你的父进程退出,子进程不应该受影响,应该一直在scanf那里等待数据输入;

翻阅了 <<UNIX环境高级编程>> 第三版 孤儿进程组那一节 后才似乎清楚:
书上的原话是:
“父进程终止后,进程组 ( 我的注释:'进程有一个组id,为父进程的id,虽然子进程的父进程id被设置为1,但是进程组id并没有改变' ) 包含一个停止的进程,进程组成为孤儿进程组,POSIX.1 要求向新孤儿进程组中处于停止状态的没一个进程发送挂断信号(SIGHUP),接着又向其发送继续信号(SIGCONT)
在处理了挂断信号后,子进程继续......”
再结合 http://www.cocoachina.com/articles/84766 这个博客的测试结果来分析;

先是SIGCONT信号唤醒了卡在 scanf 的子进程,然后子进程向下执行的过程中,执行了你的三句printf,之后被SIGHUP信号终止进程。
这里又有个问题了,SIGHUP 为什么没有在执行 printf 之前就终止进程呢,
我猜测是
内核响应SIGCONT信号后,得到了继续运行的命令,然后就继续执行,然后内核处理第二个信号 SIGHUP ,就是说两个信号处理之间是有间隔的,这间隔 和 SIGCONT的默认处理机制【继续运行】的合作 就让你的三句printf 执行了。

你这个小问题让我翻了好几页书,不错不错。
多谢指导,我再继续尝试一下,刚学习遇到的问题多多
抽搐人偶师 2020-04-20
  • 打赏
  • 举报
回复
引用 2 楼 bo_self_effacing 的回复:
你可以在父进程里注册child信号的处理函数,在处理函数里执行父进程的退出,在子进程退出的时候系统
会发对应信号给父进程的
希望能再给我解答一下问题,谢谢
抽搐人偶师 2020-04-20
  • 打赏
  • 举报
回复
引用 2 楼 bo_self_effacing 的回复:
你可以在父进程里注册child信号的处理函数,在处理函数里执行父进程的退出,在子进程退出的时候系统
会发对应信号给父进程的

我现在把代码改得能够正常退出了
代码如下
server.c
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<unistd.h>
#include<signal.h>

#define MYPORT 3490 //侦听端口号
#define BACKLOG 100 //侦听队列长度
#define MAXDATASIZE 1024 //一次可以读的最大的字节数

void dealsig(int sig)
{
exit(0);
}

int main(int argc, char* argv[])
{
int serverfd; //服务器socket套接字描述符
int communicationfd = 0; //双方通信描述符
int link = 0; //连接状态,根据accept函数返回值改变,初始未连接时为0
struct sockaddr_in serveraddr; //服务器地址信息
struct sockaddr_in clientaddr; //客户端地址信息
int clientsinsize; //客户端地址信息结构体大小
//1获得服务器套接字描述符
if((serverfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Server Socket Failed!");
exit(1);
}
//2构造服务器的地址信息sockaddr_in
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(MYPORT); //使用网络字节序端口号
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //网络字节序IP
bzero(&(serveraddr.sin_zero), 8);
//3绑定侦听端口
if(bind(serverfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)) == -1)
{
perror("Server Bind Failed!");
exit(1);
}
//4监听端口
if(listen(serverfd, BACKLOG) == -1)
{
perror("Server Listen Failed!");
exit(1);
}
while(link == 0)
{
//5接受客户端的连接请求
printf("等待连接中......\n");
clientsinsize = sizeof(struct sockaddr_in);
communicationfd = accept(serverfd, (struct sockaddr*)&clientaddr, &clientsinsize);
if(communicationfd == -1)
{
perror("Server Accept faild");
exit(1);
}
if(communicationfd > 0) //accept函数返回值大于0时表示接收了连接请求,返回的即是通信所需要的文件描述符
link = 1;
}
int clientip = clientaddr.sin_addr.s_addr;
printf("Got Connection From %d.%d.%d.%d\n", clientip&255,(clientip>>8)&255,(clientip>>16)&255,(clientip>>24)&255);
printf("现在可以开始通信了!\n");
//主循环,接受连接请求后link值变为1
pid_t fpid; //创建子进程,让它处理和父进程不同的工作
fpid = fork();
pid_t waitchildpid; //用来检测子进程是否结束
if(fpid > 0)
{
while(1)
{
signal(SIGCHLD, dealsig);
waitchildpid=waitpid(-1, 0, WNOHANG);
printf("PID: %d\n",waitchildpid);
if(waitchildpid == 0)
{
//6父进程读取消息
char buf[MAXDATASIZE];
int numbytes = recv(communicationfd, buf, MAXDATASIZE, 0);
if(numbytes == -1)
{
perror("Receive Failed!");
exit(1);
}
else if(numbytes > 0)
{
buf[numbytes] = '\0';
printf("对方(Client)发来的消息: \n");
printf("*****");
printf("%s\n", buf);
}
else
{
printf("对方已经关闭连接!\n");
return 0;
}
}
else
{
return 0;
}
}
}
else
{
while(1)
{
//7子进程发送消息
char msg[1024];
char judge[1024] = "quit";
scanf("%s", msg);
if(strcmp(msg, judge)==0)
{
close(serverfd);
close(communicationfd);
printf("连接已经被关闭!\n");
exit(0);
}
else
{
printf("我(Server)发送的消息:\n");
printf("*****");
printf("%s\n", msg);
if(send(communicationfd, msg, 1024, 0) == -1)
{
perror("Send Failed");
continue;
}
}
}
}
return 0;
}

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

#define PORT 3490
#define MAXDATASIZE 1024 //一次可以读的最大的字节数

void dealsig(int sig)
{
exit(0);
}


int main(int argc, char* argv[])
{
int clientfd, numbytes; //客户端socket描述符和接收到数据大小
int link = 1 ; //表示连接状态,由connect函数赋值,连接时为0,未连接时为1。
char buf[MAXDATASIZE]; //读取的缓冲区
struct sockaddr_in serveraddr; //服务器地址信息结构体
//1创建客户端socket,并获取客户端socket描述符
if((clientfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Client Socket Failed!");
exit(1);
}
//2构造服务器的地址信息sockaddr_in结构
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT); //使用网络字节序端口号
serveraddr.sin_addr.s_addr = inet_addr("192.168.1.104"); //将点分十进制IP地址转化为网络字节序IP
bzero(&(serveraddr.sin_zero), 8);
//3向服务器发起连接
printf("等待服务器响应连接请求......\n");
link = connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr));
if(link == -1)
{
perror("Connect Failed!");
exit(1);
}
if(link == 0)
printf("已经成功连接至服务器!\n");
else
printf("与服务器连接失败!\n");
//将connect函数返回值赋给link,为0则表示连接成功,开始读取缓冲区内容
pid_t fpid; //创建子进程,让它处理和父进程不同的工作
fpid = fork();
pid_t waitchildpid; //用来检测子进程是否结束
if(fpid > 0)
{
while(1)
{
signal(SIGCHLD, dealsig);
waitchildpid=waitpid(-1, 0, WNOHANG);
printf("PID: %d\n",waitchildpid);
if(waitchildpid == 0)
{
//4父进程读取消息
numbytes = recv(clientfd, buf, MAXDATASIZE, 0);
if(numbytes == -1)
{
perror("Receive Failed!");
exit(1);
}
else if(numbytes > 0)
{
buf[numbytes] = '\0';
printf("对方(Server)发来的消息: \n");
printf("*****");
printf("%s\n", buf);
}
else
{
printf("对方已经关闭连接!\n");
return 0;
}
}
else
{
return 0;
}
}
}
else
{
while(1)
{
//5子进程用来发送消息
char msg[1024];
char judge[1024] = "quit";
scanf("%s", msg);
if(strcmp(msg, judge)==0)
{
close(clientfd);
printf("连接已经被关闭!\n");
exit(0);
}
else
{
printf("我(Client)发送的消息:\n");
printf("*****");
printf("%s\n", msg);
if(send(clientfd, msg, 1024, 0) == -1)
{
perror("Send Failed");
continue;
}
}
}
}
return 0;
}

但是会又有了新的问题,在我主动断开连接时,主动断开连接的一方能够正常退出回到命令提示符,被动断开连接的一方却会出现问题,提示符会多输出三行文字,不管之前接受过多少消息,就是正好三行,不多也不会少,这到底是哪里的问题呢?找了好久没找到
tobybo 2020-04-20
  • 打赏
  • 举报
回复
你可以在父进程里注册child信号的处理函数,在处理函数里执行父进程的退出,在子进程退出的时候系统 会发对应信号给父进程的
tobybo 2020-04-20
  • 打赏
  • 举报
回复
你用一个内存值link来标记是否连接中,但是一旦子进程修改了这个值,写时复制机制就会给子进程copy一份新的内存,复进程的link并没有变化

23,217

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 应用程序开发区
社区管理员
  • 应用程序开发区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧