并发通信原理
多线程中分为两大类:主线程(父线程)和子线程,他们分别处理服务端的监听流程和通信流程。
-
主线程:
负责监听,处理客户端的连接请求,也就是在父进程中循环调用accept()函数
创建子线程:建立一个新的连接,就创建一个新的子进程,让这个子进程和对应的客户端通信
回收子线程资源:由于回收需要调用阻塞函数,这样就会影响accept(),直接做线程分离即可。 -
子线程:
负责通信,基于主线程建立新连接之后得到的文件描述符,和对应的客户端完成数据的接收和发送。
发送数据:send() / write()
接收数据:recv() / read()
在多线程版的服务器端程序中,多个线程共用同一个地址空间,有些数据是共享的,有些数据的独占的,下面来分析一些其中的一些细节
- 同一地址空间中的多个线程的栈空间是独占的
- 多个线程共享全局数据区,堆区,以及内核区的文件描述符等资源,因此需要注意数据覆盖问题,并且在多个线程访问共享资源的时候,还需要进行线程同步。
服务端代码:
#include <stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.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;
//接收数据
char buf[1024];
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{
write(info->fd, buf, strlen(buf)+1);
}
}
return NULL;
}
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);
saddr.sin_addr.s_addr = INADDR_ANY;
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;
}
//4.阻塞并等待客户端连接
struct sockaddr_in caddr;
int addrlen = sizeof(caddr);
//数据初始化
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--;
}
}
int connfd = accept(fd, (struct sockaddr*)&pinfo->addr, (socklen_t*)&addrlen);
printf("parrent thread, connfd: %d\n", connfd);
if(connfd == -1){
perror("accept");
exit(0);
}
pinfo->fd = connfd;
pthread_create(&pinfo->tid,NULL,working,pinfo);
pthread_detach(pinfo->tid);
}
//释放资源
close(fd); //监听
return 0;
}
客户端代码:
#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,"10.12.19.120",&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, "你好,hello, world, %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);
send(fd, buff,len,0);
}else if(len == 0){
printf("服务端已经断开连接...\n");
break;
}else{
perror("recv");
break;
}
sleep(1);
}
//关闭文件描述符
close(fd);
return 0;
}