【Linux学习笔记51】多线程TCP服务端之简易QQ

引言

本篇笔记主要记录使用多线程的方式来实现,多个客户端之间进行通信,由服务器将信息进行转发。

运行原理

在这里插入图片描述

  • 使用链表管理客户端
  • 主线程主要负责等待连接,当有客户端1连接,在链表创建新的节点,并且创建新的线程s1负责与客户端进行通信。客户端2连接时,也会创建对应的线程负责。
  • s1子线程等待客户端发送数据
  • 客户端1中的s3负责等待键盘键入的数据,然后线程s2负责将键入的数据发送到服务器中
  • 服务器s1收到数据后,将数据转发到客户端2
  • 客户端2中的s4线程负责接收来自服务器转发的数据
  • 客户端2中的s5线程负责将接收到的数据从显示屏中显示出来

代码实现

这里以【Linux学习笔记50】socket编程之TCP协议的代码为模板修改

server.c添加内容:

#include "head4sock.h"
#include "kernel_list.h"

typedef struct
{
	int fd;
	struct list_head list;
}client;

client *head;

//删除链表节点
void del_client(int quitor)
{
	struct list_head *pos, *n;
	//从节点list中开始遍历,pos指向节点,因为要删除所以涉及了n参数
	list_for_each_safe(pos,n,&head->list)
	{
		client *tmp = list_entry(pos,client,list); //指向具体节点
		if (tmp->fd == quitor)
		{
			list_del(pos);
			free(tmp);
			break;
		}
	}
}

//广播发送
void broad_cast(const char *msg, int sender)
{
	struct list_head *pos;
	list_for_each(pos,&head->list) //遍历整个客户端链表
	{
		client *tmp = list_entry(pos,client,list); //指向具体节点
		//如果是发送者本身则跳过
		if (tmp->fd == sender)
			continue;
		//如果是其他客户端,则发送
		write(tmp->fd,msg,strlen(msg));
	}
}

void * routine(void *arg)
{
	pthread_detach(pthread_self());//分离线程
	int connfd = (int)arg;
	char buf[SIZE];
	while (1)
	{
		bzero(buf,SIZE);
		if(Read(connfd,buf,SIZE)==0 || !strcmp(buf,"quit\n")) //如何没有字符或者输入quit则删除线程
		{
			del_client(connfd); //删除链表节点
			break;
		}
		broad_cast(buf,connfd); //将数据转发到链表中的其他客户端
	}
	pthread_exit(NULL);
}

//初始化链表
client *init_list(void)
{
	head = malloc(sizeof(client));
	if(head != NULL)
	{
		INIT_LIST_HEAD(&head->list);
	}
	return head;
}

//创建链表节点
client *new_cli(int connfd)
{
	client *new = malloc(sizeof(client));
	if (new != NULL )
	{
		new->fd = connfd;
		INIT_LIST_HEAD(&head->list);
	}
	return new;
}

int main(int argc, char const *argv[])
{
	if(argc != 2)
	{
		printf("Usage: %s <PORT>\n", argv[0]);
		exit(0);
	}

	// 创建一个TCP套接字
	int fd = Socket(AF_INET, SOCK_STREAM, 0); //设为IPv4的套接字

	// 绑定地址(IP:PORT)
	struct sockaddr_in srvaddr, cliaddr;
	socklen_t len = sizeof(srvaddr);
	bzero(&srvaddr, len);

	srvaddr.sin_family = AF_INET;
	srvaddr.sin_port = htons(atoi(argv[1]));
	// inet_pton(AF_INET, "192.168.1.166", &srvaddr.sin_addr);
	srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(fd, (struct sockaddr *)&srvaddr, len);

	// 设置监听套接字
	Listen(fd, 3); //如果同时有人发出请求,能够同时处理若干个,在Linux中至少同时允许4+3个连接请求

	head = init_list();

	len = sizeof(cliaddr);
	while (1)
	{
		int connfd = Accept(fd, (struct sockaddr *)&cliaddr, &len);

		char peeraddr[50];
		bzero(peeraddr, 50);
		printf("new connection: %s:%hu\n",  //打印端口号
					inet_ntop(AF_INET, &cliaddr.sin_addr, peeraddr, 50),
					ntohs(cliaddr.sin_port));

		//创建新的客户端节点,并将其放到链表的末端
		client *new = new_cli(connfd);
		list_add_tail(&new->list,&head->list);

		//创建线程
		pthread_t tid;
		pthread_create(&tid,NULL,routine,(void *)connfd);
	}
	close(fd);

	return 0;
}

client.c

#include "head4sock.h"

void *routine(void *arg)
{
	int fd = (int) arg;
	char buf[SIZE];
	while (1)
		{
			bzero(buf,SIZE);
			if(Read(fd,buf,SIZE)==0 )
			{
				break;
			}
			printf("form server: %s ",buf);
		}
}

int main(int argc, char const *argv[])
{
	if(argc != 3)
	{
		printf("Usage: %s <IP> <PORT>\n", argv[0]);
		exit(0);
	}

	// 创建一个TCP套接字
	int fd = Socket(AF_INET, SOCK_STREAM, 0);

	// 准备好对端的地址信息
	struct sockaddr_in srvaddr;
	socklen_t len = sizeof(srvaddr);
	bzero(&srvaddr, len);

	srvaddr.sin_family = AF_INET;
	inet_pton(AF_INET, argv[1], &srvaddr.sin_addr);
	srvaddr.sin_port = htons(atoi(argv[2]));

	// 连接服务端
	Connect(fd, (struct sockaddr *)&srvaddr, len);

	pthread_t tid;
	pthread_create(&tid,NULL,routine,(void *)fd);

	char buf[SIZE];
	while(1)
	{
		bzero(buf, SIZE);
		if(fgets(buf, SIZE, stdin) == NULL||
			!strcmp(buf, "quit\n"))
			break;

		write(fd, buf, strlen(buf));
	}

	close(fd);
	return 0;
}

代码运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值