Liunx C 编程之多线程与Socket

简介: Liunx C 编程之多线程与Socket多线程pthread.h是linux特有的头文件,POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。

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.一个进程可以创建的线程最大数量取决于系统实现

  1. 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
2 #include //liunx线程头文件
3 #include
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_mutex_t lockx;
初始化

pthread_mutex_init(&lockx,NULL);
上锁与解锁

pthread_mutex_lock(&lockx);//上锁
//独立资源
//代码块
pthread_mutex_unlock(&lockx);//解锁
信号量
实现循序控制
定义变量

include

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
2 #include
3 #include
4 #include
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=%dn",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=%dn",x,y,xy);
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
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
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("连接进入:%sn",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 的数据:%sn",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
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
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 serveripn");
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:%sn",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);
头文件:
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
2 #include
3 #include
4 #include
5 #include
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:%sn",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
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
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 serveripn");
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:%sn",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

物联网网关开发技术(罗老师)
原文地址https://www.cnblogs.com/dongxiaodong/p/11309140.html

相关文章
|
27天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
29天前
|
安全 Java UED
深入浅出Java多线程编程
【10月更文挑战第40天】在Java的世界中,多线程是提升应用性能和响应能力的关键。本文将通过浅显易懂的方式介绍Java中的多线程编程,从基础概念到高级特性,再到实际应用案例,带你一步步深入了解如何在Java中高效地使用多线程。文章不仅涵盖了理论知识,还提供了实用的代码示例,帮助你在实际开发中更好地应用多线程技术。
43 5
|
18天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
16天前
|
缓存 Java 调度
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
36 10
|
18天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
12天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
12天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
34 3
|
16天前
|
算法 调度 开发者
多线程编程核心:上下文切换深度解析
在多线程编程中,上下文切换是一个至关重要的概念,它直接影响到程序的性能和响应速度。本文将深入探讨上下文切换的含义、原因、影响以及如何优化,帮助你在工作和学习中更好地理解和应用多线程技术。
25 4
|
21天前
|
数据采集 存储 数据处理
Python中的多线程编程及其在数据处理中的应用
本文深入探讨了Python中多线程编程的概念、原理和实现方法,并详细介绍了其在数据处理领域的应用。通过对比单线程与多线程的性能差异,展示了多线程编程在提升程序运行效率方面的显著优势。文章还提供了实际案例,帮助读者更好地理解和掌握多线程编程技术。
|
18天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
52 1