多线程中的线程有两大类:主线程和子线程,分别在服务器端处理监听和通信流程。
主线程:
- 负责监听,处理客户端的连接请求,即在主线程中循环调用accept()函数。
- 创建子线程:建立一个新的连接,就创建一个新的子线程,让这个子线程和对应的客户端通信。
- 回收子线程资源,做线程分离。
子线程:
- 负责通信,基于主线程建立新连接后得到的文件描述符,和对应的客户端进行数据收发。
- 发送数据:send()。
- 接受数据:recv()。
多个线程会共用同一片地址空间,并共享其中的全局数据区、堆区以及内核区的文件描述符等资源,但栈区是每个线程独有的。
pthread_create用来创建子线程。
服务端代码如下:
(客户端代码见 Linux下的套接字通信)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<strings.h>
#include<arpa/inet.h>
#include<pthread.h>
//信息结构体
struct SockInfo
{
struct sockaddr_in addr;
int fd;
};
struct SockInfo infos[512];
void* working(void* arg);
int main()
{
//创建监听套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror("socket");
return -1;
}
//绑定ip 端口号
struct sockaddr_in saddr;
saddr.sin_family = AF_INET; //协议簇
saddr.sin_port = htons(9999); //端口号 主机转网络字节序
saddr.sin_addr.s_addr = INADDR_ANY; //自动识别IP
int ret = bind(fd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1)
{
perror("bind");
return -1;
}
//开始监听
ret = listen(fd,128);
if(ret == -1)
{
perror("listen");
return -1;
}
//初始化结构体数组
int max = sizeof(infos) / sizeof(infos[0]);
for(int i = 0; i < max; ++i)
{
bzero(&infos[i],sizeof(infos[i]));
infos[i].fd = -1;
}
//准备接受客户端连接
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;
}
}
int cfd = accept(fd,(struct sockaddr*)&pinfo->addr,&addrlen);
pinfo->fd = cfd;
if(cfd == -1)
{
perror("accept");
break;
}
//创建子线程
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;
//连接成功,打印客户端ip 和 端口号
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));
//通信
while(1)
{
//接收数据
char buff[1024];
int len = recv(pinfo->fd,buff,sizeof(buff),0);
if(len > 0)
{
printf("client data: %s\n",buff);
send(pinfo->fd,buff,len,0);
}
else if(len == 0)
{
printf("断开连接\n");
break;
}
else
{
perror("recv");
break;
}
}
//结束连接
close(pinfo->fd);
pinfo->fd = -1;
return NULL;
}