[C语言]TCP协议\并发 十进制数字转换为二进制数字。

服务端

PS:服务端不会自行停止,只有手动输入任意终止信号后方可终止。

服务端基本逻辑

首先构造套接字、网络地址结构、绑定、侦听。

使用主进程进行侦听,其余处理函数进入循环内进行操作。

构造出子进程实现并发机制,其中循环内不断对太平间信号进行捕获,进行收尸操作。

//基于TCP协议的客户端和服务器
#include<stdio.h>
#include<string.h>
#include<stdlib.h> //atoi
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<signal.h>
#include<sys/wait.h>
#include<errno.h>

//信号处理函数
void diebodys(int signum){
    for(;;){
        pid_t pid =  waitpid(-1,NULL,WNOHANG);
        if(pid == -1){
            if(errno == ECHILD){
                printf("服务器:子进程回收完毕!\n");
                break;
            }else{
                perror("waitpid");
                return;
            }
        }else if(pid == 0){
            printf("服务器:子进程阻塞,跳过!\n");
            break;
        }else{
            printf("服务器:回收了%d的僵尸\n",pid);
        }
        }
    }



int main(){
    //对子进程进行回收
    if(signal(SIGCHLD,diebodys) == SIG_ERR) {
        perror("signal");
        return -1;
    }
    //创建套节字
    printf("创建套节字\n");
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(fd == -1){
        perror("socket");
        return -1;
    }
    //构造网络地址结构
    printf("构造网络地址结构\n");
    struct sockaddr_in moiii;
    moiii.sin_family = AF_INET;
    moiii.sin_port = htons(19993);
    moiii.sin_addr.s_addr = INADDR_ANY;
    //将套节字和地址结构绑定在一起
    printf("套节字与地址结构绑定\n");
    if(bind(fd,(struct sockaddr*)&moiii,sizeof(moiii)) == -1){
        perror("bind");
        return -1;
    }
    //启动侦听使之成为侦听套节字
    printf("启动侦听\n");
    if(listen(fd,1024) == -1){
        perror("listen");
        return -1;
    }
    for(;;){
    //与客户端建立通信
    struct sockaddr_in client;
    socklen_t clientlen = sizeof(client);
    int newfd = accept(fd,(struct sockaddr*)&client,&clientlen);
    if(newfd == -1){
        perror("accept");
        return -1;
    }
    //业务处理
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    if(pid == 0 ){
        printf("关闭子进程侦听\n");
    close(fd);
    for(;;){
    char buf[128]= {};
    char newbuf[128] = {};
    int count = 0;
    ssize_t size = read(newfd,buf,sizeof(buf)-1);
    if(size == -1){
        perror("read");
        return -1;
    }
    if(size == 0){
        break;
    }
/*
   if(read(newfd,buf,sizeof(buf)-1) == -1){
        perror("read");
        return -1;
    } 
    if(read(newfd,buf,sizeof(buf)-1) == 0){
        printf("hhahahahhaha\n");
        break;
    }
*/
    int num = atoi(buf);
    printf("客户端发来的数字:%s\n",buf);
    for(;;){
        if((num%2 == 0) && (num/2 == 0)){
            break;
        }
        else if(num%2 == 1){
            num = num/2;
            newbuf[count] = '1';
            count++;
        }else{
            num = num/2;
            newbuf[count] = '0';
            count++;
        }
        }
    newbuf[count+1] = '\0';
    for(int left=0,right=count-1;right>left;left++,right--){
        char vary = newbuf[left];
        newbuf[left] = newbuf[right];
        newbuf[right] = vary;
    }
    printf("处理后二进制:%s\n",newbuf);

    if(write(newfd,newbuf,strlen(newbuf)) == -1){
        perror("write");
        return -1;
    }
   
    }
    printf("关闭子进程套节字\n");
    close(newfd); //关闭子进程套节字.
    }
    printf("关闭父进程通信套节字\n");
    close(newfd);
    }
    return 0 ;
}

客户端

PS:服务器可能无需终止,但客户端需要做终止操作。

客户端基本逻辑

首先构造套接字、服务端网络地址结构、绑定。

循环内写入接收与发送,终止条件等。

//基于TCP协议的客户端和服务器
#include<stdio.h>
#include<string.h>
#include<stdlib.h> //atoi
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<signal.h>
#include<sys/wait.h>
#include<errno.h>



int main(){
    //创建套节字
    printf("创建套节字\n");
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(fd == -1){
        perror("socket");
        return -1;
    }
    //服务端地址结构
    struct sockaddr_in moiii;
    moiii.sin_family = AF_INET;
    moiii.sin_port = htons(19993);
    moiii.sin_addr.s_addr = inet_addr("服务器IP地址");
    //套节字和服务器地址结构连接
    if(connect(fd,(struct sockaddr*)&moiii,sizeof(moiii)) == -1){
        perror("connect");
        return -1;
    }
    //客户端业务处理
    for(;;){
        char buf[128] = {};
        fgets(buf,sizeof(buf),stdin);

        if(strcmp(buf,"#\n") == 0){
            break;
        }
        printf("向服务端发送:%s\n",buf);
        if(send(fd,buf,strlen(buf),0) == -1){
            perror("send");
            return -1;
        }
        if(recv(fd,buf,sizeof(buf),0) == -1){
            perror("recv");
            return -1;
        } 

        printf("转换为二进制数字:%s\n",buf);

    }
    printf("关闭套节字\n");
    close(fd);
    return 0;
}

注意易错点

read函数的使用

使用read函数时,要时刻提醒它是一个阻塞函数,使用时要注意用法千万不要把主进程阻塞。

其中被注释掉的这部分代码如下:
/*
if(read(newfd,buf,sizeof(buf)-1) == -1){
perror("read");
return -1;
}
if(read(newfd,buf,sizeof(buf)-1) == 0){
printf("hhahahahhaha\n");
break;
}
*/
为什么使用这部分代码会导致客户端关闭后服务端死循环,而使用这部分代码,就可以正常运行:
ssize_t size = read(newfd,buf,sizeof(buf)-1);
if(size == -1){
perror("read");
return -1;
}
if(size == 0){
break;
}

这部分代码的作用是读取客户端发送的数据,并判断是否读取完毕或遇到错误。两种代码的区别在于读取数据的方式和判断读取状态的条件。

在被注释掉的代码中,read函数被调用两次,并且没有将读取到的数据保存到变量 size 中。第一次调用 read 函数用于判断是否读取到数据,如果返回值为 -1,表示读取出错;如果返回值为 0,表示客户端关闭了连接。然后,又调用了一次 read 函数,用于读取数据,但这次读取的返回值并没有被使用。

而在保留的代码中,通过一次 read 函数调用将读取的数据保存到变量 size 中,然后根据 size 的值进行判断。如果 size 为 -1,表示读取出错;如果 size 为 0,表示客户端关闭了连接。这样可以保证读取的数据被正确处理,同时可以正确判断客户端是否关闭连接。

因此,保留的代码能够正常运行,而被注释掉的代码会导致服务端进入死循环,因为它没有正确判断客户端的状态,而是一直尝试读取数据。当客户端关闭连接后,服务端的 read 函数仍然在阻塞等待数据,因此导致死循环。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值