AI智能
改变未来

简单理解socket与多线程和实现Linux系统下多客户端的数据通信


学习总结

1、首先简单理解什么是socket

socket(套接字)是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

2、什么是多线程,有什么用?

首先我们要理解什么是线程与多线程。

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

多线程是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

简单来说:线程是程序中一个单一的顺序控制流程;而多线程就是在单个进程中同时运行多个线程来完成不同的工作。

多线程的优缺点

优点:

1)、多线程技术可以加快程序的运行速度,使程序的响应速度更快,因为用户界面可以在进行其它工作的同时一直处于活动状态

2)、可以把占据长时间的程序中的任务放到后台去处理,同时执行其他操作,提高效率

3)、当前没有进行处理的任务时可以将处理器时间让给其它任务

4)、可以让同一个程序的不同部分并发执行,释放一些珍贵的资源如内存占用等等

5)、可以随时停止任务

6)、可以分别设置各个任务的优先级以优化性能

缺点:

1)、因为多线程需要开辟内存,而且线程切换需要时间因此会很消耗系统内存。

2)、线程的终止会对程序产生影响

3)、由于多个线程之间存在共享数据,因此容易出现线程死锁的情况

4)、对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。

3、代码实现

首先来完成一个服务端的创建

第一步需要先定义俩个全局变量

#define BUF_SIZE 100//能够连接的最大数量#define MAX_CLNT 256//客户端的连接数int clnt_cnt = 0;//客户端的连接sock的容器int clnt_socks[MAX_CLNT];//线程锁pthread_mutex_t mutx;

这俩个全局变量的作用:
操作系统可以看作时一个工厂,进程是生产线,线程就是在这条生产线上的工人。
而这俩个变量就是用来记录工人数量和存储工人组的。

//第一个变量代表工人的数目int clnt_cnt = 0;       //客户端链接个数//第二个变量数组代表工人组int clnt_socks[MAX_CLNT];//socket分机

第二步就是创建socket服务端

想要完成通信最少需要一个服务端和一个或者多个客户端

而服务端与客户端可以看成一个电话机的主机与分机

int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;int clnt_adr_sz;pthread_t t_id;if(argc!=2) {printf(\"Usage : %s <port>\\n\", argv[0]);exit(1);}pthread_mutex_init(&mutx, NULL);//1  安装一步电话机serv_sock=socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));//传输地址的地址族serv_adr.sin_family=AF_INET;//设置指定IPv4传输地址//htonl 地址转化 INADDR_ANY 任何地址serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);//传输协议端口号//当服务端与客户段端口号不同是将无法连接成功serv_adr.sin_port=htons(atoi(argv[1]));//2、分配地址和端口if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)//判断分配是否成功   失败返回错误号error_handling(\"bind() error\");//3、监听if(listen(serv_sock, 5)==-1)error_handling(\"listen() error\");//判断连接是否成功   失败返回错误号void error_handling(char * msg){fputs(msg, stderr);//输出错误号fputc(\'\\n\', stderr);exit(1);}

第三步 多线程链接处理

while(1){//每来一个链接,服务端要去处理客户端// 第一步 分配分机 处理此链接clnt_adr_sz = sizeof(clnt_adr);clnt_sock=accept(serv_sock, (struct sockaddr*) &clnt_adr, &clnt_adr_sz);// 第二步 让服务端维护的客户端数量加一pthread_mutex_lock(&mutx);//上锁clnt_socks[clnt_cnt++]=clnt_sock;//将工人的数据存储到工人组中pthread_mutex_unlock(&mutx);//解锁// 第三步 起一个线程去维护链接//函数 pthread_create  Linux系统下创建线程的函数pthread_create(&t_id,//新创建的线程ID指向的内存单元NULL, //线程属性,默认为NULLhandle_clnt,//新创建的线程从 handle_clnt函数的地址开始运行(void*)&clnt_sock//运行函数的参数);//pthread_detach 实现线程分离pthread_detach(t_id);//让线程分离  --自动退出,无系统残留资源//打印当前链接的IP地址printf(\"ip = %s\\n\",inet_ntoa(clnt_adr.sin_addr));}

第四步 收发消息的处理

接收到消息与某个客户端下线的处理

void * handle_clnt(void* arg){//第一步 把工人取过来了int clnt_sock = *((int *)arg);//获取消息int str_len = 0;//消息的长度int i;char msg[BUF_SIZE];//消息的容器//每次收到来自客户端的消息//read   读取数据while((str_len = read(clnt_sock, msg, sizeof(msg)))!=0)//判断是否收到消息{//将收到的消息发给所有的客户端send_msg(msg, str_len);}pthread_mutex_lock(&mutx);//上锁//当某个客户端下线了//首先 通过遍历整个工作组找到下线的客户端for(i=0; i<clnt_cnt; i++) //for是查找所有的socket有没有clnt_sock{if(clnt_sock == clnt_socks[i]) //删除clnt_sock,并且迁移{while(i++ < clnt_cnt-1){clnt_socks[i] = clnt_socks[i+1];}break;}}pthread_mutex_unlock(&mutx);//解锁close(clnt_sock);return NULL;}

给所有客户端发消息

void send_msg(char * msg, int len)   // send to all{int i;pthread_mutex_lock(&mutx);//上锁//通过循环的方式向每个在线的客户端发送消息for(i=0; i<clnt_cnt; i++){//write 写数据(发送数据)write(clnt_socks[i], msg, len);}pthread_mutex_unlock(&mutx);//解锁}

第五步 线程锁

线程锁:主要用来给方法、代码块加锁。当某个方法或者代码块使用锁时,那么在同一时刻至多仅有有一个线程在执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但是,其余线程是可以访问该对象中的非加锁代码块的。

//线程锁pthread_mutex_t mutx;//加上线程锁以后 可以保证每次只有一个工人存储到工人组中,这样就保证了所有线程能够按照我们所设定的顺序执行pthread_mutex_lock(&mutx);//上锁clnt_socks[clnt_cnt++]=clnt_sock;//将工人的数据存储到工人组中pthread_mutex_unlock(&mutx);//解锁

以上代码我们就完成了一个服务端的创建。

创建服务端就需要创建客户端来与服务端通信。

第一步 先定义俩个全局变量数组

#define BUF_SIZE 100#define NAME_SIZE 20//客户端的名称char name[NAME_SIZE]=\"[DEFAULT]\";//消息的容器char msg[BUF_SIZE];

第二部 与服务端相同,创建一个电话机

int sock;struct sockaddr_in serv_addr;pthread_t snd_thread, rcv_thread;void * thread_return;if(argc!=4) {printf(\"Usage : %s <IP> <port> <name>\\n\", argv[0]);exit(1);}//输入 当前客户端的名称sprintf(name, \"[%s]\", argv[3]);// 创建 socketsock=socket(PF_INET, SOCK_STREAM, 0);// 配置要连接的服务器memset(&serv_addr, 0, sizeof(serv_addr));//按字节对内存块进行初始化//传输地址的地址族serv_addr.sin_family=AF_INET;//设置指定IPv4传输地址serv_addr.sin_addr.s_addr=inet_addr(argv[1]);//传输协议端口号serv_addr.sin_port=htons(atoi(argv[2]));//连接服务端if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)error_handling(\"connect() error\");//判断是否连接成功   连接失败返回错误号void error_handling(char *msg){//输出错误号fputs(msg, stderr);//每次输出完后换行fputc(\'\\n\', stderr);exit(1);}

第三步 接收消息

//创建一个线程  并处理从服务端接收到的消息pthread_create(&snd_thread, NULL, send_msg, (void*)&sock);void * send_msg(void * arg)   // send thread main{//接收到的消息int sock=*((int*)arg);//存放 客户端名称与消息内容char name_msg[NAME_SIZE+BUF_SIZE];while(1){//接收来自于控制台的消息//读取数据fgets(msg, BUF_SIZE, stdin);//收到的消息为 q或者Q 时关闭线程与客户端if(!strcmp(msg,\"q\\n\")||!strcmp(msg,\"Q\\n\")){close(sock);//关闭线程exit(0);    //退出客户端}//打印 发送消息的客户端名称与收到的消息sprintf(name_msg,\"%s %s\", name, msg);//将数据写入到sock中write(sock, name_msg, strlen(name_msg));}return NULL;}

第四步 发送消息

//创建一个线程  用于处理向服务器发送消息pthread_create(&rcv_thread, NULL, recv_msg,      (void*)&sock);void * recv_msg(void * arg)   // read thread main{//接收到的消息int sock=*((int*)arg);//存放 客户端名称与消息内容char name_msg[NAME_SIZE+BUF_SIZE];//消息内容的长度int str_len;while(1){//读取消息内容str_len=read(sock, name_msg, NAME_SIZE+BUF_SIZE-1);//判断消息内容是否为空if(str_len==-1)return (void*)-1;//初始化容器name_msg[str_len]=0;//写入需要发送的消息内容fputs(name_msg, stdout);}return NULL;}

第五步 线程同步

//函数pthread_join用来等待一个线程的结束,线程间同步的操作。//以阻塞的方式等待thread指定的线程结束pthread_join(snd_thread, //thread:线程标识符,即线程ID&thread_return//retvat:用户定义的指针,用来存储被等待线程的返回值);pthread_join(rcv_thread, &thread_return);

第六步 关闭分机

close(sock);

总结,多客户端的通信是通过客户端发送数据包到服务端,然后通过服务端中转,发送到另一个客户端。

以上代码会用到的头文件:

服务端头文件:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>#include <sys/socket.h>#include <netinet/in.h>#include <pthread.h>

客户端头文件:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>#include <sys/socket.h>#include <netinet/in.h>#include <pthread.h>
赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 简单理解socket与多线程和实现Linux系统下多客户端的数据通信