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

转载: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信息)的进程要向等待发送的进程发送一个结束通信的信号,回调函数处理使得等待输入的进程结束,否则该进程会一直等待,直到有输入(但此时的输入已经没有意义,所以应提早结束,而不是一直等待)。

三、测试结果:

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

这里写图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384390.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构(一)线性表

数据结构&#xff08;一&#xff09;线性表一、线性表定义二、顺序表定义动态数组三、单链表定义不带头结点带头结点头结点与不带头结点的区别头插法与尾插法双链表循环链表循环单链表循环双链表静态链表一、线性表定义 线性表是具有相同数据类型的n个数据元素的有限序列 特点…

linux网络编程(二)TCP通讯状态

linux网络编程&#xff08;二&#xff09;TCP通讯状态TCP状态转换为什么需要等待2MSL&#xff1f;端口复用TCP状态转换 tcp协议连接开始会经过三次握手&#xff0c;客户端和服务器开始都会处于CLOSED状态 第一次握手&#xff1a;客户端会先发送SYN请求给服务器&#xff0c;客户…

gethostbyname() 函数说明

转载&#xff1a;http://www.cnblogs.com/cxz2009/archive/2010/11/19/1881611.html gethostbyname()函数说明——用域名或主机名获取IP地址 包含头文件 #include <netdb.h> #include <sys/socket.h> 函数原型 struct hostent *gethostbyna…

linux网络编程(三)select、poll和epoll

linux网络编程&#xff08;三&#xff09;select、poll和epoll一、为什么会有多路I/O转接服务器&#xff1f;二、select三、poll三、epoll一、为什么会有多路I/O转接服务器&#xff1f; 为什么会有多路I/O转接服务器呢&#xff1f;在学这个之前&#xff0c;我们同使用的是多线…

Linux socket编程(一) 对套接字操作的封装

转载:http://www.cnblogs.com/-Lei/archive/2012/09/04/2670942.html 以前写的&#xff0c;现在回顾一下&#xff1a; 下面是对socket操作的封装&#xff0c;因为在Linux下写中文到了windows里面会乱码&#xff0c;所以注释用英文来写&#xff0c;有空再查下解决方法吧 socket.…

数据结构(二)栈

栈一、栈顺序栈线性栈(不带头结点)线性栈(带头结点)共享栈一、栈 栈是只允许在一端进行插入或删除操作的线性表。 栈顶&#xff1a;线性表允许进行插入删除的那一端 栈底&#xff1a;固定的&#xff0c;不允许进行插入和删除的那一端 空栈&#xff1a;不含如何元素的空表 顺序…

如何在linux上安装sqlite数据库

如何在linux上安装sqlite数据库一、下载二、解压三、配置&#xff08;configure&#xff09;四、编译和安装五、执行sqlite3程序六、测试代码一、下载 首先要先下载sqlite3源码包 链接&#xff1a;https://pan.baidu.com/s/1_70342ZLlPjLlqGzpy5IHw 提取码&#xff1a;84ne …

数据结构(三)队列

数据结构&#xff08;三&#xff09;队列队列队列&#xff08;顺序存储&#xff09;循环队列&#xff08;顺序存储&#xff09;队列&#xff08;链式存储&#xff09;队列 队列是一种受限制的线性表&#xff0c;只允许表的一端插入&#xff0c;在表的另一端删除 队列&#xf…

Linux fcntl函数详解

转载&#xff1a;http://www.cnblogs.com/xuyh/p/3273082.html 功能描述&#xff1a;根据文件描述词来操作文件的特性。 文件控制函数 fcntl -- file control 头文件&#xff1a; #include <unistd.h> #include <fcntl.h> 函数原型&#xff1a; …

vs2019使用sqlite数据库远程连接linux

vs2019使用sqlite数据库远程连接linux一、sqlite3添加到目录二、添加依赖库三、测试一、sqlite3添加到目录 将两个sqlite3头文件放入目录中 二、添加依赖库 打开项目属性 添加完成 三、测试 #include <stdio.h> #include <sqlite3.h>int main(int argc, cha…

linux网络编程(四)线程池

linux网络编程&#xff08;四&#xff09;线程池为什么会有线程池&#xff1f;实现简单的线程池为什么会有线程池&#xff1f; 大多数的服务器可能都有这样一种情况&#xff0c;就是会在单位时间内接收到大量客户端请求&#xff0c;我们可以采取接受到客户端请求创建一个线程的…

AIGC:大语言模型LLM的幻觉问题

引言 在使用ChatGPT或者其他大模型时&#xff0c;我们经常会遇到模型答非所问、知识错误、甚至自相矛盾的问题。 虽然大语言模型&#xff08;LLMs&#xff09;在各种下游任务中展示出了卓越的能力&#xff0c;在多个领域有广泛应用&#xff0c;但存在着幻觉的问题&#xff1a…

关于C++子类父类成员函数的覆盖和隐藏

转载&#xff1a;http://blog.csdn.net/worldmakewayfordream/article/details/46827161 函数的覆盖 覆盖发生的条件&#xff1a; &#xff08;1&#xff09; 基类必须是虚函数&#xff08;使用virtual 关键字来进行声明&#xff09; &#xff08;2&#xff09;发生覆盖的两个函…

数据结构(四)串的顺序存储

#include <stdio.h> #include <stdlib.h>#define MAXLEN 255 //定长顺序存储 typedef struct {char ch[MAXLEN]; //每个分量存储一个字符int length; //串的实际长度 }SString;//串的初始化 bool StrAssign(SString& T, char* chars) {int i 0, len;char* …

数据结构(四)串的动态数组存储

#include <stdio.h> #include <stdlib.h>#define MAXLEN 255 //定长顺序存储 typedef struct {char* ch; //每个分量存储一个字符int length; //串的实际长度 }SString;//串的初始化 bool StrAssign(SString& T, char* chars) {int i 0, len;T.ch (char*)m…

C++名字隐藏

转载&#xff1a;http://www.weixueyuan.net/view/6361.html 如果派生类中新增一个成员变量&#xff0c;该成员变量与基类中的成员变量同名&#xff0c;则新增的成员变量就会遮蔽从基类中继承过来的成员变量。同理&#xff0c;如果派生类中新增的成员函数与基类中的成员函数同…

c语言深入浅出(一)strcpy和memcpy的区别

c语言深入浅出&#xff08;一&#xff09;strcpy和memcpy的区别strcpy和memcpy都是c语言的库函数 strcpy:只用于字符串的复制&#xff0c;当碰到‘\0’就停止了 memcpy:用于这个内存的拷贝&#xff0c;适用于结构体、字符数组、类等 char * strcpy(char * dest, const char * s…

C++ dynamic_cast操作符

转载&#xff1a;http://www.weixueyuan.net/view/6377.html 在C中&#xff0c;编译期的类型转换有可能会在运行时出现错误&#xff0c;特别是涉及到类对象的指针或引用操作时&#xff0c;更容易产生错误。Dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。…

数据结构(五)树

数据结构&#xff08;五&#xff09;树一、基本操作树是n个节点的有限集&#xff0c;它是一种递归的数据结构 一、基本操作 #include <stdio.h> #include <stdlib.h> #include <iostream>#define Elemtype charusing namespace std; typedef struct BiTNod…

C++ typeid操作符

转载&#xff1a;http://www.weixueyuan.net/view/6378.html typeid操作符用于判断表达式的类型&#xff0c;注意它和sizeof一样是一个操作符而不是函数。如果需要使用typeid操作符&#xff0c;最好加上typeinfo头文件。 给出以下定义 int a;double b;char * c;long d; 下表列…