POSIX多线程技术
编译:g++ -pthread main.cpp -o main
·线程也称为轻量级进程,在后面的实际应用中,线程的应用将会取代fork
- 什么是线程
- 在一个程序里的多个执行路线就叫做线程(thread),线程是“一个进程内部的控制序列,一切进程至少都有一个执行线程;
- 进程是资源分配的基本单位,线程是cpu调度的基本单位(最多大概200个);
- 线程的基础:cup的轮片执行;
-
编写多线程需要更全面更深入的考虑,数据不安全,调试一个多线程程序也比调试一个单线程程序困难得多,DeBug困难;
- fork和创建新线程的区别
- 当一个进程执行一个fork调用的时候,会创建出进程的一个新拷贝,新进程将拥有它自己的变量和它自己的PID。这个新进程的运行时间是独立的,它在执行时几乎完全独立于创建它的进程;
- 在进程里面创建一个新线程的时候,新的执行线程会拥有自己的堆栈(如何;理解区分函数)(因此也就有自己的局部变量),但要与它的创建者共享全局变量、文件描述符、信号处理器和当前的子目录状态;
- 创建新线程
void* pthread_fun(void* pv) {int accfd = *(int*)pv;}
pthread_create(&pthreadid, NULL, pthread_fun, void*);
双击sln->属性->链接器->-pthread->添加确定
#include<iostream>
using namespace std;
#include <unistd.h>
#include <pthread.h>
void* pthread_fun(void* pv) {
int res = *((int*)pv);
while (1) {
cout << " 子线程……" << endl;
sleep(1);
}
}
int main(){
pthread_t pthreadid;
int a;
pthread_create(&pthreadid, NULL, pthread_fun, &a);
while (1) {
cout << "主线程……" << endl;
sleep(1);
}
return 0;
}
网络通信案例-私聊
- 用户唯一标识
- accept_fd并不能作为用户的唯一标志,文件标识符是随机分配的,再次上线会造成改变,所以要创建用户的唯一标识符——用户账号;
- 全局变量
- 利用全局变量map<账号,文件标识符>,存在于容器证明已经上线,这个全局变量可以在各个用户处理的客户端子线程中访问到。为子线程聊天业务打通了通道;
- 自定义通讯协议
- 来自用户的信息:登录信息,聊天信息,对应不同的的业务,所以需要自定义通讯协议(涉及不同的业务信息,也对用不同的接收方式和解析信息的方式);
- 包头和包体:一般socket编程需要协议,把包拆分为两部分,包头和包体,包头中存放请求的一些命令信息参数等内容,包体为具体要处理的内容;
- 协议头+协议体(定长包头+不定长包体)
- 协议头:bussinesstype(对应的业务逻辑),bussinesslen(确定接受的数据结构);
- 协议体:具体业务数据;
- 发送需要回包
- 在网络传输当中是离散传递,可能回出现丢包的情况,回包保证数据包能够收到;
- 用户监管
- 用户token(令牌):交互会话中唯一身份标识符令牌化技术;
- 每次交互都需要下发账号和时间,上次操作的时间;
- 比如网页版的账号,太久没操作就需要出现登录,服务器每次都可以干预用户,判断用户权限;
主要概述
//客户端
//读的线程
pthread_create(&readpthread, NULL, readpthread_fun, &c_sockfd);
//写的线程
pthread_create(&writepthread, NULL, writepthread_fun, &c_sockfd);
readpthread_fun:
while (1) ://不同的业务判断:登录结果、发送结果(回包)和接收数据
if (h.businessType == -1)
……
writepthread_fun:
while (1) :if (isLogin == 0) :登录包发送
else if (isLogin == 1) :用户发送信息
//服务器
//创建一个专门针对这个客户端的业务线程,(需要读来自用户的socket,将acceptfd传入)
pthread_create(&pthreadid, NULL, pthread_function, &acceptfd);
void* pthread_function(void* pv) {
while (1) {
//业务:登录请求(1),消息发送(2);
if (h.businessType == 1) {
……
服务器
#include<iostream>
using namespace std;
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include<string.h>
#include<map>
#include "protocol.h"
map<int, int> map_Online;
int login_verification(int name, char* pass)
{
//1:成功登录 -1:登录失败
return 1;
}
void* pthread_function(void* pv) {
int res = 0;
int accfd = *((int*)pv);
char buf[300] = { 0 };
HEAD h = { 0 };
USER u = { 0 };
MSG msg = { 0 };
LoginRe loginresult = { 0 };
while (1) {
//服务器读客户端发来的数据:
//读包头、数据类型判断、读包体、完成相应业务
res = read(accfd, &h, sizeof(HEAD));
cout << " ·server read(" << res << ") 来自:fd = "<< accfd <<" HEAD : Type=" << h.businessType<<" Leg=" << h.businessLeg << endl;
//业务:登录请求(1),消息发送(2);
if (h.businessType == 1) {
//读用户包体,几次用户信息将不清空,作为该进程连接客户端的id
res = read(accfd, &u, h.businessLeg);
cout << " ·server read(" << res << ") 来自:fd = " << accfd << " USER : name=" << u.name << " pwd=" << u.pass << endl;
//登录判断
//回包:包头(-1)+包体(登录回应LoginRe) 登录结果依然保留
bzero(&h, sizeof(h));
h.businessType = -1;
h.businessLeg = sizeof(LoginRe);
loginresult.out = login_verification(u.name, u.pass);
if (loginresult.out == 1) {
memcpy(loginresult.msg, "登录成功", sizeof("登录成功"));
//添加至上线map
map_Online[u.name] = accfd;
cout << "上线人数:" << map_Online.size() <<endl;
}
else if (loginresult.out == -1) {
memcpy(loginresult.msg, "失败登录", sizeof("失败登录"));
}
//拼包
memcpy(buf, &h, sizeof(HEAD));
memcpy(buf+ sizeof(HEAD), &loginresult, sizeof(LoginRe));
res = write(accfd, buf, sizeof(HEAD) + sizeof(LoginRe));
cout << " ·server write(" << res << ") to:fd = " << accfd << "LoginRe : out=" << loginresult.out << " msg=" << loginresult.msg << endl;
bzero(buf, sizeof(buf));
}
else if (h.businessType == 2) {
//读MSG消息体
res = read(accfd, &msg, h.businessLeg);
cout << " ·server read(" << res << ") 来自:fd = " << accfd << " MSG : from=" << msg.fromname << " to " << msg.toname<<" flag(1):"<<msg.flag << " buf = " << buf << endl;
//判断这个用户是否在线:
//在线:回包+发包;
//不在线:回包
if (map_Online.count(msg.toname) == 1) {
//在线
//发包() h msg都是和接收的一样
memcpy(buf, &h, sizeof(HEAD));
memcpy(buf+ sizeof(HEAD), &msg, sizeof(MSG));
res = write(map_Online[msg.toname], buf, sizeof(HEAD) + sizeof(MSG));
cout << " ·server 转发write(" << res << ") 2 ==" << h.businessType << h.businessType << "==" << sizeof(MSG) << "flag(1)" << msg.flag<< " from" << msg.fromname << "to" << msg.toname << " buf:" << msg.msg << endl;
bzero(buf, sizeof(buf));
//回包 h一样,msgflag = 0
msg.flag = 0;
memcpy(buf, &h, sizeof(HEAD));
memcpy(buf + sizeof(HEAD), &msg, sizeof(MSG));
res = write(accfd, buf, sizeof(HEAD) + sizeof(MSG));
cout << " ·server 回包write(" << res << ") 2 ==" << h.businessType << h.businessType << "==" << sizeof(MSG) << "flag(0)" <<msg.flag<< " from" << msg.fromname << "to" << msg.toname << " buf:" << msg.msg << endl;
bzero(buf, sizeof(buf));
}
else if(map_Online.count(msg.toname) == 0){
//不在线
//回包 h一样,msgflag = -1
msg.flag = -1;
memcpy(buf, &h, sizeof(HEAD));
memcpy(buf + sizeof(HEAD), &msg, sizeof(MSG));
res = write(accfd, buf, sizeof(HEAD) + sizeof(MSG));
cout << " ·server 回包write(" << res << ") 1 ==" << h.businessType << h.businessType << "==" << sizeof(MSG) << "flag(-1)" << msg.flag << " from" << msg.fromname << "to" << msg.toname << " buf:" << msg.msg << endl;
bzero(buf, sizeof(buf));
}
}
}
}
int main()
{
int socketfd = 0;
struct sockaddr_in addr;
pthread_t pthreadid = 0;
//创建一个新的套接字
socketfd = socket(AF_INET, SOCK_STREAM,0);
if (socketfd == -1) {
perror("socket error");
}
else {
cout << "socket success" << endl;
//初始化地址并绑定地址到套接字
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(10112);
socklen_t addrlen = sizeof(addr);
int res = bind(socketfd, (struct sockaddr*)&addr,addrlen);
if (res == -1) {
perror("bind error");
}
else {
cout << "bind success" << endl;
//设置套接字为监听模式
res = listen(socketfd, 10);
if (res == -1) {
perror("listen error");
}
else {
cout << "listen success" << endl;
//不断从队列里面取出申请连接的客户端,并返回一个fd指向这个客户端套接字
while (1) {
cout << "等待接受新用户 " << endl;
int acceptfd = accept(socketfd, (struct sockaddr*)&addr, &addrlen);
cout << "aceept:fd = " << acceptfd <<"连接成功!" << endl;
//创建一个专门针对这个客户端的业务线程,(需要读来自用户的socket,将acceptfd传入)
pthread_create(&pthreadid, NULL, pthread_function, &acceptfd);
}
}
}
}
}
客户端
#include<iostream>
using namespace std;
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include<map>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdio.h>
#include<string.h>
#include "protocol.h"
int isLogin = 0;
int MY = 0;
void* readpthread_fun(void* pv) {
int c_sockfd = *((int*)pv);
int res = 0;
char buf[300] = { 0 };
HEAD h = { 0 };
LoginRe loginresult = { 0 };
MSG msg = { 0 };
while (1) {
// 读包头、数据类型判断、读包体、完成相应业务
res = read(c_sockfd, &h, sizeof(HEAD));
cout << " ···client read(" << res << ") 来自:fd = " << c_sockfd << " HEAD : Type=" << h.businessType << " Leg=" << h.businessLeg << endl;
//业务: 登录结果(-1)、发送结果(回包)和接收数据(2);
if (h.businessType == -1) {
//读用户包体
res = read(c_sockfd, &loginresult, h.businessLeg);
cout << " ···client read(" << res << ") 来自:fd = " << c_sockfd << " LoginRe : out=" << loginresult.out << " msg=" << loginresult.msg << endl;
//业务:修改登录标记isLogin
isLogin = loginresult.out;
}
else if (h.businessType == 2) {
//读用户包体
res = read(c_sockfd, &msg, h.businessLeg);
cout << " ···client read(" << res << ") 来自:fd = " << c_sockfd << " MSG : flag=" << msg.flag << " from" << msg.fromname << "to" << msg.toname << " buf:" << msg.msg << endl;
//业务:判断//1发送(服务器与接收端) -1发送失败(对方离线) 0发送成功(回包)
if (msg.flag == 1) {
cout<< msg.fromname <<"->我:"<< msg.msg << endl;
}
else if (msg.flag == -1) {
cout << "我->"<<msg.toname<<":" << msg.msg << "(对方离线)" << endl;
}
else if (msg.flag == 0) {
cout << "我->" << msg.toname << ":" << msg.msg << endl;
}
}
}
}
void* writepthread_fun(void* pv) {
int c_sockfd = *((int*)pv);
char buf[300] = { 0 };
int res = 0;
HEAD h = { 0 };
USER u = { 0 };
MSG msg = { 0 };
while (1) {
//首先 进行用户登录判断
//离线则进行登录操作
if (isLogin == 0) {
//输入账号密码
cout << "····【登录】请输入账号:" << endl;
cin >> u.name;
cout << "····【登录】请输入密码:" << endl;
cin >> u.pass;
//赋值连接包头包体+发送
h.businessType = 1;
h.businessLeg = sizeof(USER);
memcpy(buf,&h,sizeof(HEAD));
memcpy(buf + sizeof(HEAD), &u, sizeof(USER));
res = write(c_sockfd,buf,sizeof(HEAD)+ sizeof(USER));
cout <<" ···client已经发送登录请求:write("<< res <<") "<< u.name << " " << u.pass << endl;
//清空
bzero(buf, sizeof(buf));
bzero(&h, sizeof(h));
MY = u.name;
bzero(&u, sizeof(u));
//阻塞一会,让登录结果有时间更新,避免回到登录业务
cout << "····按任意键继续……" << endl;
int i;
cin >> i;
}
else if (isLogin == 1) {
//用户发送信息
fflush(stdin);
// cin h msg buf write
h.businessLeg = sizeof(MSG);
h.businessType = 2;
msg.flag = 1;//发送给服务器
msg.fromname = MY;
cout << "····请输入发送的用户:" << endl;
cin >> msg.toname;
//fflush(stdin);
cout << "····请输入信息:" << endl;
//清空缓存区
char ch;
while ((ch = getchar()) != '\n' && ch != EOF) {}
char msgbuf[150] = { 0 };
//输入一行
cin.getline(msg.msg, sizeof(msg.msg));
cout << msg.msg << endl;
memcpy(msg.msg, msgbuf, strlen(msgbuf));
memcpy(buf,&h,sizeof(HEAD));
memcpy(buf + sizeof(HEAD), &msg, sizeof(MSG));
res = write(c_sockfd,buf,sizeof(HEAD)+ sizeof(MSG));
cout << " ···发送消息write(" << res << ") from " << msg.fromname << "to" << msg.toname <<" buf = "<<buf<< endl;
bzero(buf, sizeof(buf));
}
}
}
int main() {
int c_sockfd = 0;
struct sockaddr_in addr;
pthread_t readpthread = 0;
pthread_t writepthread = 0;
//创建新的客户端套接字
c_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (c_sockfd == -1) {
perror(" ···socket error");
}
else {
cout << "socket success"<<endl;
//初始化地址,绑定地址给套接字
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");//本机回环
addr.sin_port = htons(10112);
int addrlen = sizeof(addr);
int res = connect(c_sockfd, (struct sockaddr*)&addr, addrlen);
if (res == -1) {
perror(" ···connect error");
}
else {
cout << " ···connect success" << endl;
//读的线程
pthread_create(&readpthread, NULL, readpthread_fun, &c_sockfd);
//写的线程
pthread_create(&writepthread, NULL, writepthread_fun, &c_sockfd);
}
}
while (1){}
return 0;
}
协议体
#pragma once
//通用的通信协议 头
typedef struct head
{
int businessType;
//业务类型 //1-登录USER(C2S) //(-1)-登录结果LoginRe(S2C) //2-消息MSG(C2S) //(-2)-发送消息结果MsgRe
int businessLeg; //协议体长度
}HEAD;
typedef struct user
{
int name; //如果这里是char * -> 同一个结构体接收,map<char*,accfd>,如果map键要用指针,每次要用新的
char pass[20];
char data[20];
}USER;