基于Linux的Socket编程之TCP全双工Server-Client聊天程序
来源:互联网 发布:家用洗地机 知乎 编辑:程序博客网 时间:2024/05/21 23:33
一、引言:
由于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;}
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;}
收到对端结束信息(NULL信息)的进程要向等待发送的进程发送一个结束通信的信号,回调函数处理使得等待输入的进程结束,否则该进程会一直等待,直到有输入(但此时的输入已经没有意义,所以应提早结束,而不是一直等待)。
三、测试结果:
这个不厚道的服务器结束了通信:
1 0
- 基于Linux的Socket编程之TCP全双工Server-Client聊天程序
- 基于Linux的Socket编程之TCP全双工Server-Client聊天程序
- 基于Linux的SOCKET编程之TCP半双工Client-Server聊天程序
- 基于Linux的SOCKET编程之TCP半双工Client-Server聊天程序
- python socket编程实现半双工与全双工聊天
- Linux Linux函数 Linux聊天程序 基于socket的TCP(有连接的)聊天程序
- Linux Linux函数 Linux聊天程序 基于socket的TCP(有连接的)聊天程序
- Java基于Tcp的socket聊天程序
- Java Socket编程 - 基于TCP方式的客户服务器聊天程序
- TCP的socket编程中“全双工的字节流”含义的深刻理解
- python全双工聊天窗口编程学习之旅
- Socket聊天程序(Client)
- 实现基于TCP/IP协议的简单Client/Server程序
- 套接字(socket)编程简单实现server-client聊天程序
- 基于TCP协议实现客户服务器的全双工通信
- Linux下基于TCP的Socket编程
- python socket编程 半双工聊天
- Linux网络编程之聊天程序(TCP协议之select)
- android核心基础day04
- python机器学习库scikit-learn简明教程之:SVM支持向量机
- bzoj 1948 [Ceoi2006]Connect 插头dp
- ExecuteNonQuery()返回受影响行数不适用select语句
- 查找算法之二分查找
- 基于Linux的Socket编程之TCP全双工Server-Client聊天程序
- ios中关于获取当前时间和截止时间的时间差
- aop 实现方法计时 日志
- 解决发布测试版和正式版修改N多代码的问题
- 那年,我开始编程
- 积性函数学习笔记
- 验证Xposed模块自身是否被启用
- CodeForces-716B. Complete the Word(模拟)
- 安卓APP版本更新及自动打开