Liunx C 编程之多线程与Socket
多线程
pthread.h 是 linux 特有的头文件,POSIX 线程(POSIX threads),简称 Pthreads,是线程的 POSIX 标准。该标准定义了创建和操纵线程的一整套 API。在类 Unix 操作系统(Unix、Linux、Mac OS X 等)中,都使用 Pthreads 作为操作系统的线程。Windows 操作系统也有其移植版 pthreads-win32。
创建线程
1.pthread_create 创建一个新线程并使之运行起来。该函数可以在程序的任何地方调用包括线程内,线程是没有依赖关系的。
2. 一个进程可以创建的线程最大数量取决于系统实现
3. pthread_create 参数:
thread:返回一个不透明的,唯一的新线程标识符。
attr:不透明的线程属性对象。可以指定一个线程属性对象,或者 NULL 为缺省值。
start_routine:线程将会执行一次的 C 函数。
arg: 传递给 start_routine 单个参数,传递时必须转换成指向 void 的指针类型。没有参数传递时,可设置为 NULL。
pthread_create (threadid,attr,start_routine,arg)
结束线程
1. 结束线程的方法有一下几种:
线程从主线程(main 函数的初始线程)返回。
线程调用了 pthread_exit 函数。
其它线程使用 pthread_cancel 函数结束线程。
调用 exec 或者 exit 函数,整个进程结束。
2. 如果 main()在其他线程创建前用 pthread_exit() 退出了,其他线程将会继续执行。否则,他们会随着 main 的结束而终止。
pthread_exit (status) int pthread_cancel(pthread_t threadid);
等待线程状态
pthread_join (threadid,status)
例子:
1 #include <stdio.h> 2 #include <pthread.h> //liunx 线程头文件 3 #include <stdlib.h> 4 //线程 5 void *thread1_proc(void *arg) 6 { 7 int i=*(int *)arg; //取出内容 8 free(arg);//释放空间 9 while(i<105) 10 { 11 printf("thread1:%-5d",i); 12 sleep(2);//延时等待两秒 13 i++; 14 } 15 printf("Thread1 finished!\n"); 16 pthread_exit(NULL);//终止当前线程 17 } 18 void main() 19 { 20 pthread_t thread1; 21 int *ixi=(int *)malloc(sizeof(int));//在堆中申请一块内容 22 *ixi=100; //存在内容 23 if(pthread_create(&thread1,NULL,thread1_proc,(void *)ixi)!=0)//创建线程 1 并传递参数 24 perror("Create thread failed:");//创建错误时执行 25 //终止当前线程,此时会子线程会执行完毕,相当于在此处 join 所有子线程一样 26 pthread_exit(NULL);//(1)结束主 27 // pthread_join(thread1,NULL);//(2)可替换上一条 28 printf("主线程已经退出,本条不执行"); //(1)不执行,(2)执行该条 29 }
多线程共享资源
共享资源时可能会出现操作未完成而被另一个线程打破,造成资源存取异常
锁
定义变量
#include <pthread.h>
pthread_mutex_t lockx;
初始化
pthread_mutex_init(&lockx,NULL);
上锁与解锁
pthread_mutex_lock(&lockx);//上锁 //独立资源 //代码块 pthread_mutex_unlock(&lockx);//解锁
信号量
实现循序控制
定义变量
#include <semaphore.h>
sem_t can_scanf;
初始化
sem_init(&can_scanf,0,1);
PV 操作
sem_wait(&can_scanf);//等待信号量置位并进行减一操作 sem_post(&can_scanf); //信号量加一 操作
例子
主线程负责从键盘获取两个整数,子线程 1 负责对这两个整数完成求和运算并把结果打印出来,子线程 2 负责对这两个整数完成乘法运算并打印出来。三个线程要求遵循如下同步顺序:
1. 主线程获取两个数;
2. 子线程 1 计算;
3. 子线程 2 计算;
4. 转(1)
1 #include <stdio.h> 2 #include <semaphore.h> 3 #include <pthread.h> 4 #include <stdlib.h> 5 sem_t can_add;//能够进行加法计算的信号量 6 sem_t can_mul;//能够进行输入的信号量 7 sem_t can_scanf;//能够进行乘法计算的信号量 8 int x,y; 9 void *thread_add(void *arg)//加法线程入口函数 10 { 11 while(1) 12 { 13 sem_wait(&can_add); 14 printf("%d+%d=%d\n",x,y,x+y); 15 sem_post(&can_mul); 16 } 17 } 18 void *thread_mul(void *arg)//乘法线程入口函数 19 { 20 while(1) 21 { 22 sem_wait(&can_mul); 23 printf("%d*%d=%d\n",x,y,x*y); 24 sem_post(&can_scanf); 25 } 26 } 27 int main() 28 { 29 pthread_t tid; 30 int arg[2]; 31 //信号量初始化 32 sem_init(&can_scanf,0,1); 33 sem_init(&can_add,0,0); 34 sem_init(&can_mul,0,0); 35 if(pthread_create(&tid,NULL,thread_add,NULL)<0) 36 { 37 printf("Create thread_add failed!\n"); 38 exit(0); 39 } 40 if(pthread_create(&tid,NULL,thread_mul,NULL)<0) 41 { 42 printf("Create thread_mul failed!\n"); 43 exit(0); 44 } 45 while(1) 46 { 47 sem_wait(&can_scanf);//等待信号量置位并进行减一操作 48 printf("Please input two integers:"); 49 scanf("%d%d",&x,&y); 50 sem_post(&can_add);//信号量加一 操作 51 } 52 }
Socket 编程
数据包的发送
(1)TCP(write/send)
基于流的,没有信息边界,所以发送的包的大小没有限制;但由于没有信息边界,就得要求要求应用程序自己能够把逻辑上的包分割出来。
(2)UDP(sendto)
基于包的,应用层的包在由下层包的传输过程中因尽量避免有分片——重组的发生,否则丢包的概率会很大,在以太网中,MTU 的大小为 46——1500 字节,除掉 IP 层头、udp 头,应用层的 UDP 包不要超过 1472 字节。鉴于 Internet 上的标准 MTU 值为 576 字节, 所以我建议在进行 Internet 的 UDP 编程时。 最好将 UDP 的数据长度控制在 548 字节 (576-8-20) 以内.
数据包的接收
(1)TCP(read/recv)
如果协议栈缓冲区实际收到的字节数大于所请求的字节数,则返回实际要读取的字节数,剩余未读取的字节数下次再读;
如果协议栈缓冲区实际收到的字节数小于所请求的字节数,则返回所能提供的字节数;
(2)UDP(recvfrom)
如果协议栈缓冲区实际收到的字节数大于所请求的字节数,在 linux 下会对数据报进行截段,并丢弃剩下的数据;
如果协议栈缓冲区实际收到的字节数小于所请求的字节数,则返回所能提供的字节数;
注意点
当发送函数返回时,并不表示数据包已经到达了目标计算机,仅仅说明待发送的数据包被协议栈给接收了;
UDP 的数据包要么被接收,要么丢失;TCP 的数据报一定会无差错按序交付给对方
TCP
服务端
1、连接 WiFi 或者开启 AP, 使模块接入网络
2、socket 创建一个套接字
socket 可以认为是应用程序和网络之间信息传输通道,所以 TCP 编程服务端、客户端的第一步就是要建立这个信息传输的通道,主要通过 socket 函数完成。
3、 Bind socket 信息
给在第一步中所创建的 socket 显式指定其 ip 地址和端口号 (bind)
其中结构体为:
//设置 server 的详情信息 struct sockaddr_in server_addr,client_addr; u32_t sock_size=sizeof(struct sockaddr_in); server_addr.sin_family = AF_INET; //IPV4 server_addr.sin_port = htons(2351); //端口 //绑定本机的所有 IP 地址 htonl(INADDR_ANY),确定某个 inet_addr(“172.16.4.1”) server_addr.sin_addr.s_addr =htonl(INADDR_ANY); bind(connect_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
4、 listen 确定请求队列的最大值
5、 accept 等待接入
此函数为所有网络函数中最难理解的一个函数,它的调用将意味着服务端开始处理外来请求,如果没有外来请求(也就是没有 listen 到请求进来)默认情况下则阻塞;当有外来请求时会新产生一个 soket,并返回其描述符,应用程序将在这个新的 socket 上和请求者进行会话(读、写该 socket),原套接字 sockfd 则继续侦听
6、 send
当 send 返回时,并不是表示数据已经发送到了对方,而仅仅表示数据已经到了协议栈的缓冲区中。最后一个值在 ESP32 中不可用
7、 recv
默认情况下,当没有可接收的数据时则阻塞,参数 len 表示最多接收多少个字节数, 成功的接受的字节数完全可以小于 len。最后一个值在 ESP32 中不可用
客户端
1、连接 WiFi 或者开启 AP, 使模块接入网络
2、socket 创建一个套接字,参考服务器
3、是指向服务端发起连接请求(请求成功的前提是服务端已经进入了 accept 状态)
结构体参数
//设置 server 的详情信息 struct sockaddr_in server_addr,client_addr; u32_t sock_size=sizeof(struct sockaddr_in); server_addr.sin_family = AF_INET; //IPV4 server_addr.sin_port = htons(2351); //端口 //绑定本机的所有 IP 地址 htonl(INADDR_ANY),确定某个 inet_addr(“172.16.4.1”) server_addr.sin_addr.s_addr = inet_addr("192.168.43.21"); int ret=connect(client_fd,(struct sockaddr*)&server_addr,sock_size);//连接服务器
4、recv 和 send
服务器示例
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <fcntl.h> 4 #include <sys/socket.h> 5 #include <arpa/inet.h> 6 #include <netinet/in.h> 7 #include <string.h> 8 #define MAXCONN 8 9 int main() 10 { 11 int listen_fd,comm_fd; 12 int ret; 13 int i=1; 14 struct sockaddr_in server_addr,client_addr; 15 int sock_size=sizeof(struct sockaddr_in); 16 listen_fd=socket(AF_INET,SOCK_STREAM,0);//创建一个 socket, 参数(IPV4,TCP,0) 17 if(listen_fd<0) 18 { 19 perror("Failed to create socket:"); 20 return -1; 21 } 22 bzero(&server_addr,sock_size);//清零 server_addr 23 server_addr.sin_family=AF_INET;//IPV4 24 server_addr.sin_port=htons(8000);//端口 25 server_addr.sin_addr.s_addr=INADDR_ANY;//绑定主机全部网络地址 26 setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int));//设置套接字关联的选 项 27 ret=bind(listen_fd,(struct sockaddr*)&server_addr,sock_size);//网络主机绑定 28 if(ret==0) 29 { 30 printf("Bind Successfully!\n"); 31 } 32 ret=listen(listen_fd,MAXCONN);//确定最大监听数 33 if(ret==0) 34 { 35 printf("Listen Successfully!\n"); 36 } 37 while((comm_fd=accept(listen_fd,(struct sockaddr*)&client_addr,&sock_size))>=0)//阻塞并等待接入 38 { 39 char ipaddr[16]; 40 inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipaddr,16);//网络地址符转换 41 printf("连接进入:%s\n",ipaddr); 42 while(1) 43 { 44 char buff[512]; 45 int count; 46 count=read(comm_fd,buff,511);//读数据,接收 47 if(count>0)//判断接收的字节数是否大于零 48 { 49 buff[count]=0;//截断字符串 50 printf("收到来自 %s 的数据:%s\n",ipaddr,buff); 51 if(strncmp(buff,"quit",4)==0)//判断退出条件 52 { 53 printf("%s 已经退出退出,等待下一个连接 \n",ipaddr); 54 break;//退出此个连接,进行下一个连接接入 55 } 56 write(comm_fd,buff,count);//写数据,发送 57 } 58 else 59 { 60 printf("A talking is over!\n"); 61 break; // 客户端断开 62 } 63 } 64 } 65 close(listen_fd);//关闭连接 66 return 0; 67 68 }
客户端示例
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <fcntl.h> 4 #include <sys/socket.h> 5 #include <arpa/inet.h> 6 #include <netinet/in.h> 7 #include <string.h> 8 #include <string.h> 9 int main(int argc,char **argv) 10 { 11 int client_fd; 12 int ret; 13 int count; 14 struct sockaddr_in server_addr; 15 char buf[512]; 16 char recv_buf[512]; 17 int sock_size=sizeof(struct sockaddr_in); 18 if(argc<2) 19 { 20 printf("Usage:./client serverip\n"); 21 return 0; 22 } 23 bzero(&server_addr,sock_size);//清零 server_addr 24 client_fd=socket(AF_INET,SOCK_STREAM,0);//创建一个 socket, 参数(IPV4,TCP,0) 25 server_addr.sin_family=AF_INET; 26 server_addr.sin_port=htons(8000); 27 server_addr.sin_addr.s_addr=inet_addr(argv[1]); 28 ret=connect(client_fd,(struct sockaddr*)&server_addr,sock_size);//连接服务器 29 if(ret<0) 30 { 31 perror("Failed to connect:"); 32 return -1; 33 } 34 printf("Connect successfully!\n"); 35 while(1) 36 { printf("请输入要发送的内容:"); 37 fgets(buf,512,stdin);//从键盘获取字符串 38 ret=write(client_fd,buf,strlen(buf));//写数据,发送 39 if(ret<=0) 40 break; 41 if(strncmp(buf,"quit",4)==0){ 42 printf("程序退出 \n"); 43 break; 44 } 45 count=read(client_fd,recv_buf,511);//读数据,接收 46 if(count>0) 47 { 48 recv_buf[count]=0;//截断接收的字符串 49 printf("Echo:%s\n",recv_buf); 50 } 51 else 52 { 53 break;// 服务器断开 54 } 55 } 56 close(client_fd);//关闭连接 57 return 0; 58 59 }
UDP
服务器
1、 创建 socket
2、 调用函数设置 udp 播
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen); 头文件:<sys/socket.h> level : 选项级别(例如 SOL_SOCKET) optname : 选项名(例如 SO_BROADCAST) optval : 存放选项值的缓冲区的地址 optlen : 缓冲区长度 返回值:成功返回 0 失败返回-1 并设置 errno
3、 绑定服务器信息 bind
4、 数据收发
数据发送
int sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, int tolen); 返回:大于 0-成功发送数据长度;-1-出错; UDP 套接字使用无连接协议,因此必须使用 sendto 函数,指明目的地址; msg: 发送数据缓冲区的首地址; len: 缓冲区的长度; flags: 传输控制标志,通常为 0; to: 发送目标; tolen: 地址结构长度——sizeof(struct sockaddr)
数据接收
int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen); 返回:大于 0——成功接收数据长度;-1——出错; buf: 接收数据的保存地址; len: 接收的数据长度 flags: 是传输控制标志,通常为 0; from: 保存发送方的地址 fromlen: 地址结构长度。
服务器示例
1 #include <sys/socket.h> 2 #include <netinet/in.h> 3 #include <arpa/inet.h> 4 #include <string.h> 5 #include <stdio.h> 6 int main() 7 { 8 int sockfd; 9 int ret; 10 char buff[512]; 11 char ipaddr[16]; 12 struct sockaddr_in server_addr,client_addr; 13 int i=1; 14 int sock_size=sizeof(struct sockaddr_in); 15 sockfd=socket(AF_INET,SOCK_DGRAM,0); 16 if(sockfd<0) 17 { 18 perror("Failed to socket:"); 19 return -1; 20 } 21 bzero(&server_addr,sock_size); 22 server_addr.sin_family=AF_INET;//服务器相关参数设置 23 server_addr.sin_port=htons(6000); 24 server_addr.sin_addr.s_addr=INADDR_ANY; 25 setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int)); 26 if(bind(sockfd,(struct sockaddr*)&server_addr,sock_size)<0)//等待客户端接入,阻塞 27 { 28 perror("Failed to bind:"); 29 return -1; 30 } 31 while(1) 32 { 33 ret=recvfrom(sockfd,buff,512,0,(struct sockaddr*)&client_addr,&sock_size);//收到数据包 34 if(ret>0) 35 { 36 buff[ret]=0; 37 inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipaddr,16);//网络地址符转换 38 printf("Receive a string from %s:%d,data:%s\n",ipaddr,client_addr.sin_port,buff); 39 if(strncmp(buff,"exit",4)==0){//退出 40 printf("Socket server exit "); 41 close(sockfd);//关闭 socket 42 break; 43 } 44 sendto(sockfd,buff,ret,0,(struct sockaddr*)&client_addr,sock_size); 45 } 46 } 47 close(sockfd); 48 }
客户端示例
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <fcntl.h> 4 #include <sys/socket.h> 5 #include <arpa/inet.h> 6 #include <netinet/in.h> 7 #include <string.h> 8 #include <strings.h> 9 int main(int argc,char **argv) 10 { 11 int client_fd; 12 int ret; 13 int count; 14 struct sockaddr_in server_addr,sock_addr; 15 char buf[512]; 16 char recv_buf[512]; 17 int sock_size=sizeof(struct sockaddr_in); 18 if(argc<2) 19 { 20 printf("Usage:./udpclient serverip\n"); 21 return 0; 22 } 23 client_fd=socket(AF_INET,SOCK_DGRAM,0); 24 bzero(&server_addr,sock_size); 25 server_addr.sin_family=AF_INET; 26 server_addr.sin_port=htons(6000); 27 server_addr.sin_addr.s_addr=inet_addr(argv[1]); 28 while(1) 29 { 30 printf("In:"); 31 fgets(buf,512,stdin); 32 ret=sendto(client_fd,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sock_size); 33 if(ret<0) 34 { 35 perror("Failed to sendto:"); 36 break; 37 } 38 if(strncmp(buf,"exit",4)==0) 39 break; 40 count=recvfrom(client_fd,recv_buf,512,0,(struct sockaddr*)&sock_addr,&sock_size); 41 if(count>0) 42 { 43 recv_buf[count]=0; 44 printf("Echo:%s\n",recv_buf); 45 } 46 else 47 { 48 perror("Failed to recvfrom:"); 49 break; 50 } 51 } 52 close(client_fd); 53 return 0; 54 55 }
参考:
https://www.cnblogs.com/mywolrd/archive/2009/02/05/1930707.html
物联网网关开发技术(罗老师)