定义
进程的定义是操作系统中资源分配的基本单位,是程序的执行实体。具体地来分析这个定义的一体两面:
特别进程
资源回收表示将子进程的PCB给释放掉,不再占用内存,因为子进程已经无法被调度或使用。可使用wait来阻塞地回收子进程资源,或使用waitpid来等待特定子进程结束。使用WIFEXITED(&status)来判断进程是否正常退出,WEXITSTATUS(&status)来获取进程退出状态。
子进程
子进程和父进程的资源是独立的,如全局变量等,一旦子进程fork()出来之后,对变量的操作就不会再对父进程产生影响,同理,fork()出来之后,父进程对变量的操作也不会对子进程产生影响。
PIPE是半双工通信,即数据只能向一个方向传输,一端读一端写,创建管道其实就是1个长度为2的数组fd[2],fd[0]表示读端(一般用0表示没贡献内容来记忆),fd[1]表示写端。
PIPE可用于父子进程通信,父进程创建管道后fork出1个子进程,然后父进程关闭写端,子进程关闭读端,则可以在此管道中进行父子进程的通信了。
PIPE的升级版是FIFO,即命名管道,可用于2个非继承关系的进程之间的通信。
socket一般是用在不同主机之间的通信,如TCP、UDP通信;也可用于本机的进程间通信,如dbus。
创建1个socket进行进程间通信,一般有1个客户端和1个服务端
创建套接字
int socket(int domain, int type, int protocol)
domain表示协议域,如使用AF_INET表示IPv4,AF_INET6表示IPv6。type表示套接字类型,SOCK_STREAM表示流式套接字,提供可靠的,面向连接的,基于字节流的通信,确保数据不丢失不重复不乱序不出错,可用于TCP;SOCK_DGRAM表示数据报,提供不可靠,无连接的通信,通过分组传输数据。protoccol指定使用的协议,如IPROTO_TCP表示TCP协议,IPROTO_UDP表示UDP协议,若为0则系统会根据domain和type自设定使用协议。设置套接字选项
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
level表示指定选项的级别或协议,可以这么理解,socket本身有非常多的属性,如SO_REUSEPORT表示是否允许多个套接字绑定同一个端口,TCP_KEEPCNT表示发送多少次Keep-Alive报文后仍未收到响应则认定连接断开等等。而level可以设置一整组属性的值,如level = SOL_SOCKET 表示通用套接字,level = IPPROTO_TCP 表示控制TCP套接字行为等optname表示要设置的属性,optval表示要将属性设为什么目标值,optlen表示目标属性值的长度。如以下表示将SO_REUSEPORT属性设为1,则允许多个套接字绑定到同一个端口,可用于多进程服务器。int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt))
设置套接字地址
以下结构体用来存储套接字的地址信息,主要需要设置3个字段
struct sockaddr_in address;
sin_family:协议族,AF_INET表示IPv4,AF_INET6表示IPv6address.sin_addr.s_addr:可以接收什么IP地址的连接,若可以接收来自任意地址的连接,则可设为INADDR_ANYaddress.sin_port :套接字监听的端口号绑定套接字
将套接字和特定的网络地址和端口号绑定,以便套接字能接受来自指定地址的连接。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
监听连接
backlog表示等待连接队列的最长长度,如果请求连接的客户端数量超过此值,则连接请求会被拒绝,若小于此值,则将请求放在连接队列里,直到服务器有能力处理该连接。
int listen(int sockfd, int backlog);
接受客户连接请求
accept函数接受一个客户连接,并创建1个新的套接字用于和客户通信,返回值则是新创建的套接字
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
开始通信
ssize_t read(int fd, void *buf, size_t count);读取客户端请求
ssize_t send(int sockfd, const void *buf, size_t len, int flags); 回复客户端请求,flags用于设置一些属性,修改send函数的行为。
服务端完整代码
#include
#include
#include
#include
#include
#include
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
const char* hello = "Hello from server";
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Socket creation failed");
return -1;
}
// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("Setsockopt failed");
return -1;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
// 绑定套接字
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
perror("Bind failed");
return -1;
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("Listen failed");
return -1;
}
std::cout << "Server listening..." << std::endl;
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
perror("Accept failed");
return -1;
}
int valread;
while ((valread = read(new_socket, buffer, 1024)) > 0) {
std::cout << "Client: " << buffer << std::endl;
send(new_socket, hello, strlen(hello), 0);
}
std::cout << "Connection closed by client." << std::endl;
return 0;
}
#include
#include
#include
#include
#include
#include
int main() {
int sock = 0;
struct sockaddr_in server_addr;
char buffer[1024] = {0};
const char* hello = "Hello from client";
// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
std::cerr << "Socket creation error" << std::endl;
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
// 将IPv4地址从文本转换为二进制
if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
std::cerr << "Invalid address/ Address not supported" << std::endl;
return -1;
}
// 连接到服务器
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
std::cerr << "Connection Failed" << std::endl;
return -1;
}
send(sock, hello, strlen(hello), 0);
std::cout << "Hello message sent" << std::endl;
int valread;
while ((valread = read(sock, buffer, 1024)) > 0) {
std::cout << "Server: " << buffer << std::endl;
}
std::cout << "Connection closed by server." << std::endl;
return 0;
}
参考链接
线程的定义是操作系统中进行调度的基本单位,它出现的原因就是来解决进程的不足,因此需要了解它相比进程的区别,一切区别都从定义出发:线程是调度基本单位,而进程是资源分配基本单位,同个进程中的线程的虚拟地址空间是共享的,因此可以节约资源分配的时间和空间。
thread:直接使用thread(fun, arg1, arg2…)创建线程并指定线程入口函数和对应参数
join:在A线程中join(B线程),则是阻塞A线程,直到B线程终止后才继续执行A线程剩余部分
detach:在A线程中detach(B线程),则是将B线程从A线程中分离,B线程终止后立即回收其资源
unique_lock:使用unique_lock(mutex)保护1个临界区,并且在离开作用域时会自动解锁,和智能指针的思想很像,不需要用户手动解锁,符合RAII原则