在编写多线程版并发服务器代码的时候,应该注意到的是父子线程共用同一个地址空间中的文件描述符,所以每当在主线程中建立一个新的连接,都需要将得到文件描述符值保存起来,不能在同一变量上进行覆盖。如果进行覆盖,这样做丢失了之前的文件描述符值也就不知道如何和客户端通信了。
//服务端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
struct SockInfo
{
int fd; //通信
pthread_t tid; //线程
struct sockaddr_in addr; //地址信息
};
struct SockInfo infos[128];
//线程函数
void* working(void* arg)
{
while(1)
{
struct SockInfo* info = (struct SockInfo*)arg;
//接收 or 发送
//和客户端通信
//接收数据
char buf[1024];
memset(buf, 0, sizeof(buf));
int ret = read(info->fd, buf, sizeof(buf));
if(ret == 0)
{
printf("客户端断开了连接....\n");
info->fd = -1;
break;
}
else if(ret == -1)
{
printf("接收数据失败....\n");
info->fd = -1;
break;
}else
{
printf("客户端say: %s\n",buf);
write(info->fd, buf, strlen(buf) + 1);
}
}
return NULL;
}
int main()
{
//1、创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket");
exit(0);
}
//2、将socket()返回值和本地的IP端口绑定在一起
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
//INADDR_ANY代表本机的所有IP,假设有三个网卡就有三个IP地址
//这个宏可以代表任意一个地址
//这个宏一般用于本地的绑定操作
addr.sin_addr.s_addr = INADDR_ANY; //这个宏的值为0 == 0.0.0.0
int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
//3、设置监听
ret = listen(lfd, 128);
if(ret == -1)
{
perror("listen");
exit(0);
}
//阻塞等待并接收客户端连接
struct sockaddr_in cliaddr;
int client = sizeof(cliaddr);
//结构体初始化
int max = sizeof(infos) / sizeof(infos[0]);
for(int i = 0; i < max; i++)
{
bzero(&infos[i], sizeof(infos[i]));
infos[i].fd = -1;
infos[i].tid = -1;
}
while(1)
{
//创建子线程
struct SockInfo* pinfo;
for(int i = 0; i < max; ++i)
{
if(infos[i].fd == -1)
{
pinfo = &infos[i];
break;
}
if(i == max - 1)
{
sleep(1);
i--;
}
}
//接收,cfd客户端套接字
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &client);
if(cfd == -1)
{
perror("accept");
exit(0);
}
//连接成功后,将其值赋给结构体通信fd
pinfo->fd = cfd;
pthread_create(&pinfo->tid, NULL, working, pinfo);
pthread_detach(pinfo->tid); //线程分离
//打印客户端的地址消息
char ip[24] = {0};
printf("客户端的IP地址 :%s, 端口: %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(cliaddr.sin_port));
}
//关闭资源
close(lfd);
return 0;
}
客户端,可进行测试
//客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
int main()
{
//1、创建监听的套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
perror("socket");
exit(0);
}
//2、将socket()返回值和本地的IP端口绑定在一起
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr*)&addr, sizof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
int number = 0;
//和服务端通信
while(1)
{
//接收收据
char buf[1024];
sprintf(buf, "你好,服务器...%d\n", number++);
write(fd, buf, strlen(buf) + 1);
//接收数据
memset(buf, 0, sizeof(buf));
int len = read(fd, buf, sizeof(buf));
if(len > 0)
{
printf("服务器say: %s\n", buf);
}
else if(len == 0)
{
printf("服务器端断开了连接...\n");
break;
}
else
{
perror("read");
break;
}
sleep(3);
}
close(fd);
return 0;
}