基于Linux的Socket编程之TCP全双工Server-Client聊天程序

来源:互联网 发布:数据库常用地约束包括 编辑:程序博客网 时间:2024/05/22 05:09

转载:http://blog.csdn.net/apollon_krj/article/details/53437764#0-tsina-1-58570-397232819ff9a47a7b7e80a40613cfe1

一、引言:

由于accept函数、read、write、recv、send等函数都是是阻塞式的,在同一个进程之中,只要有任何一个函数没有执行完毕,处于阻塞状态,之后的函数与功能就不能处理,很难实现点对点的Server-Client全双工通信。因为全双工通信是非阻塞式的通信方式,即使对方没有回复消息,都可以随时发送。如果只是电报机式的半双工通信,之前已经基本实现:基于Linux的SOCKET编程之TCP半双工Client-Server聊天程序 
而对于QQ点对点聊天式的全双工通信,又该怎样实现呢?对于当前所学只能想到使用fork函数创建一个子进程,其中父进程用来处理发(或者收),而子进程用来处理收(或者发)的过程。fork函数的一些基本的使用可参照:进程创建与fork()的恩怨情仇

二、测试代码:

测试环境(Redhat 6.4)

1、客户端(Client):

# include<stdio.h># include<stdlib.h># include<string.h># include<unistd.h># include<sys/socket.h># include<arpa/inet.h># include<netinet/in.h># include<signal.h># define MAX_BUF_LEN 128/*处理系统调用中产生的错误*/void error_print(char * ptr){        perror(ptr);        exit(EXIT_FAILURE);}/*处理通信结束时回调函数接收到的信号*/void quit_tranmission(int sig){    printf("recv a quit signal = %d\n",sig);    exit(EXIT_SUCCESS);}int main(void){    int sockfd = socket(AF_INET, SOCK_STREAM, 0);    if(sockfd < 0)        error_print("socket");    struct sockaddr_in servaddr;    bzero(&servaddr,sizeof(servaddr));    servaddr.sin_family = AF_INET;    servaddr.sin_port = 1234;    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");    /*inet_aton("127.0.0.1",&servaddr.sin_addr);*/    int conn;    if((conn = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0)        error_print("connect");    pid_t pid;    pid = fork();    if(pid == -1){        error_print("fork");    }    if(pid == 0){        char recv_buf[MAX_BUF_LEN] = {0};        while(1){            bzero(recv_buf,sizeof(recv_buf));            int ret = read(sockfd, recv_buf, sizeof(recv_buf));            if(ret == -1)                error_print("read");            else if(ret == 0){                printf("server is close!\n");                break;//子进程收到服务器端退出的信息(服务器Ctrl+C结束通信进程,read函数返回值为0,退出循环)            }            fputs(recv_buf,stdout);/*将收到的信息输出到标准输出stdout上*/        }        close(sockfd);/*子进程退出,通信结束关闭套接字*/        kill(getppid(),SIGUSR1);/*子进程结束,也要向父进程发出一个信号告诉父进程终止接收,否则父进程一直会等待输入*/        exit(EXIT_SUCCESS);/*子进程正常退出结束,向父进程返回EXIT_SUCCESS*/    }    else{        signal(SIGUSR1,quit_tranmission);/*回调函数处理通信中断*/        char send_buf[MAX_BUF_LEN] = {0};        /*如果服务器Ctrl+C结束通信进程,fgets获取的就是NULL,否则就进入循环正常发送数据*/        while(fgets(send_buf,sizeof(send_buf), stdin) != NULL){            int set = write(sockfd, send_buf, strlen(send_buf));/*将send_buf缓冲区的数据发送给对端服务器*/            if(set < 0)                error_print("write");            bzero(send_buf,strlen(send_buf));        }        close(sockfd);/*通信结束,关闭套接字*/    }    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

2、服务器(Server):

# include<stdio.h># include<stdlib.h># include<string.h># include<unistd.h># include<sys/socket.h># include<arpa/inet.h># include<netinet/in.h># include<signal.h># define MAX_BUF_LEN 128void error_print(char * ptr){        perror(ptr);        exit(EXIT_FAILURE);}void quit_tranmission(int sig){    printf("recv a quit signal = %d\n",sig);    exit(EXIT_SUCCESS);}int main(void){    int sockfd = socket(AF_INET, SOCK_STREAM, 0);/*IPV4流式协议即TCP协议*/    if(sockfd < 0)        error_print("socket");    struct sockaddr_in servaddr;    bzero(&servaddr,sizeof(servaddr));    servaddr.sin_family = AF_INET;/*IPV4*/    servaddr.sin_port = 1234;    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*使用本地环回地址做测试*/    /*inet_aton("127.0.0.1",&servaddr.sin_addr);//与inet_addr函数作用相同*/    /*setsockopt确保服务器不用等待TIME_WAIT状态结束就可以重启服务器,继续使用原来的端口号*/    int on = 1;    if( setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0)        error_print("setsockopt");    /*绑定本地Socket地址*/    if(bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)        error_print("bind");    /*监听连接*/    if(listen(sockfd, SOMAXCONN) < 0)        error_print("listen");    struct sockaddr_in peeraddr;/*存储连接成功的客户端Socket信息*/    socklen_t peerlen = sizeof(peeraddr);    int conn;    /*接收监听队列第一个完成连接的请求*/    if((conn = accept(sockfd,(struct sockaddr*)&peeraddr,&peerlen)) < 0)        error_print("accept");    pid_t pid;    pid = fork();/*创建一个新的子进程*/    if(pid == -1){        error_print("fork");    }    if(pid == 0){/*子进程中用来向客户端发送数据*/        signal(SIGUSR1,quit_tranmission);/*回调函数处理通信中断*/        char send_buf[MAX_BUF_LEN]={0};        /*如果客户端Ctrl+C结束通信进程,fgets获取的就是NULL,否则就进入循环正常发送数据*/        while(fgets(send_buf, sizeof(send_buf), stdin) != NULL){            write(conn,send_buf,strlen(send_buf));            bzero(send_buf,strlen(send_buf));/*发送完成清空发送缓冲区*/        }        exit(EXIT_SUCCESS);/*成功退出子进程*/    }    else{        char recv_buf[MAX_BUF_LEN]={0};        while(1){            bzero(recv_buf,strlen(recv_buf));            int ret = read(conn, recv_buf, sizeof(recv_buf));/*读取conn连接发送过来的数据*/            if(ret < 0)                error_print("read");            else if(ret == 0){                printf("client is close!\n");                break;//父进程收到服务器端退出的信息(服务器Ctrl+C结束通信进程,read函数返回值为0,退出循环)            }            fputs(recv_buf,stdout);        }        kill(pid,SIGUSR1);/*父进程结束,也要向子进程发出一个信号告诉子进程终止接收,否则子进程会一直等待输入*/    }    close(conn);    close(sockfd);    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

收到对端结束信息(NULL信息)的进程要向等待发送的进程发送一个结束通信的信号,回调函数处理使得等待输入的进程结束,否则该进程会一直等待,直到有输入(但此时的输入已经没有意义,所以应提早结束,而不是一直等待)。

三、测试结果:

这个不厚道的服务器结束了通信:

这里写图片描述


阅读全文
0 0
原创粉丝点击