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)
例子:
#include <stdio.h>
#include <pthread.h> //liunx线程头文件
#include <stdlib.h>
//线程
void *thread1_proc(void *arg)
{
int i=*(int *)arg; //取出内容
free(arg);//释放空间
while(i<)
{
printf("thread1:%-5d",i);
sleep();//延时等待两秒
i++;
}
printf("Thread1 finished!\n");
pthread_exit(NULL);//终止当前线程
}
void main()
{
pthread_t thread1;
int *ixi=(int *)malloc(sizeof(int));//在堆中申请一块内容
*ixi=; //存在内容
if(pthread_create(&thread1,NULL,thread1_proc,(void *)ixi)!=)//创建线程1并传递参数
perror("Create thread failed:");//创建错误时执行
//终止当前线程,此时会子线程会执行完毕,相当于在此处join所有子线程一样
pthread_exit(NULL);//(1)结束主
// pthread_join(thread1,NULL);//(2)可替换上一条
printf("主线程已经退出,本条不执行"); //(1)不执行,(2)执行该条
}
多线程共享资源
共享资源时可能会出现操作未完成而被另一个线程打破,造成资源存取异常
锁
定义变量
#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,,);
PV操作
sem_wait(&can_scanf);//等待信号量置位并进行减一操作
sem_post(&can_scanf); //信号量加一 操作
例子
主线程负责从键盘获取两个整数,子线程1负责对这两个整数完成求和运算并把结果打印出来,子线程2负责对这两个整数完成乘法运算并打印出来。三个线程要求遵循如下同步顺序:
1.主线程获取两个数;
2.子线程1计算;
3.子线程2计算;
4.转(1)
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <stdlib.h>
sem_t can_add;//能够进行加法计算的信号量
sem_t can_mul;//能够进行输入的信号量
sem_t can_scanf;//能够进行乘法计算的信号量
int x,y;
void *thread_add(void *arg)//加法线程入口函数
{
while()
{
sem_wait(&can_add);
printf("%d+%d=%d\n",x,y,x+y);
sem_post(&can_mul);
}
}
void *thread_mul(void *arg)//乘法线程入口函数
{
while()
{
sem_wait(&can_mul);
printf("%d*%d=%d\n",x,y,x*y);
sem_post(&can_scanf);
}
}
int main()
{
pthread_t tid;
int arg[];
//信号量初始化
sem_init(&can_scanf,,);
sem_init(&can_add,,);
sem_init(&can_mul,,);
if(pthread_create(&tid,NULL,thread_add,NULL)<)
{
printf("Create thread_add failed!\n");
exit();
}
if(pthread_create(&tid,NULL,thread_mul,NULL)<)
{
printf("Create thread_mul failed!\n");
exit();
}
while()
{
sem_wait(&can_scanf);//等待信号量置位并进行减一操作
printf("Please input two integers:");
scanf("%d%d",&x,&y);
sem_post(&can_add);//信号量加一 操作
}
}
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(); //端口
//绑定本机的所有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(); //端口
//绑定本机的所有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
服务器示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#define MAXCONN 8
int main()
{
int listen_fd,comm_fd;
int ret;
int i=;
struct sockaddr_in server_addr,client_addr;
int sock_size=sizeof(struct sockaddr_in);
listen_fd=socket(AF_INET,SOCK_STREAM,);//创建一个socket,参数(IPV4,TCP,0)
if(listen_fd<)
{
perror("Failed to create socket:");
return -;
}
bzero(&server_addr,sock_size);//清零server_addr
server_addr.sin_family=AF_INET;//IPV4
server_addr.sin_port=htons();//端口
server_addr.sin_addr.s_addr=INADDR_ANY;//绑定主机全部网络地址
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int));//设置套接字关联的选 项
ret=bind(listen_fd,(struct sockaddr*)&server_addr,sock_size);//网络主机绑定
if(ret==)
{
printf("Bind Successfully!\n");
}
ret=listen(listen_fd,MAXCONN);//确定最大监听数
if(ret==)
{
printf("Listen Successfully!\n");
}
while((comm_fd=accept(listen_fd,(struct sockaddr*)&client_addr,&sock_size))>=)//阻塞并等待接入
{
char ipaddr[];
inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipaddr,);//网络地址符转换
printf("连接进入:%s\n",ipaddr);
while()
{
char buff[];
int count;
count=read(comm_fd,buff,);//读数据,接收
if(count>)//判断接收的字节数是否大于零
{
buff[count]=;//截断字符串
printf("收到来自 %s 的数据:%s\n",ipaddr,buff);
if(strncmp(buff,"quit",)==)//判断退出条件
{
printf("%s已经退出退出,等待下一个连接\n",ipaddr);
break;//退出此个连接,进行下一个连接接入
}
write(comm_fd,buff,count);//写数据,发送
}
else
{
printf("A talking is over!\n");
break; //客户端断开
}
}
}
close(listen_fd);//关闭连接
return ; }
客户端示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <string.h>
int main(int argc,char **argv)
{
int client_fd;
int ret;
int count;
struct sockaddr_in server_addr;
char buf[];
char recv_buf[];
int sock_size=sizeof(struct sockaddr_in);
if(argc<)
{
printf("Usage:./client serverip\n");
return ;
}
bzero(&server_addr,sock_size);//清零server_addr
client_fd=socket(AF_INET,SOCK_STREAM,);//创建一个socket,参数(IPV4,TCP,0)
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons();
server_addr.sin_addr.s_addr=inet_addr(argv[]);
ret=connect(client_fd,(struct sockaddr*)&server_addr,sock_size);//连接服务器
if(ret<)
{
perror("Failed to connect:");
return -;
}
printf("Connect successfully!\n");
while()
{ printf("请输入要发送的内容:");
fgets(buf,,stdin);//从键盘获取字符串
ret=write(client_fd,buf,strlen(buf));//写数据,发送
if(ret<=)
break;
if(strncmp(buf,"quit",)==){
printf("程序退出\n");
break;
}
count=read(client_fd,recv_buf,);//读数据,接收
if(count>)
{
recv_buf[count]=;//截断接收的字符串
printf("Echo:%s\n",recv_buf);
}
else
{
break;//服务器断开
}
}
close(client_fd);//关闭连接
return ; }
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-成功发送数据长度;--出错;
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——成功接收数据长度;-——出错;
buf:接收数据的保存地址;
len:接收的数据长度
flags:是传输控制标志,通常为0;
from:保存发送方的地址
fromlen: 地址结构长度。
服务器示例
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
int main()
{
int sockfd;
int ret;
char buff[];
char ipaddr[];
struct sockaddr_in server_addr,client_addr;
int i=;
int sock_size=sizeof(struct sockaddr_in);
sockfd=socket(AF_INET,SOCK_DGRAM,);
if(sockfd<)
{
perror("Failed to socket:");
return -;
}
bzero(&server_addr,sock_size);
server_addr.sin_family=AF_INET;//服务器相关参数设置
server_addr.sin_port=htons();
server_addr.sin_addr.s_addr=INADDR_ANY;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int));
if(bind(sockfd,(struct sockaddr*)&server_addr,sock_size)<)//等待客户端接入,阻塞
{
perror("Failed to bind:");
return -;
}
while()
{
ret=recvfrom(sockfd,buff,,,(struct sockaddr*)&client_addr,&sock_size);//收到数据包
if(ret>)
{
buff[ret]=;
inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipaddr,);//网络地址符转换
printf("Receive a string from %s:%d,data:%s\n",ipaddr,client_addr.sin_port,buff);
if(strncmp(buff,"exit",)==){//退出
printf("Socket server exit ");
close(sockfd);//关闭socket
break;
}
sendto(sockfd,buff,ret,,(struct sockaddr*)&client_addr,sock_size);
}
}
close(sockfd);
}
客户端示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
int main(int argc,char **argv)
{
int client_fd;
int ret;
int count;
struct sockaddr_in server_addr,sock_addr;
char buf[];
char recv_buf[];
int sock_size=sizeof(struct sockaddr_in);
if(argc<)
{
printf("Usage:./udpclient serverip\n");
return ;
}
client_fd=socket(AF_INET,SOCK_DGRAM,);
bzero(&server_addr,sock_size);
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons();
server_addr.sin_addr.s_addr=inet_addr(argv[]);
while()
{
printf("In:");
fgets(buf,,stdin);
ret=sendto(client_fd,buf,strlen(buf),,(struct sockaddr*)&server_addr,sock_size);
if(ret<)
{
perror("Failed to sendto:");
break;
}
if(strncmp(buf,"exit",)==)
break;
count=recvfrom(client_fd,recv_buf,,,(struct sockaddr*)&sock_addr,&sock_size);
if(count>)
{
recv_buf[count]=;
printf("Echo:%s\n",recv_buf);
}
else
{
perror("Failed to recvfrom:");
break;
}
}
close(client_fd);
return ; }
参考:
https://www.cnblogs.com/mywolrd/archive/2009/02/05/1930707.html
物联网网关开发技术(罗老师)
Liunx C 编程之多线程与Socket的更多相关文章
- 多线程Java Socket编程示例
package org.merit.test.socket; import java.io.BufferedReader; import java.io.IOException; import jav ...
- 并发编程~~~多线程~~~计算密集型 / IO密集型的效率, 多线程实现socket通信
一 验证计算密集型 / IO密集型的效率 IO密集型: IO密集型: 单个进程的多线程的并发效率高. 计算密集型: 计算密集型: 多进程的并发并行效率高. 二 多线程实现socket通信 服务器端: ...
- c/c++ 网络编程与多线程 编译参数
网络编程与多线程 编译参数 编译时要链接操作系统的pthread库 g++ -g socket01.cpp -std=c++11 -pthread 不加-pthread的话,出现下面的错误: term ...
- 《Unity 3D游戏客户端基础框架》多线程异步 Socket 框架构建
引言: 之前写过一个 demo 案例大致讲解了 Socket 通信的过程,并和自建的服务器完成连接和简单的数据通信,详细的内容可以查看 Unity3D -- Socket通信(C#).但是在实际项目应 ...
- 可扩展多线程异步Socket服务器框架EMTASS 2.0 (转自:http://blog.csdn.net/hulihui)
可扩展多线程异步Socket服务器框架EMTASS 2.0 (转自:http://blog.csdn.net/hulihui) 0 前言 >>[前言].[第1节].[第2节].[第3节]. ...
- TCP/IP网络编程之多线程服务端的实现(二)
线程存在的问题和临界区 上一章TCP/IP网络编程之多线程服务端的实现(一)的thread4.c中,我们发现多线程对同一变量进行加减,最后的结果居然不是我们预料之内的.其实,如果多执行几次程序,会发现 ...
- 网络编程之多线程——GIL全局解释器锁
网络编程之多线程--GIL全局解释器锁 一.引子 定义: In CPython, the global interpreter lock, or GIL, is a mutex that preven ...
- Python拾忆--多线程的socket服务器
阳光明媚的午后,想想最近要开始从写Java到写Python了,就随手打开电脑来体验一下Python与Java之间的不同吧~ 记得我还在上大二的时候,那个时候才开始学Java,最感兴趣的就是Java书最 ...
- 网络编程:Http通信与Socket通信
http://note.youdao.com/share/?id=f14d304548003f65e34255d3ddf9df31&type=note 网络编程:Http通信与Socket通信 ...
随机推荐
- Codeforces 758D:Ability To Convert(思维+模拟)
http://codeforces.com/problemset/problem/758/D 题意:给出一个进制数n,还有一个数k表示在n进制下的值,求将这个数转为十进制最小可以是多少. 思路:模拟着 ...
- django基础知识之认识MVT MVC:
MVT Django是一款python的web开发框架 与MVC有所不同,属于MVT框架 m表示model,负责与数据库交互 v表示view,是核心,负责接收请求.获取数据.返回结果 t表示templ ...
- 删除git中缓存的用户名和密码
我们使用Git命令去clone Gitlab仓库的代码时,第一次弹框提示输入账号密码的时候输错了,然后后面就一直拒绝,不再重复提示输入账号密码,怎么破? git报错信息 运行一下命令缓存输入的用户名和 ...
- scala刷LeetCode--26 删除排序数组中的重复项
一.题目描述 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完 ...
- extern和static区别
1. 声明和定义 当定义一个变量的时候,就包含了对该变量声明的过程,同时在内存张申请了一块内存空间.如果在多个文件中使用相同的变量,为了避免重复定义,就必须将声明和定义分离开来.定义是创建与名字关 ...
- asp.net core系列 68 Filter管道过滤器
一.概述 本篇详细了解一下asp.net core filters,filter叫"筛选器"也叫"过滤器",是请求处理管道中的特定阶段之前或之后运行代码.fil ...
- 七牛云图床和Markdown使用
七牛云图床和Markdown使用 1.图床是什么? 图床一般是指储存图片的服务器,有国内和国外之分.国外的图床由于有空间距离等因素决定访问速度很慢影响图片显示速度.国内也分为单线空间.多线空间和cdn ...
- CSDN怎么一键转载别人的博客
在参考"如何快速转载CSDN中的博客"后,由于自己不懂html以及markdown相关知识,所以花了一些时间来弄明白怎么转载博客,以下为转载CSDN博客步骤和一些知识小笔记. 参考 ...
- python爬虫笔记之爬取足球比赛赛程
目标:爬取某网站比赛赛程,动态网页,则需找到对应ajax请求(具体可参考:https://blog.csdn.net/you_are_my_dream/article/details/53399949 ...
- SpringBoot快速入门01--环境搭建
SpringBoot快速入门--环境搭建 1.创建web工程 1.1 创建新的工程. 1.2 选择maven工程,点击下一步. 1.3 填写groupid(maven的项目名称)和artifacti ...