复习进程线程
1.进程和线程的区别:
共同点: 都为系统提供并发执行的能力;
区别:
资源调度 : 线程是内核调度的最小单位, 进程是资源分配的最小单位;
地址空间: 进程间独立, 同一进程内线程共享资源;
通信: 多线程通信简单, 全局变量 多进程通信麻烦,使用3-4G内核空间;
安全: 多线程安全性较差, 互斥锁,信号量,条件变量;
多进程安全性较高;
进程通信方式: 有名和无名管道 , 信号, 信号灯集, 共享内存, 消息队列, 套接字
2. 进程: pid_t pid = fork():
父进程先退出, 子进程如果没有结束, 子进程会成为孤儿进程, 会被系统回收
子进程先结束, 父进程没有回收子进程资源, 子进程成为僵尸进程
注意:
fork之前创建的文件描述符, fork之后创建的两个进程都可以拿到这两个文件描述符,操作的是同一个文件,移动的是同一个文件指针。
fork创建子进程后, 子父进程相互独立
3. 线程:pthread_t tid
pthread_create(&tid, NULL,线程函数名,传参);
pthread_join(tid, NULL); 阻塞等待回收线程资源
pthread_detach(tid) 将线程设置为 游离态, 当线程退出,自动回收自己资源
pthread_exit(NULL);
服务器模型
● 在网络程序里面,通常都是一个服务器处理多个客户机。
● 为了处理多个客户机的请求, 服务器端的程序有不同的处理方式。
【1】 循环服务器 (同一时刻,只能连接一个客户端,进行通信)
特点: 循环服务器,同一时刻,只能处理一个客户端请求;
框架:
TCP服务器流程:
socket()
bind();
listen()
while(1)
{
accept()
while(1)
{
recv();
}
close(accpetfd);
}
close(sockfd);
UDP服务器流程:
socket();
bind();
while(1)
{
recvfrom();
}
close(sockfd);
【2】 并发服务器
特点: 同一时刻可以相应多个客户端的请求 (同时连接多个客户端,并能够通信)
1. 多进程实现并发 (最好不用)
每来一个客户端连接, 开一个进程来专门处理客户端的数据, 实现简单, 资源占用大;
fork创建多进程的特点:
1. fork之前的代码会被复制,fork之后的代码会被复制并执行
2. fork之前打开的文件,fork之后父子进程拿到的是同一个文件描述符,操作的是同一个文件
3. 一旦fork之后,父子进程独立
框架
socket()
bind();
listen();
while(1)
{
accept();
if(fork() == 0) //子进程
{
while(1)
{
process();
}
close(client_fd);
exit();
}
else
{
}
}
程序实例
服务器端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
void handler(int arg)
{
waitpid(-1, NULL, WNOHANG);
}
int main(int argc, char const *argv[])
{
// 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket错误");
return -1;
}
// 填充结构体
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1])); // 从命令行参数获取端口号
saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 监听所有IP地址
int len = sizeof(caddr);
// 绑定IP和端口
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind错误");
return -1;
}
// 监听
if (listen(sockfd, 5) < 0)
{
perror("listen错误");
return -1;
}
printf("监听成功\n");
// 设置信号处理函数,用于处理子进程状态
signal(SIGCHLD, handler);
while (1)
{
// 阻塞等待连接
int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accept错误");
return -1;
}
// 创建子进程来处理客户端连接
pid_t pid = fork();
if (pid < 0)
{
perror("fork错误");
return -1;
}
else if (pid == 0) // 子进程
{
char buf[128];
int ret;
while (1)
{
ret = recv(acceptfd, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("recv错误");
return -1;
}
else if (ret == 0) // 客户端断开连接
{
printf("客户端已退出\n");
break;
}
else
{
buf[ret] = '\0'; // 添加字符串结束符
printf("%s\n", buf);
}
}
close(acceptfd);
exit(-1); // 子进程结束
}
else
{
close(acceptfd); // 父进程关闭acceptfd,继续监听
}
}
close(sockfd);
return 0;
}
客户端:
2. 多线程实现并发
每来一个客户端连接, 开一个子线程来专门处理客户端的数据, 实现简单, 资源占用少;
实现服务器给所有客户端发送消息比较困难。
框架:
socket()
bind();
listen();
while(1)
{
accept();
pthread_create();
}
函数接口:
创建线程 pthread_create
头文件: #include<pthread.h>
原型:
int pthread_create(pthread_t *thread , const pthread_attr_t*arr , void*(* routine)(void *) , void *arg)
功能: 创建线程
参数: pthread_t *thread: 指针,要指向一个地址。创建的线程对象,地址
pthread_attr_t: 线程的属性,为NULL表示默认属性
void*(* routine)(void *): 线程函数(函数指针),传入函数名
void*arg: 给线程函数传参,以地址的形式,不需要传参可以设NULL
返回值: 成功 : 0 失败 : errno
退出线程: pthread_exit(void *retval)
原型:
int pthread_exit(void *retval)
功能: 用于退出线程的执行
参数: retval: 线程退出时返回的值
返回值: 成功: 0
失败: errno
使用: pthread_exit(NULL);
将线程设置为游离态,自动回收线程资源, pthread_detach
原型:
int pthread_detach(pthread_t thread);
功能:将线程设置为游离态,等线程退出系统自动回收线程资源
参数:
thread:线程tid
返回值:
成功0,失败非0
使用: pthread_detach(tid);
程序实例
服务器:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
// 线程函数,用于处理客户端连接
void *mythread(void *arg)
{
int acceptfd = *((int *)arg);
char buf[128];
while (1)
{
int ret = recv(acceptfd, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("recv错误");
return NULL;
}
else if (ret == 0)
{
printf("客户端已退出\n");
break;
}
else
{
buf[ret] = '\0'; // 添加字符串结束符
printf("%s\n", buf);
}
}
close(acceptfd);
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
// 1. 创建套接字 >> 返回一个建立连接的文件描述符
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("创建套接字时出错");
return -1;
}
printf("sockfd : %d\n", sockfd); // 打印套接字文件描述符
// 2. 填充结构体
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1])); // 从命令行参数获取端口号
saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 监听所有IP地址
int len = sizeof(caddr);
// 3. 绑定IP和端口
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("绑定套接字地址时出错");
return -1;
}
// 4. 监听
if (listen(sockfd, 5) < 0)
{
perror("监听套接字时出错");
return -1;
}
printf("监听成功\n");
while (1)
{
// 5. 阻塞等待客户端连接
int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("接受客户端连接时出错");
return -1;
}
printf("客户端:IP:%s 端口:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
// 创建线程处理客户端连接
pthread_t tid;
pthread_create(&tid, NULL, mythread, &acceptfd);
pthread_detach(tid); // 分离线程,线程结束后自动释放资源
}
close(sockfd);
return 0;
}
客户端:
3. IO多路复用
特点:
借助select,poll,epoll机制, 将新连接的客户端描述符增加到描述符表中,只需要一个线程即可处理所有的客户端连接,在嵌入式开发中应用广泛,但代码复杂度较高。
并发服务器总结:
多进程:
优点: 服务器更稳定 , 父子进程资源独立, 安全性高一点
缺点: 需要开辟多个进程,大量消耗资源
多线程:
优点: 相对多进程, 资源开销小, 线程共享同一个进程的资源
缺点: 需要开辟多个线程,而且线程安全性较差
IO多路复用:
优点: 节省资源, 减小系统开销,性能高;
缺点: 代码复杂性高
setsockopt设置套接字属性
int setsockopt(int sockfd,int level,int optname,void *optval,socklen_t optlen)
功能:获得/设置套接字属性
参数:
sockfd:套接字描述符
level:协议层
optname:选项名
optval:选项值
optlen:选项值大小
返回值: 成功 0 失败-1
选项名称 | 说明 | 数据类型 |
===================== SOL_SOCKET 应用层 ======================== | ||
SO_BROADCAST | 允许发送广播数据 | int |
SO_DEBUG | 允许调试 | int |
SO_DONTROUTE | 不查找路由 | int |
SO_ERROR | 获得套接字错误 | int |
SO_KEEPALIVE | 保持连接 | int |
SO_LINGER | 延迟关闭连接 | struct linger |
SO_OOBINLINE | 带外数据放入正常数据流 | int |
SO_RCVBUF | 接收缓冲区大小 | int |
SO_SNDBUF | 发送缓冲区大小 | int |
SO_RCVLOWAT | 接收缓冲区下限 | int |
SO_SNDLOWAT | 发送缓冲区下限 | int |
SO_RCVTIMEO | 接收超时 | struct timeval |
SO_SNDTIMEO | 发送超时 | struct timeval |
SO_REUSEADDR | 允许重用本地地址和端口 | int |
SO_TYPE | 获得套接字类型 | int |
SO_BSDCOMPAT | 与BSD系统兼容 | int |
==================== IPPROTO_IP IP层/网络层 ======================== | ||
IP_HDRINCL | 在数据包中包含IP首部 | int |
IP_OPTINOS | IP首部选项 | int |
IP_TOS | 服务类型 | int |
IP_TTL | 生存时间 | int |
IP_ADD_MEMBERSHIP | 将指定的IP加入多播组 | struct ip_mreq |
====================== IPPRO_TCP 传输层 ========================== | ||
TCP_MAXSEG | TCP最大数据段的大小 | int |
TCP_NODELAY | 不使用Nagle算法 | int |