核心知识点:
- 主线程和子线程
- 子线程的剥离
- 主线程使用范围 子线程使用范围
- 主线程的数据传到子线中
- 思考范围
思考范围
- 多线程网络通讯–>1个服务器, 多个客户端连接服务器, 在主线程里监听并连接这些客户端, 由于客户端的ip不同,所以每一个服务器和客户端通讯的socket的文件文件描述符内容是不一样的, 所以需要一个很大的数组来保存每一个客户端的ip和端口
- 整体可以才拆解为两个部分:1.上面的连接 2. 下面的通信部分, 由于每个客户端的ip不一样+传送的内容不一样, 需要每个客户端都有一个专门的通信函数,也就是创建出多个线程,1个线程为1个客户端服务(通信)
源代码
服务器端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#define SIZE 512
// 信息结构体 包含Socket 和通信文件描述符fd
struct SockInfo
{
struct sockaddr_in addr; // 包含ip和端口
int fd;
};
// 创建N个客户端需要的信息结构体 512个客户端
struct SockInfo infos[SIZE];
void* working(void* arg);
int main()
{
// 1.创建服务器监听socket
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
perror("服务器创建Socket失败");
return -1;
}
// 2.套接字和本地的ip绑定 IP地址和端口号
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);// 注意大小端转换
saddr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));
if(ret == -1)
{
perror("绑定失败");
return -1;
}
// 3.设置监听
ret = listen(fd, 128); // 最大监听128个
if(ret == -1)
{
perror("监听失败");
return -1;
}
// 初始化信息结构体数组(= ip端口组合体 + 文件描述符)
int max = sizeof(infos) / sizeof(infos[0]);
for(int i = 0; i < max; i++)
{
bzero(&infos[i], sizeof(infos[i]));
infos[i].fd = -1;
}
// 4.阻塞并且等待客户端链接==创建客户端socket
int addrlen = sizeof(struct sockaddr_in);
// 不断连接客户端
while(1)
{
struct SockInfo* pinfo;
for(int i = 0; i < max; i++)
{
if(infos[i].fd == -1)
{
pinfo = &infos[i];
break;
}
}
// cfd 通信文件描述符, 在子线程里使用
int cfd = accept(fd, (struct sockaddr*)&pinfo->addr, &addrlen);
pinfo->fd = cfd;
if(cfd == -1)
{
perror("客户端连接错误");
break; // 可以重新尝试连接 使用contiue
}
//创建子线程
pthread_t tid;
pthread_create(&tid, NULL, working, pinfo);
// 分离子线程
pthread_detach(tid);
}
close(fd);
return 0;
}
// 子线程任务函数
void* working(void* arg)
{
struct SockInfo* pinfo = (struct SockInfo*)arg;
char ip[32];
printf("IP地址为: %s, 端口号: %d\n", inet_ntop(AF_INET,
&pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)),
ntohs(pinfo->addr.sin_port));
// 5.通信
while(1)
{
char Buff[1024];
int len = recv(pinfo->fd, Buff, sizeof(Buff), 0);
if(len > 0)
{
printf("client say: %s\n",Buff);
send(pinfo->fd, Buff, len, 0); // 发送数据
}else if(len == 0)
{
printf("客户端 断开连接\n");
break;
}
else
{
perror("接收数据错误");
break;
}
}
// 6.关闭文件描述符
close(pinfo->fd);
pinfo->fd = -1;
return NULL;
}
客户端
客户端代码不变
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
// 1.创建通信的套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
perror("创建通信socket失败");
return -1;
}
// 2.连接服务器 绑定服务器的ip和端口
struct sockaddr_in saddr;
// 初始化绑定的接口体
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999); //注意大小端转换
inet_pton(AF_INET, "192.168.1.1", &saddr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
if(ret == -1)
{
perror("连接错误");
return -1;
}
int number = 0;
while(1)
{
// 3.通信
char Buff[1024];
sprintf(Buff, "通信中..., %d \n", number++);
// 发送数据,通信的套接字 数据 实际长度 0
send(fd, Buff, strlen(Buff)+1, 0);
// 4. 接收数据前先清空数据
memset(Buff, 0 , sizeof(Buff));
// 接收数据
int len = recv(fd, Buff, sizeof(Buff), 0);
if(len > 0)
{
printf("Server say: %s\n", Buff);
}else if(len == 0)
{
printf("连接断开");
break;
}else
{
perror("Recv");
break;
}
sleep(1);
}
// 5. 关闭文件描述符;
close(fd);
return 0;
}