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);	//网络连接并返回服务器socket
	setsocket_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;
}

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

  • 6
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
实现TCP客户端之间多线程通信需要以下步骤: 1. 创建Socket连接:使用Socket类创建客户端Socket对象,并指定服务器的IP地址和端口号。 2. 创建输入输出流:创建客户端的输入输出流,以便进行数据的读写。 3. 实现多线程通信:使用多线程技术实现客户端之间的通信,每个客户端启动一个线程来处理与其他客户端的通信。 4. 消息传输:对于每个客户端,通过输出流把消息发送到服务器,服务器再把消息发送给其他客户端。 5. 关闭连接:当客户端不再需要连接时,需要关闭Socket连接,释放资源。 实现聊天页面的UI需要以下步骤: 1. 设计UI界面:设计一个聊天窗口,包含输入框、发送按钮以及显示框。 2. 实现UI控件:使用Swing或JavaFX等工具实现UI控件,添加事件监听器。 3. 实现发送消息:当用户在输入框中输入消息并点击发送按钮时,将消息发送到服务器。 4. 实现接收消息:当用户接收到其他客户端发送的消息时,在显示框中显示消息。 5. 处理异常:在连接服务器时,可能会出现各种异常,需要在程序中进行处理。 注意事项: 1. 多线程通信需要注意线程安全问题,避免数据竞争和死锁。 2. UI设计需要考虑用户体验和美观度,应该尽可能地简洁明了。 3. 对于网络连接异常,应该给出友好的提示信息,避免用户因网络问题而感到困扰。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值