Linux 之 IPC进程间通信(七、Socket结合多线程案例私聊)

POSIX多线程技术

编译:g++ -pthread main.cpp -o main

·线程也称为轻量级进程,在后面的实际应用中,线程的应用将会取代fork

  1. 什么是线程
    1. 在一个程序里的多个执行路线就叫做线程(thread),线程是“一个进程内部的控制序列,一切进程至少都有一个执行线程;
    2. 进程是资源分配的基本单位,线程是cpu调度的基本单位(最多大概200个);
    3. 线程的基础:cup的轮片执行;
    4. 编写多线程需要更全面更深入的考虑,数据不安全,调试一个多线程程序也比调试一个单线程程序困难得多,DeBug困难;

  2. fork和创建新线程的区别
    1. ​​​​​​​当一个进程执行一个fork调用的时候,会创建出进程的一个新拷贝,新进程将拥有它自己的变量和它自己的PID。这个新进程的运行时间是独立的,它在执行时几乎完全独立于创建它的进程;
    2. 在进程里面创建一个新线程的时候,新的执行线程会拥有自己的堆栈(如何;理解区分函数)(因此也就有自己的局部变量),但要与它的创建者共享全局变量文件描述符信号处理器和当前的子目录状态
  3. 创建新线程
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;
}

 


网络通信案例-私聊

  1. 用户唯一标识
    1. accept_fd并不能作为用户的唯一标志,文件标识符是随机分配的,再次上线会造成改变,所以要创建用户的唯一标识符——用户账号;
  2. 全局变量
    1. 利用全局变量map<账号,文件标识符>,存在于容器证明已经上线,这个全局变量可以在各个用户处理的客户端子线程中访问到。为子线程聊天业务打通了通道;
  3. 自定义通讯协议
    1. 来自用户的信息:登录信息,聊天信息,对应不同的的业务,所以需要自定义通讯协议(涉及不同的业务信息,也对用不同的接收方式和解析信息的方式);
    2. 包头和包体:一般socket编程需要协议,把包拆分为两部分,包头和包体,包头中存放请求的一些命令信息参数等内容,包体为具体要处理的内容;
    3. 协议头+协议体(定长包头+不定长包体)
      • 协议头:bussinesstype(对应的业务逻辑),bussinesslen(确定接受的数据结构);
      • 协议体:具体业务数据;
  4. 发送需要回包
    1. 在网络传输当中是离散传递,可能回出现丢包的情况,回包保证数据包能够收到;
  5. 用户监管
    1. 用户token(令牌):交互会话中唯一身份标识符令牌化技术;
    2. 每次交互都需要下发账号和时间,上次操作的时间;
    3. 比如网页版的账号,太久没操作就需要出现登录,服务器每次都可以干预用户,判断用户权限;

主要概述

//客户端
//读的线程
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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值