linux——两个客户端之间实现聊天(TCP、单线程)

两个客户端实现聊天功能,那么服务器作转发信息的作用,客户端A先将信息发送到服务器,在由服务器将信息发送到客户端B,客户端B也是一样。客户端与服务器都应该有两个执行流,服务器的一个执行流不断的接收客户端A的信息并将其发送给客户端B,另一个执行流不断地接收客户端B的信息并将其发送给客户端A,而客户端的两个执行流分别做读信息操作和写信息操作。这是我们的常规思维,如果用单线程的方法有该如何做呢?
socket称之为网络套接字,但其实也是一个文件描述符,这个文件描述符被默认为阻塞状态,accept函数如果没有客户端与之相连就一直阻塞在这里,程序不会在执行下去,read函数也是一样,如果从缓冲区中没有读到数据就会被阻塞,直到读到数据时才能退出这个函数。如下图,clientA向server发送一个data1,server读到了这个data1后在发送给clientB,如果clientA并没有发送信息,此时read函数就会阻塞,函数卡在read函数这,那么此时如果clientB发来一个data2,server根本读不到,但是这个data2已经放在了缓冲区里,当clientA发来消息后,read函数便不再阻塞,并将这条消息发送给clientB,此时server才能从缓冲区里读到data2。
在这里插入图片描述
既然是默认为阻塞,那么也可以设置为非阻塞,在非阻塞状态下,read函数读到数据就返回所读取数据的个数,没有读到数据就立即返回0,此时便不会出现无法发送数据或者发完数据后才接收到上一条信息。
我们可用如下方法设置阻塞

 #include <unistd.h>
#include <fcntl.h>
int flags=fcntl(sockid,F_GETFL,0);
fcntl(sockid,F_SETFL,flags|O_NONBLOCK);

sockid表示套接字,也是文件描述符,想要设置谁非阻塞就填谁的套接字

设置非阻塞方法:

 #include <unistd.h>
#include <fcntl.h>
int flags=fcntl(sockid,F_GETFL,0);
fcntl(sockid,F_SETFL,flags&~O_NONBLOCK);

服务器

#include <stdio.h>
#include <time.h>
#include <fcntl.h>
#include <sys/select.h>
#include <string.h>
#include <unistd.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define clientA 0
#define clientB 1
#define max_client 2  //最大连接客户端数量typedef struct clientNew
{char     *addr;int      sockid;uint16_t port;
}client_new;//存放所连客户端的地址和socket等信息void client_new_init(client_new c_new[])
{int i=0;for(;i<max_client;i++){c_new[i].addr=NULL;c_new[i].sockid=0;c_new[i].port=0;}
}void setsocket_noblock(client_new c_new[])
{//设置所连的两个客户端socket非阻塞int flags=fcntl(c_new[clientA].sockid,F_GETFL,0);fcntl(c_new[clientA].sockid,F_SETFL,flags|O_NONBLOCK);flags=fcntl(c_new[clientB].sockid,F_GETFL,0);fcntl(c_new[clientB].sockid,F_SETFL,flags|O_NONBLOCK);
}void  read_clientA(client_new c_new[],int *flag)
{char receive[100]={0};if(read(c_new[clientA].sockid,receive,sizeof(receive))>0)//读取A客户端信息,如果没读到数据就返回0{time_t timep;//获取A客户端发来信息的时间time(&timep);if(strcmp(receive,"quit\n")==0)//如果A客户端发来quit,先把quit发给B客户端,在结束聊天{printf("ip=%s %s 用户发起退出\n",c_new[clientA].addr,ctime(&timep));write(c_new[clientB].sockid,receive,strlen(receive));usleep(1000);*flag=0;return ;}printf("ip=%s %s: ",c_new[clientA].addr,ctime(&timep));printf("%s\n",receive);write(c_new[clientB].sockid,receive,strlen(receive));//将A客户端发来的信息转发给B客户端}
}void  read_clientB(client_new c_new[],int *flag)
{char receive[100]={0};if(read(c_new[clientB].sockid,receive,sizeof(receive))>0)//读取B客户端信息,如果没读到数据就返回0{time_t timep;time(&timep);if(strcmp(receive,"quit\n")==0){printf("ip=%s %s 用户发起退出\n",c_new[clientA].addr,ctime(&timep));write(c_new[clientA].sockid,receive,strlen(receive));usleep(1000);*flag=0;return ;}printf("ip=%s: %s:",c_new[clientB].addr,ctime(&timep));printf("%s\n",receive);write(c_new[clientA].sockid,receive,strlen(receive));//将B客户端发来的信息转发给A客户端}
}void  communication(client_new c_new[],int server_sockid)
{	int flag=1;while(flag){read_clientA(c_new,&flag);read_clientB(c_new,&flag);}close(server_sockid);
}int internet(client_new c_new[])
{struct sockaddr_in sockaddr;sockaddr.sin_family=AF_INET;sockaddr.sin_port=htons(5188);sockaddr.sin_addr.s_addr=htonl(INADDR_ANY);int server_sockid=socket(AF_INET,SOCK_STREAM,0);const int on=1;if(setsockopt(server_sockid,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)//设置端口可重复利用{printf("setsockopt\n");return 0;}if(bind(server_sockid,(struct sockaddr *)&sockaddr,sizeof(sockaddr))<0){printf("bind\n");return 0;}if(listen(server_sockid,SOMAXCONN)<0){printf("listen\n");return 0;}struct sockaddr_in other_sock_addr;socklen_t other_sock_addr_len=sizeof(other_sock_addr);int j=0;while(j!=max_client)//连接两个客户端{int sockid_client=accept(server_sockid,(struct sockaddr *)&other_sock_addr,&other_sock_addr_len);c_new[j].sockid =sockid_client;c_new[j].addr=inet_ntoa(other_sock_addr.sin_addr);c_new[j].port=ntohs(other_sock_addr.sin_port);printf("ip=%s,port=%d  已连接\n",c_new[j].addr,c_new[j].port);j++;}return server_sockid;
}int main()
{client_new c_new[max_client];		//定义结构体数组client_new_init(c_new);				//初始化结构体数组int server_sockid=internet(c_new);	//网络连接并返回服务器socketsetsocket_noblock(c_new);			//设置所连的两个客户端socket非阻塞communication(c_new,server_sockid);	//通信return 0;
}

客户端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/stat.h>void do_read(int sockid,int *flag)
{char receive[100]={0};int r_size=read(sockid,receive,sizeof(receive));if(strcmp(receive,"quit\n")==0){printf("对方已结束聊天\n");*flag=0;return;}if(r_size>0){printf("\t\t\t");fputs(receive,stdout);}
}void do_write(int sockid,int *flag)
{char send[100]={0};int w_size=read(0,send,sizeof(send));if(strcmp(send,"quit\n")==0){printf("您已下线\n");write(sockid,send,sizeof(send));*flag=0;return;}if(w_size>0){write(sockid,send,sizeof(send));memset(send,0,strlen(send));}
}int internet()
{int  flag=1;struct sockaddr_in addr;addr.sin_family=AF_INET;addr.sin_port=htons(5188);addr.sin_addr.s_addr=inet_addr("127.0.0.1");int sockid=socket(AF_INET,SOCK_STREAM,0);socklen_t addrlen=sizeof(addr);if(connect(sockid,(struct sockaddr *)&addr,addrlen)<0){printf("connect\n");return 0;}int flags=fcntl(sockid,F_GETFL,0);fcntl(sockid,F_SETFL,flags|O_NONBLOCK);flags=fcntl(0,F_GETFL,0);fcntl(0,F_SETFL,flags|O_NONBLOCK);while(flag){do_read(sockid,&flag);do_write(sockid,&flag);}close(sockid);return 0;
}int main()
{internet();return 0;
}

以上程序只能实现局域网内通信。实现跨局域网聊天请点击

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

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

相关文章

Java ClassLoader getSystemClassLoader()方法与示例

ClassLoader类getSystemClassLoader()方法 (ClassLoader Class getSystemClassLoader() method) getSystemClassLoader() method is available in java.lang package. getSystemClassLoader()方法在java.lang包中可用。 getSystemClassLoader() method is used to find the Sys…

zabbix邮件通知,短信通知配置详解

一、使用邮件发送报警1、前提条件是zabbix我们已经安装完成2、在官网我们下载msmtp的文件http://sourceforge.net/projects/msmtp/files/msmtp/1.4.32/msmtp-1.4.32.tar.bz2/download tar xf msmtp-1.4.32.tar.bz2 cd msmtp-1.4.32 ./configure--prefix/usr/local/msmtp make m…

关于怎么获取jsp的web站点的目录问题

发布了自己的web站点之后&#xff0c;想要访问站点文件夹下的某个文本文件&#xff0c;但是却不知道怎么找到文件根目录&#xff0c;一直尝试总是找不到文件……好不容易在网上翻了堆代码找到两句我现在急用的……so//根目录路径的获取System.out.println(request.getSession()…

开篇词:如何轻松获得 Offer

你好,我是王磊,某上市公司技术研发经理,前奇虎 360 员工,有着 10 余年的编程工作经验,目前主要负责新员工技术面试和构建企业技术架构的相关事宜。随着面试过的人数增加,我发现面试者们暴露出了技术方面的很多问题,为了让更多面试者少走一些弯路,也为了让企业能招到合适…

Java类类getGenericSuperclass()方法及示例

类类getGenericSuperclass()方法 (Class class getGenericSuperclass() method) getGenericSuperclass() method is available in java.lang package. getGenericSuperclass()方法在java.lang包中可用。 getGenericSuperclass() method is used to return the Type denoting th…

linux——客户端服务器文件传输

实现文件传输并不难&#xff0c;只需用fopen、fread、fwrite、fclose这几个函数对文件操作即可。文本文件就不说了&#xff0c;我们就已下图为例。 我们先来看看这个图片文件里装的是什么&#xff0c;我们以notpad打开这个图片&#xff0c;结果如下&#xff0c;是一堆乱码。 …

Android第二十五期 - 猜歌小游戏

代码已经整理好了&#xff0c;如下效果图&#xff1a;地址&#xff1a;http://yunpan.cn/cArkdixh5NbpQ 提取码 9300转载于:https://blog.51cto.com/liangxiao/1579433

第 1-2 课:你不知道的基础数据类型和包装类 + 面试题

基本数据类型 Java 基础数据按类型可以分为四大类&#xff1a;布尔型、整数型、浮点型、字符型&#xff0c;这四大类包含 8 种基础数据类型。 布尔型&#xff1a;boolean整数型&#xff1a;byte、short、int、long浮点型&#xff1a;float、double字符型&#xff1a;char 八种…

TomCat JDK环境变量

TomCat安装之前要安装JDK&#xff0c;安装完之后添加环境变量&#xff1b;变量名&#xff1a;Java_Home 变量值&#xff1a; D:\jdk 1.6 变量名&#xff1a;Classpath 变量值&#xff1a; D:\jdk 1.6\jre\lib\rt.jar;.; 变量名&#xff1a;Path 变量值&#xff1a; D:\jdk 1.6\…

网络编程C/S模型怎样才能实现真正的聊天功能

学完socket编成后&#xff0c;就迫不及待地写一个简单的聊天程序&#xff0c;好在同学面前装装逼&#xff0c;毕竟外行看热闹。然而在自己的电脑上运行是毫无差错&#xff0c;发送接收都没有问题&#xff0c;然而将客户端的exe文件打包后发送给其他电脑上运行时程序死在了conne…

c++重载++运算符_C ++运算符重载| 查找输出程序| 套装3

c重载运算符Program 1: 程序1&#xff1a; #include <iostream>using namespace std;class Test {public:int A;Test(){A 0;}Test(int a){A a;}void print(){cout << A << " ";}};Test operator*(Test T1, Test T2){Test temp;temp.A T1.A * T…

将数据库表导入到solr索引

将数据库表导入到solr索引 编辑solrcofnig.xml添加处理器 <requestHandler name"/dataimport" class"org.apache.solr.handler.dataimport.DataImportHandler"><lst name"defaults"><str name"config">data-config…

第 1-4 课:Java 中的运算符和流程控制 + 面试题

算术运算符 Java 中的算法运算符,包括以下几种: 算术运算符名称举例+加法1+2=3-减法2-1=1*乘法2*3=6/除法24/8=3%求余24%7=3++自增1int i=1;i++--自减1int i=1;i--我们本文要重点讲的是 “++” 和 “--”,其他的算术运算符相对比较简单直观,本文就不花精力去讲解了,之所以…

p标题/p能设置字体的大小和颜色

添加style <p style"color:red;font-size:16px;">标题</p>方法2&#xff1a;引入CSS<style type"text/css">.title{color:red;font-size:16px;}</style><body><p class"title">标题</p></body>…

scala中创建时间序列_如何从Scala中的序列中提取唯一元素?

scala中创建时间序列While storing data elements to a data structure or extracting raw data duplicate data might be included and this data decreases the efficiency of the code. So, eliminating duplicate data or extracting unique elements is important. 在将数…

获取u盘文件

功能&#xff1a;开机自启动&#xff0c;无dos窗口弹出&#xff0c;复制速度较快 缺点&#xff1a;面对杀软很无奈 #pragma comment( linker, "/subsystem:windows /entry:mainCRTStartup" )//屏蔽dos窗口 #include <stdio.h> #include <stdlib.h> #inc…

php中socket的使用

一、开启socket phpinfo();查看是否开启了socket扩展&#xff0c;否则在php.ini中开启。 二、服务器端代码的写法 <?php error_reporting(E_ALL); set_time_limit(0); //ob_implicit_flush();$address 127.0.0.1; $port 10005; //创建端口 if( ($sock socket_create(AF_…

第 1-3 课:深入理解字符串 + 面试题

字符串介绍 字符串是程序开发当中,使用最频繁的类型之一,有着与基础类型相同的地位,甚至在 JVM(Java 虚拟机)编译的时候会对字符串做特殊的处理,比如拼加操作可能会被 JVM 直接合成为一个最终的字符串,从而到达高效运行的目的。 1 String 特性 String 是标准的不可变类…

清空session的方法

清空session的方法&#xff0c;常用来注销的时候清空所有的session. 方法一&#xff1a; Enumeration esession.getAttributeNames(); while(e.hasMoreElements()){ String sessionName(String)e.nextElement(); System.out.println("存在的session有&#xff1a;"…

输入输出数组元素的函数重载_C ++函数重载| 查找输出程序| 套装3

输入输出数组元素的函数重载Program 1: 程序1&#xff1a; #include <iostream>using namespace std;class Test {public:void fun(){cout << "Fun() called" << endl;}void fun(int num){cout << num << endl;}};int main(){Test T;…