思路:首先accept是有一个线程的,另外只要这个accept成功的和一个客户端建立了连接,那么我们就需要创建一个对应的线程,用这个线程和客户端进行网络通信。每建立一个连接,通信的线程就需要创建出来一个。这样的话,能够保证通信的线程和客户端是一个一一对应的关系,也就是说用于通信的线程一共是有n个,用于建立连接的线程只有一个。在线程里边一共分为两类,一类是主线程,一类是子线程,只要是建立了新连接,主线程创建一个子线程,让子线程和对应建立连接的那个客户端去通信就行了。
这个图的思路和分析:我们需要在主线程里面不停的进行accept操作,如果说有新的客户端连接就建立连接。如果说没有新的客户端连接,主线程就阻塞在accept这个函数上。在主线程里边每创建一个新连接,就需要调用pthread_create创建一个子线程让这个子线程和对应的那个客户端进行网络通信。
考虑细节:多线程之间有哪些资源是共享的?哪些资源是不共享的?
全局和堆区是共享的,他们可以共同访问全局数据区里面的某一块内存或者说堆区里边的某一块内存。如果说有三个线程,那么这个栈区会被分成三份,每个线程都有一块属于自己的独立的栈空间,因此对于多个线程来说,他们并不是共享的。
注意细节:
cpp
// 信息结构体
struct SockInfo {
struct sockaddr_in addr;
int fd;
};
struct SockInfo infos[512];
把结构体数组里边的每一个元素中的文件描述符设置为-1,这样的话,可以通过这个服务器来判断当前的数组元素是不是被占用的。如果这个数组元素被占用了,它的文件描述符的值应该是一个有效值。如果是-1,是无效值。也就意味着这个元素是空闲的,是可用的
pthread_server.c
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.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() {
// 1.创建监听的套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd == -1) {
perror("socket");
return -1;
}
// 2.绑定本地的IP port
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY; // 0 = 0.0.0.0 对于0来说,大端和小端是没有区别的的,因此不需要转换
saddr.sin_port = htons(9999);//主机字节序转换成网络字节序
int ret = bind(fd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("bind");
return -1;
}
// 3.设置监听
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;
/*
把结构体数组里边的每一个元素中的文件描述符设置为-1
这样的话,可以通过这个服务器来判断当前的数组元素是不是被占用的
如果这个数组元素被占用了,它的文件描述符的值应该是一个有效值
如果是-1,是无效值。也就意味着这个元素是空闲的,是可用的
*/
}
// 4.阻塞并等待客户端的连接
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));
// 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("recv");
break;
}
}
// 关掉文件描述符
close(pinfo->fd);
pinfo->fd = -1;
return NULL;
}
client.c
cpp
#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 port
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET,"192.168.88.129",&saddr.sin_addr.s_addr);
int ret = connect(fd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("connect");
return -1;
}
int number = 0;
// 3.通信
while(1) {
// 发送数据
char buff[1024];
sprintf(buff,"你好,呵呵哒,%d...\n",number++);
send(fd,buff,strlen(buff) + 1,0);
//接收数据
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("服务器已经断开了连接...\n");
break;
}else{
perror("recv");
}
sleep(1);
}
// 关闭文件描述符
close(fd);
return 0;
}