引言
本篇笔记主要记录使用多线程的方式来实现,多个客户端之间进行通信,由服务器将信息进行转发。
运行原理
- 使用链表管理客户端
- 主线程主要负责等待连接,当有客户端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;
}