嵌入式之网络编程

一、IO

input:从磁盘读取数据到内存中

output: 把内存中的数据写入到磁盘上

(一)文件 

嵌入式之网络编程_第1张图片

(二)文件类型

嵌入式之网络编程_第2张图片嵌入式之网络编程_第3张图片

常规文件       -   1.c 1.txt 1.sh 1.cpp a.out    
目录文件    d   day1    
字符设备     c   键盘鼠标摄像头    
块设备         b   磁盘    
管道文件    p   pipe    
套接字文件   s   网络通信的通道 
符号链接文件 l  类似于windows快捷方式 

(三)文件IO和标准IO

嵌入式之网络编程_第4张图片

嵌入式之网络编程_第5张图片

1、文件IO------(linux系统提供) 系统调用:当我们的应用程序要使用一些底层的功能的时候,不应该自行访问底层,而应该向操作系统发出请求。

特点:

  1. 不带缓冲区

  2. 操作系统直接提供的函数接口

  3. 调用系统调用是很耗费资源的

 2、标准IO------(C库提供)

C库函数:在系统调用接口之上封装的接口,一个C库函数可以封装多个系统调用函数。

作用:

  1. 增强了代码的可移植性,复用性

  2. 提高了效率。 标准IO增加了一个【缓冲机制】

(四)标椎IO

1、fopen

FILE *fopen(const char *pathname, const char *mode); 

功能:打开文件
参数:
    pathname: 文件的路径名
    mode: 打开文件的方式
        r: 以只读的方式打开文件,文件已存在  
        r+ : 以读写的方式打开文件,文件已存在            
        w:  以可写的方式打开文件,文件已存在,则清空文件的内容
                              文件不存在,则创建一个文件            
        w+: 以读写的方式打开文件,文件已存在,则清空文件的内容
                              文件不存在,则创建一个文件                   
        a: 以可写的方式打开文件,文件存在,追加内容在末尾                             
        a+: 以读写的方式打开文件,文件存在,追加内容在末尾
返回值:
        成功: FILE * 流指针 本质上是一个结构体指针
        失败: NULL 

FILE:系统会自动为使用的文件在内存中开辟一片空间,来存储该文件的详细信息,这个空间类型为 FILE 结构体类型,该结构体由系统设计。

FILE *:流指针,在标准IO中,每次成功打开一个文件,都会返回一个流指针,这个流指针就描述了一个文件,所有的标准IO都围绕流指针来进行。  

#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	FILE *fp;
	int count = 0;
	while(1)
	{
		fp= fopen("2.txt","w");
		if(fp == NULL){
			//printf("%s\n", strerror(errno));
			perror("fopen 2.txt");
		    //return -1;
			break;
		}
		count++;
		printf("fopen success!\n");
	}
	printf("count = %d\n", count);

	//fclose(fp);

	return 0;
}

2、fclose

int fclose(FILE *stream); 

功能: 关闭流指针
参数: 流指针

为什么要关闭一个文件?

一、防止其他进程操作这个文件

二、释放结构体占用的资源

在程序结束时,系统自动回收资源(不完全),所以尽量写上fclose。

3、fgetc

 int fgetc(FILE *stream);

注意:每次调用fgetc,它的文件指针会偏移

功能: 读取一个字节的数据
参数: 流指针
返回值:
        成功:ASCII码值
        失败:-1

注意:1、fgetc的返回值类型是int!!! ,如果系统不一样定义成char可能会有问题

2、fgetc函数每行字符结束都会加'\n'

代码: 

嵌入式之网络编程_第6张图片

嵌入式之网络编程_第7张图片

结果:

嵌入式之网络编程_第8张图片

转载:fgetc踩过的坑icon-default.png?t=N7T8https://blog.csdn.net/modi000/article/details/105839310?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169251678316800197062649%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=169251678316800197062649&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-4-105839310-null-null.142%5Ev93%5Einsert_down28v1&utm_term=fgetc%E5%9D%91&spm=1018.2226.3001.4187

4、fputc

 int fputc(int c, FILE *stream);

注意:每次调用fputc,它的文件指针会偏移

功能:写入一个字节的数据

参数:
    c: 一个字节
    stream:流指针

返回值:
    成功: ASCII码值
    失败: -1

#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("a.txt", "w+");
	if(fp == NULL)
	{
		perror("fopen a.txt");
		return -1;
	}

	fputc(97, fp);
	fputc('a', fp);
	fputc(0141, fp);
	fputc(0x61, fp);
	fputc(98, fp);
	fputc('\n', fp);
	

	rewind(fp); //把文件指针移到文件的开头


	printf("%d\n", fgetc(fp));
	printf("%d\n", fgetc(fp));
	printf("%d\n", fgetc(fp));
	printf("%d\n", fgetc(fp));
	printf("%d\n", fgetc(fp));
	printf("%d\n", fgetc(fp));
	printf("%d\n", fgetc(fp));


	fclose(fp);

	return 0;
}
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("a.txt", "r+");
	if(fp == NULL)
	{
		perror("fopen a.txt");
		return -1;
	}

	printf("%d\n", fgetc(fp));
	printf("%d\n", fgetc(fp));
	
	fputc('c', fp);
	fputc('d', fp);


	fclose(fp);

	return 0;
}

#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("a.txt", "r");
	if(fp == NULL)
	{
		perror("fopen a.txt");
		return -1;
	}
	
	int n1 = fputc('1', fp);
	int n2 = fputc('2', fp);

	printf("n1=%d n2=%d\n", n1,n2);


	fclose(fp);

	return 0;
}

5、fgets

char *fgets(char *s, int size, FILE *stream);

注意:每次调用fgets,它的文件指针会偏移

功能:读取一行的数据,存到s中 
参数:
    s:代表要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名  char s[];    
    size: 读取的字节数 size-1,因为fgets会在字符串后加'\0',所以要-1   
    stream: 流指针   
返回值:
    成功:字符串首元素地址
    失败:NULL

注意:这里的一行,是指 size-1 或者 '\n'        
读到文件的末尾,返回NULL

#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	char buf[100] = {0};

	char *p = fgets(buf, 10, stdin);
	
	printf("%s\n", p);

	return 0;
}
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	char buf[10] = {0};

	fgets(buf, 10, stdin);

	//去除换行
	buf[strlen(buf)-1] = '\0';

	printf("%s", buf);

	return 0;
}

6、fputs

int fputs(const char *s, FILE *stream);

注意:每次调用fputs,它的文件指针会偏移

功能:把内存中的数据写入到流指针指向的文件中        
参数:
    s :内存地址 char s[];    
    stream: 流指针    
返回值:    
    成功:非负值
    失败:-1 

#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("4.txt", "w+");

	char buf[100] = "hello world\n";

	while(1)
	{
		fgets(buf, 100, stdin);

		buf[strlen(buf)-1] = '\0';

		if(strcmp(buf, "quit") == 0 )
		{
			break;
		}

		fputs(buf, fp);

		memset(buf, 0 , 100);
	}

	fclose(fp);

	return 0;
}

7、rewind 

void rewind(FILE *stream) ;

功能:将文件指针移动到文件开头
参数:流指针 

8、fseek 

int fseek(FILE *stream, long offset, int whence); 

功能:定位文件指针
参数:
    stream: 流指针
    offset: 偏移量            
            +100 :向后偏移100个字节
            -100 :向前偏移100个字节            
    whence:    基点
            SEEK_SET  文件开头 
            SEEK_END  文件末尾
            SEEK_CUR  文件当前的位置
返回值:
        成功:0
        失败:-1

定位到文件末尾: fseek( fp , 0 , SEEK_END );

定位到文件末尾的前一个字节:fseek( fp , -1 , SEEK_END );

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("aa.txt", "w+");
	fputs("hello world\n", fp);
	fseek(fp, -6, SEEK_END);
	fputs("xxx", fp);
	fputc('\0', fp);
	fseek(fp, 0, SEEK_SET);
	char buf[100] = {0};
	fgets(buf, 100, fp);
	printf("%s\n", buf);
	fclose(fp);
	return 0;
}

 9、ftell

long ftell(FILE *stream); 

功能: 返回文件指针到文件开头的长度 (获取文件大小)
参数:流指针

#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("1.txt", "r+");

	fputs("hello world\n", fp);

	long int n = ftell(fp);

	printf("%ld\n", n);

	fseek(fp, -7  , SEEK_CUR);

	n = ftell(fp);
	printf("%ld\n", n);

	fclose(fp);

	return 0;
}

10、fread

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

功能: 读取数据    
参数:    
    ptr:内存地址 char buf[];    
    size: 每块的字节数        
    nmemb:块数            
    stream:流指针        
返回值:    
    成功:块数
    失败:-1 

#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("1.txt", "rb");
	if(fp == NULL)
	{
		perror("fopen 1.txt");
		return -1;
	}

	char buf[1000] = {0};

	int m = fread(buf, 1000, 1, fp);
	
	printf("m=%d\nbuf=%s\n", m, buf);

	fclose(fp);

	return 0;
}

11、fwrite

size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); 

功能: 写入数据    
参数:    
    ptr:内存地址     
    size: 每块的字节数       
    nmemb:块数            
    stream:流指针        
返回值:    
    成功:块数
    失败:-1 

struct AA{
	int a;
	float b;
	char c;
};

int main(int argc, const char *argv[])
{
	struct AA t = {100, 3.14 , 'b'};
	struct AA n;
	FILE *fp = fopen("a.txt", "wb+");
	int m = fwrite( &t, sizeof(struct AA), 1, fp);
	printf("m=%d\n", m);
	rewind(fp);
	fread( &n, sizeof(struct AA), 1, fp);
	printf("%d %.2f %c\n", n.a, n.b, n.c);
	fclose(fp);
	return 0;
}

(五)缓存 

1、缓冲区类型

分为三种:行缓存、无缓存、全缓存  

1、行缓存:和终端相关的缓冲区就是行缓存。典型代表:printf、stdin、stdout

2、无缓存:没有缓冲区。典型代表:stderr

3、全缓存:和文件相关的就是全缓存,fopen打开的文件。典型代表是对磁盘文件的读写 

2、缓存大小

1、行缓存:大小1024

#include 
 
int main(int argc, const char* argv[])
{
    int ch;
    对于标准输入行缓存大小验证的时候,必须有输入才有数据
    scanf("%d", &ch);
    printf("size = %ld\n", stdin->_IO_buf_end - stdin->_IO_read_base);
     1024
 
    printf("size = %ld\n", stdout->_IO_buf_end - stdout->_IO_read_base);
     1024
 
    fprintf(stderr,"q121231qweqweqweqweqweqweqwe2");
    printf("size = %ld\n", stderr->_IO_buf_end - stderr->_IO_read_base);
    0
    while(1);
 
    return 0;
}

2、全缓存:大小4096

#include 
 
int main(int argc, const char* argv[])
{
    FILE* fp;
 
    if ((fp = fopen("./tim.txt", "a+")) == NULL)
        PRINT_ERR("fopen error");
 
    fprintf(fp,"q121231qweqweqweqweqweqweqwe2");
    printf("size = %ld\n",fp->_IO_buf_end - fp->_IO_buf_base);
    //4096
    return 0;
}

3、无缓存:为0

3、刷新缓存条件 

1.行缓存满了或遇到'\n'输出条件,可刷新行缓存

2.fflush可以强制刷新 (fflush)

3.文件关闭的时候 fclose(stdout)

4.程序结束的时候exit或return  

(六)printf相关函数 

1、fprintf

int fprintf(FILE *stream, const char *format, ...); 

注意缓存区问题

功能: 按照一定的格式组装数据,把数据放在流指针      
参数:
    stream:流指针    
    format:格式控制符    
    ... :不定参数    
返回值:    
    成功:字符串的长度
    失败:-1 

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("2.txt", "w");
	
	int m = fprintf(fp, "%d-%d-%d %d:%d:%d", 2023,8,15,11,33,30);

	printf("m=%d\n", m);

	int a = 10;
	char b = 'b';
	float c = 3.14;

	fprintf( stdout ,  "%d***%c***%.2f\n"  , a, b, c );

	fclose(fp);

	return 0;
}

 嵌入式之网络编程_第9张图片

2、fscanf 

int fscanf(FILE *stream, const char *format, ...); 

功能: 按照一定的格式提取数据,从流指针中提取数据到不定参数中(可用于提取值)      
参数:
    stream:流指针    
    format:格式控制符    
    ... :不定参数    
返回值:    
    成功:不定参数的个数
    失败:-1 

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./2.txt","w+");

	//fprintf(fp, "%d*%d",10,20);
	fputs("10*20", fp);

	rewind(fp);

	int a,b,c,d,e,f;

	int m = fscanf(fp, "%d*%d", &a, &b);

	printf("m = %d\n%d %d\n", m,a, b);

	fclose(fp);

	return 0;
}

3、sprintf

int sprintf(char *str, const char *format, ...);

注意缓存区问题

 功能: 按照一定的格式组装数据,把数据放在内存地址    
参数:
    str: 内存地址 char str[];    
    format:格式控制符    
    ... :不定参数    
返回值:    
    成功:字符串的长度
    失败:-1

#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	char buf[100] = {0};


	int m = sprintf(buf, " %d-%d-%d %d:%d:%d %.2f#%c#%c*%s \n", 2023, 8,15,11,45,40
						, 3.14 , 'a', 'B', "hello");
	
	printf("m=%d\n", m);

	printf("%s", buf);

	return 0;
}

4、sscanf 

int sscanf(const char *str, const char *format, ...);

功能: 按照一定的格式提取数据,从内存地址中提取数据到不定参数中      
参数:
    str:内存地址    
    format:格式控制符    
    ... :不定参数  
返回值:    
    成功:不定参数的个数
    失败:-1

int main(int argc, const char *argv[])
{
	char buf[100] = "97*98*99";

	int a,b,c;

	sscanf(buf, "%d*%d*%d", &a, &b, &c);

	printf("%c %c %c\n", a,b , c);
	printf("%d %d %d\n", a,b , c);
	printf("%#x %#x %#x\n", a,b,c);

	return 0;
}

(七)时间函数 

1、time

#include

time_t t;

time_t time(time_t *tloc);

功能: 从1970-1-1 0:0:0 到现在的秒数

time(&t); // time(NULL);

int main(int argc, const char *argv[])
{
	time_t t;

	while(1)
	{
		//printf("%ld\n", time(&t));
		printf("%ld\n", time(NULL));

		sleep(1);
	}

	return 0;
}

2、localtime

#include

time_t t;

struct tm *localtime(const time_t *timep);

功能: 把秒数转化成日历
参数:
    timep:&t
返回值:
    成功:struct tm*
    失败:NULL    

struct tm {
   int tm_sec;    /* Seconds (0-60) */
   int tm_min;    /* Minutes (0-59) */
   int tm_hour;   /* Hours (0-23) */
   int tm_mday;   /* Day of the month (1-31) */
   int tm_mon;    /* Month (0-11) */
   int tm_year;   /* Year - 1900 */
}; 

int main(int argc, const char *argv[])
{
	time_t t;

	FILE *fp = fopen("time.txt", "a");

	while(1)
	{
		//printf("%ld\n", time(&t));
		//printf("%ld\n", time(NULL));
		time(&t);

		struct tm *p = localtime(&t);

		printf("%d-%d-%d %d:%d:%d\n", 
						p->tm_year+1900,
						p->tm_mon+1,
						p->tm_mday,
						p->tm_hour,
						p->tm_min,
						p->tm_sec);
		
		fprintf(fp ,"%d-%d-%d %d:%d:%d\n", 
						p->tm_year+1900,
						p->tm_mon+1,
						p->tm_mday,
						p->tm_hour,
						p->tm_min,
						p->tm_sec);

		fflush(fp);

		sleep(1);
	}

	fclose(fp);

	return 0;
}

3、ctime 

char *ctime(const time_t *timep); 

功能: 把秒数转化成日历
参数:
    timep:&t
返回值:
    成功:字符串(日历)
    失败:NULL

#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	time_t t;

	while(1)
	{
		//printf("%ld\n", time(&t));
		//printf("%ld\n", time(NULL));
		time(&t);

		char *p = ctime(&t);

		printf("%s\n", p);

		sleep(1);
	}

	return 0;
}

(八)文件IO 

1、文件描述符

文件描述符实际上是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符,进程使用它来标识打开的文件。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

Linux系统为程序中每个打开的文件都分配一个文件描述符,文件IO操作通过文件描述符来完成。

文件描述符在形式上是一个顺序分配的非负整数。从0开始分配,依次递增。比如 0,1,2表示 stdin stdout stderr,一般最大打开的文件描述符数量为1024(0~1023)

2、open

#include
#include
#include

int open(const char *pathname, int flags);
功能:打开已经存在的文件

int open(const char *pathname, int flags, mode_t mode);
功能:创建文件 (文件是不存在的) 

参数:
    pathname:文件的路径名      
    flags: 打开方式
                O_RDONLY 只读
                O_WRONLY 可写
                O_CREAT  创建
                O_RDWR     读写
                O_TRUNC  清空
                O_APPEND 追加

    mode: 创建文件的权限 rw-rw-rw-   0666 
返回值:
    成功:文件描述符 一个非负整数 从0开始 范围0~1023 
         每打开一个文件,就会有一个对应文件描述符产生
    失败:-1

实际创建的文件权限需要经过一个公式计算得到: mode & (~umask) 注:umask=002 

标准IO         文件IO
    r              O_RDONLY
    r+            O_RDWR
    w             O_WRONLY | O_CREAT | O_TRUNC
    w+           O_RDWR   | O_CREAT | O_TRUNC
    a              O_WRONLY | O_CREAT | O_APPEND
    a+            O_RDWR   | O_CREAT | O_APPEND 

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	int fd = open("./b.txt", O_CREAT | O_RDWR, 0666);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}

	printf("open success!\n");

	printf("fd = %d\n", fd); //3


	close(fd);

	return 0;
}

3、close

#include

int close(int fd);

功能: 关闭文件描述符
参数: 文件描述符

4、read

#include  

ssize_t read(int fd, void *buf, size_t count);

功能:从文件中读取数据到内存
参数:
    fd: 文件描述符    
    buf: 内存地址        
    count : 读取的字节数
返回值:
    成功: 
        > 0    实际读取的字节数        
        == 0   读到文件末尾了     
    失败:   
        < 0 

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	int fd = open("./1.txt",  O_RDONLY);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}

	printf("open success!\n");

	printf("fd = %d\n", fd); //3

	//read
	char buf[100] = {0};

	int num = 0;

	while(1)
	{
		int n = read(fd, buf, 99);
		if(n < 0)
		{
			perror("read");
			break;
		}
		else if(n == 0) //说明读到文件的末尾了
		{
			break;
		}
		
		num += n;

		printf("n=%d\nbuf=%s\n", n, buf);
		
		memset(buf, 0 ,100); //bzero(buf,100);
	}

	printf("num = %d\n", num);

	close(fd);

	return 0;
}

5、write

#include  

ssize_t write(int fd, const void *buf, size_t count);

功能:把内存中的数据写入到文件中     
参数:
    fd: 文件描述符    
    buf: 内存地址        
    count : 写入的字节数
返回值:
    成功: 
        >= 0    实际写入的字节数       
    失败:   
        < 0 

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	int fd = open("./2.txt", O_RDWR|O_TRUNC);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}

	printf("open success!\n");

	printf("fd = %d\n", fd); //3

	//write
	
	int m = write(fd, "hello world\n", 12);

	printf("m=%d\n", m);


	close(fd);

	return 0;
}

6、lseek

#include
#include

off_t lseek(int fd, off_t offset, int whence);

功能:定位文件指针
参数:
    fd: 文件描述符    
    offset: 偏移量            
            +100 :向后偏移100个字节
            -100 :向前偏移100个字节           
    whence:    基点
            SEEK_SET  文件开头 
            SEEK_END  文件末尾
            SEEK_CUR  文件当前的位置
返回值:
        成功:0
        失败:-1

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	int fd = open("./test.txt", O_CREAT| O_RDWR|O_TRUNC, 0666);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}

	printf("open success!\n");

	printf("fd = %d\n", fd); //3


	//LSEEK
	write( fd,  "hello", 5);


	lseek(fd, 100, SEEK_END);


	write(fd, "world", 5);


	close(fd);

	return 0;
}

7、opendir

#include
#include

DIR *opendir(const char *name);

功能:打开目录
参数:
    name:目录名
返回值:
    成功: DIR *
    失败: NULL 

#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	//1.opendir
	if(argc != 2)
	{
		printf("%s dir\n", argv[0]);
		return -1;
	}

	DIR *dir = opendir( argv[1] );
	if(dir == NULL)
	{
		perror("opendir");
		return -1;
	}

	while(1)
	{
		struct dirent *p = readdir(dir);
		if(p == NULL)
		{
			break;
		}

		if( strncmp(p->d_name, ".",1) == 0 )
		{
			continue;
		}

		printf("%s\n", p->d_name);
	}

	closedir(dir);

	return 0;
}

 8、dirent

#include
#include

struct dirent *readdir(DIR *dirp);

功能:读取目录中的信息
参数:
    dirp: opendir 的返回值        
返回值:
    成功: struct dirent *
    失败: NULL (一直遍历目录,直到遍历完成,返回NULL) 
      
struct dirent {
               ino_t          d_ino;       /* Inode number */
               off_t          d_off;       /* Not an offset; see below */
               unsigned short d_reclen;    /* Length of this record */
               unsigned char  d_type;      /* Type of file; not supported
                                                              by all filesystem types */

               char           d_name[256]; /* Null-terminated filename */
           };

 9、closedir

#include
#include

int closedir(DIR *dirp);

功能:关闭目录    
参数:    
    dirp: opendir 的返回值 

10、stat  

#include
#include
#include

struct stat mybuf;

int stat(const char *path, struct stat *buf);

功能:得到文件的属性
参数:
      path:文件路径名
      buf: &mybuf

-rw-rw-r-- 1 farsight farsight   593  4月 19 09:26 01picture.c
-rw-rw-r-- 1 farsight farsight   537  4月 19 09:53 02readdir.c
-rw-rw-r-- 1 farsight farsight   161  4月 19 10:23 03stat.c
-rwxrw-rw- 1 farsight farsight 97893  4月 19 09:26 5.jfif
-rwxrwxr-x 1 farsight farsight  7357  4月 19 09:53 a.out
    
//查看设备号
major   minor  
    
struct stat {
    dev_t     st_dev;     /* ID of device containing file */
    ino_t     st_ino;     /* inode number */
    mode_t    st_mode; 文件的权限、文件的类型   /* protection */
    nlink_t   st_nlink;   /* number of hard links */
    uid_t     st_uid;  所属用户ID   /* user ID of owner */ 
    gid_t     st_gid;  所属组ID   /* group ID of owner */
    dev_t     st_rdev;    /* device ID (if special file) */
    off_t     st_size; 文件的大小   /* total size, in bytes */
    blksize_t st_blksize; /* blocksize for filesystem I/O */
    blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
    time_t    st_atime; 最后一次访问时间  /* time of last access */
    time_t    st_mtime; 最后一次修改时间  /* time of last modification */
    time_t    st_ctime; 最后一次文件文件属性修改时间  /* time of last status change */
};

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
	struct stat mybuf;
	
	stat("./hk/", &mybuf);

	//类型
	if(S_ISREG(mybuf.st_mode))
		printf("- 常规文件\n");
	else if(S_ISDIR(mybuf.st_mode))
		printf("d 目录文件\n");


	//权限
	if( mybuf.st_mode & (0x1<<0) )
	
		printf("other: x\n");
	else
		printf("other: -\n");

	if( mybuf.st_mode & (0x1<<1) )
	
		printf("other: w\n");
	else
		printf("other: -\n");

	if( mybuf.st_mode & (0x1<<2) )
	
		printf("other: r\n");
	else
		printf("other: -\n");

	//硬链接
	printf("link = %ld\n", mybuf.st_nlink);

	//组名
	struct passwd *p = getpwuid(mybuf.st_uid);

	printf("username = %s\n", p->pw_name);

	//size
	printf("size=%ld\n", mybuf.st_size);

	//time
	struct tm *k = localtime(&mybuf.st_mtime);

	printf("%d-%d %d:%d\n", k->tm_year+1900,
							k->tm_mon+1,
							k->tm_mday,
							k->tm_hour);

	return 0;
}

 11、getpwuid

struct passwd *getpwuid(uid_t uid);
获取用户名 

struct passwd {
   char   *pw_name;       /* username */
   char   *pw_passwd;     /* user password */
   uid_t   pw_uid;        /* user ID */
   gid_t   pw_gid;        /* group ID */
   char   *pw_gecos;      /* user information */
   char   *pw_dir;        /* home directory */
   char   *pw_shell;      /* shell program */
}; 

12、getgrgid  

struct group *getgrgid(gid_t gid);
获取组名 

struct group {
    char   *gr_name;       /* group name */
    char   *gr_passwd;     /* group password */
    gid_t   gr_gid;            /* group ID */
    char  **gr_mem;        /* group members */
}; 

示例:stat.c  

文件类型
{
    常规文件:S_ISREG     '-'
    目录:S_ISDIR         'd'
    字符设备:S_ISCHR     'c'
    块设备:S_ISBLK     'b'
    管道:S_ISFIFO         'p'
    套接字:S_ISSOCK     's'
    符号链接:S_ISLNK     'l'

二、进线程

什么是进程?

是正在运行的程序的实例。当程序被操作系统加载到内存中并执行时,就产生了一个进程。一个进程可以包含多个线程,各自执行不同的任务。进程之间通常是相互独立的,无法直接访问对方的内存空间。通常一个进程一定程度上可以看成一个程序。

进程的概念 

1、从用户的角度来说,进程就是程序的一次动态执行过程。
2、从操作系统角度:操作系统分配内存,CPU时间等系统资源的基本单位。

特点

1、每一个进程都有自己独立的虚拟地址空间和进程状态。
2、进程是分配资源的最小单位(基本单位)。
3、程序执行一次的过程,包含  创建,调度,消亡, 是动态的。
4、包括代码段,数据段,堆栈,PCB模块 task_struct(进程相关属性,时间片,进程id...)。

什么是线程?

是进程中的一个执行单元。一个进程可以包含多个线程,它们共享同一个地址空间和系统资源。线程之间的切换比进程之间的切换开销小,因此线程的创建和销毁比进程更加高效。

特点 

1、存储在磁盘上的可执行的二进制编码, 是静态的
2、包括代码段,数据段 

什么是程序?

 是一系列指令的集合,用于完成特定任务。它是静态的,通常存储在磁盘或其他存储介质上,并不会直接执行。例如,一个文本编辑器的程序包括保存、打开、编辑等功能的指令集合。

进程和程序的区别和联系 

联系

1、程序 = 文件(静态的可执行文件)
2、进程 = 执行中的程序 = 程序+执行状态
3、同一个程序的多次执行对应为不同的进程(ls的多次执行对应多个不同进程)
4、进程执行需要的资源:内存(保存代码和数据)+cpu(执行指令)

区别
1、程序是静态的,进程是动态的
(1)程序是有序代码的集合
(2)进程是程序的执行,进程有内核状态、用户态

2、进程相对于程序的生命周期是短暂的,程序是永久的

3、进程有PCB的数据结构
(1)程序 :数据段+代码段
(2)进程 :代码段+数据段+堆栈+PCB

4、一个进程只能对应一个程序,一个程序可以对应多个进程

 (一)进程的状态

执行态   R 
停止态   T
等待态   S
僵尸态   Z
不可中断的等待  D 

(二)进程的相关指令 

ps -aux / ps -ajx (静态查看进程)
top(动态查看进程)
kill(结束进程)
jobs(查看后台进程)
ctrl+z(让前台进程进入后台停止)
./a.out &(让程序在后台执行)
bg(后台执行)
fg(前台执行)

(三)进程相关接口

创建进程

fork()函数 

#include
#include

pid_t fork(void); 

功能:开辟一个子进程,子进程会复制父进程的所有用户空间内容(包括缓存)
返回值:成功返回0代表子进程,大于
0(子进程的pid)是父进程,失败-1,并且子进程和父程互                    不影响

一般用法 

#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    else if(0 == pid)
    {
        while(1)
        {
            printf("child\n");
        }
    }
    else
    {
        while(1)
        {
            printf("father\n");
        }
    }
    return 0;
} 

下面代码改一个'\n'为什么结果不一样? 

不加'\n'打印4个hello,加了'\n'打印3个hello 

#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    printf("hello");  //printf("hello\n");
    fork();
    printf("hello\n");
    return 0;
} 

原因:printf是行缓存。因为不加'\n'时,将hello存入缓存区,当fork时,连缓存区里的fork也一起拷贝了,所以打印了4个;加'\n'时,在父进程fork之前的hello直接输出在终端,当执行fork时已经没有了上一行的hello,所以是3个

vfork()函数 (不怎么用,了解)

pid_t vfork(void); 

功能:开辟一个子进程,子进程直接使用父进程空间
返回值:成功返回 0 代表子进程,大于 0 (子进程的 pid )是父进程,失败 -1,并且子进程先运行

 获取当前进程id和它父亲id

getpid()和getppid()函数 

#include
#include

pid_t getpid(void);
pid_t getppid(void);

 功能:getpid()获取当前进程id,getppid()获取当前进程父亲id
返回值:getpid()返回当前进程id,getppid()返回当前进程父亲id

看下面代码,为什么不加sleep(1)后子进程的父id不等于父亲的id,加了sleep(1)后却又相等了?

#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    int a = 5;
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    else if(0 == pid)
    {
        a = 6;
        printf("child a=%d\n pid=%d ppid=%d", a, getpid(), getppid());
    }
    else
    {
        printf("father a=%d pid=%d ppid=%d cpid=%d\n", a, getpid(), getppid(),pid);
        //sleep(1);
    } 
    return 0;
} 

不加sleep结果 

嵌入式之网络编程_第10张图片  

加了sleep的结果 

 

原因:不确定时父进程先执行,还是子进程先执行,但是加了sleep时,父进程就比子进程晚一些,所以子进程去找父进程id,找得到就返回真正的父进程id。没加sleep时,当父进程结束时,子进程去找父进程id,没有找到,只有系统随机分配给子进程id,所以这个被称为孤儿进程。

总结:父进程提前退出后,子进程被操作系统领养的一种情况,被操作系统领养的进程就被称为孤儿进程

看下面代码,有几个进程运行? 

#include 
#include 
#include 
int main(int argc, char *argv[])
{ 
    fork()&&fork()||fork();
    printf("hello\n");
    return 0;
} 

答案:有5个进程在执行

嵌入式之网络编程_第11张图片

原因如下: 

嵌入式之网络编程_第12张图片

嵌入式之网络编程_第13张图片

退出进程

 进程退出分为3种:正常退出(return)、exit函数退出、_exit函数退出

1、return退出 

在main函数中使用return退出进程是我们常用的方法。

2、exit退出

使用exit函数退出进程也是我们常用的方法,exit函数可以在代码中的任何地方退出进程,并且exit函数在退出进程前会做一系列工作:

  1. 执行用户通过atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入。
  3. 调用_exit函数终止进程。

3、_exit退出

使用_exit函数退出进程的方法我们并不经常使用,_exit函数也可以在代码中的任何地方退出进程,但是_exit函数会直接终止进程,并不会在退出进程前会做任何收尾工作。 

return、exit和_exit之间的区别与联系 

return、exit和_exit之间的区别 

只有在main函数当中的return才能起到退出进程的作用,子函数当中return不能退出进程,而exit函数和_exit函数在代码中的任何地方使用都可以起到退出进程的作用。

使用exit函数退出进程前,exit函数会执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再终止进程,而_exit函数会直接终止进程,不会做任何收尾工作。

return、exit和_exit之间的联系 

 执行return num等同于执行exit(num),因为调用main函数运行结束后,会将main函数的返回值当做exit的参数来调用exit函数。
在这里插入图片描述
使用exit函数退出进程前,exit函数会先执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再调用_exit函数终止进程。

exit()函数 

#include

void exit(int status);

功能:结束进程并且刷新缓存
参数: status 退出状态 成功EXIT_SUCCESS(0) 失败EXIT_FAILURE( 非零值 )
_exit()函数 

#include

void _exit(int status);  

功能:结束进程不刷新缓存
参数: status退出状态 成功EXIT_SUCCESS(0) 失败EXIT_FAILURE( 非零值 )

资源回收(wait/waitpid)

wait()函数

#include
#include

pid_t wait(int *wstatus);

功能: 回收任意子进程的资源,阻塞
返回值。成功返回已结束子进程的 pid ,失败 -1
参数: wstatus :退出的状态
waitpid()函数 

#include
#include

pid_t waitpid(pid_t pid, int *status , int options)

功能:回收子进程资源 可以设置非阻塞
返回值:成功 pid ,失败 -1 0 非阻塞

嵌入式之网络编程_第14张图片

exec函数族 

 功能:用来操作文件,本质是替换原来进程的内容,运行调用的进程

所需头文件 #include
函数说明 执行文件
函数原型 int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
函数返回值 成功:函数不返回
失败:返回-1,失败原因记录在error中

常用的就前两个函数 

 l(list) : 表示参数采用列表;
v(vector) : 参数用数组;
p(path) : 有p自动搜索环境变量PATH;
e(env) : 表示自己维护环境变量;

一般操作

#include 
#include 

int main(int argc, char *argv[])
{ 
    char *env[] = {"tiejiaxiaobao=3.1415", NULL};
    char* buf[3] = {"ls", "-l", NULL};
    // execl("/bin/ls", "ls", "-l", NULL)

    // execlp("ls", "ls", "-l", NULL);

    //execvp("ls", argv+1);
    execle("./app", "./app", "qingtingduizhang", NULL, env);
    printf("hello\n");
    return 0;
} 
#include 

int main()
{
    execl("./a.out","./a.out",NULL);
    return 0;
}

(四)线程相关接口 

创建线程

#include
int pthread_create ( pthread_t * thread , const pthread_attr_t * attr , void * ( * start_routine ) ( void * ), void * arg );
编译要加-lpthread
功能:创建线程
返回值:成功0,失败非0
参数:
thread: 线程id
attr: 线程属性一般NULL 结合属性(默认) 分离属性(用专门的函数实现)

start_routine:新创建线程从此函数开始运行。无参数是arg设为NULL即可。
arg:start_rtn函数的参数。无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数  时应当使用结构体传入。

#include 
#include 
#include 
#include 

void* thread_func(void* arg)
{
    int b = 6;
    while(1)
    {
        printf("%d\n", *(int*)arg);
        //pthread_exit("hello");
        pthread_exit(&b);
    }
}

int main(int argc, char *argv[])
{ 
    int a = 5;
   
    pthread_t thread;

    if( 0 != pthread_create(&thread, NULL, thread_func, &a))
    {
        printf("create is default\n");
        return -1;
    }
    /*
    void* val;
    if(0 != pthread_join(thread, &val))
    {
        printf("join default\n");
        return -1;
    }
    printf("%s\n", (char*)val);
    */
    void* val;
    if(0 != pthread_join(thread, &val))
    {
        printf("join default\n");
        return -1;
    }
    printf("%d\n", *(int*)val);

    while(1)
    {
        a++;
        
    }

    return 0;
} 

退出线程

#include

void pthread_exit(void *retval)

功能:退出线程并且返回退出的 状态,但是退出线程所占用的资源不会随着线程的终止而得到释放
参数: retval: 表示线程退出状态,通常传NULL
 

嵌入式之网络编程_第15张图片

说明:pthread_exit里面的东西就是线程退出状态,我们可以在主函数里打印退出状态

根据上面示例,为什么下面代码打印的退出状态为什么不是6? 

嵌入式之网络编程_第16张图片

 说明:因为b=6是局部变量,函数调用完就结束了,所以后面打印的退出状态是随机分配的,而hello在常量区

资源回收

pthread_join()函数

#include

int pthread_join(pthread_t thread, void **retval);  

功能:回收线程资源 阻塞
参数:
thread :线程号
retval : 接收退出状态的地址,不想接收可以填NULL

返回值:0是成功,非0是失败

#include 
#include 
#include 
#include 

void* thread_func(void* arg)
{
    int b = 6;
    while(1)
    {
        printf("%d\n", *(int*)arg);
        //pthread_exit("hello");
        pthread_exit(&b);
    }
}

int main(int argc, char *argv[])
{ 
    int a = 5;
   
    pthread_t thread;

    if( 0 != pthread_create(&thread, NULL, thread_func, &a))
    {
        printf("create is default\n");
        return -1;
    }
    /*
    void* val;
    if(0 != pthread_join(thread, &val))
    {
        printf("join default\n");
        return -1;
    }
    printf("%s\n", (char*)val);
    */
    void* val;
    if(0 != pthread_join(thread, &val))
    {
        printf("join default\n");
        return -1;
    }
    printf("%d\n", *(int*)val);

    while(1)
    {
        a++;
        
    }

    return 0;
} 
pthread_detach()函数 

#include

int pthread_detach(pthread_t thread);  

功能:设置线程分离属性,该函数使用后就不用回收线程资源,但是开销比较大
参数: thread : 线程 id

(五)线程通信

1、互斥锁

注意:
1、要锁住线程需要同一把锁 ,所以一般锁的声明定义在全局

2、锁的初始化要在线程创建之前

概念:是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。 

pthread_mutex_init()函数
#include
pthread_mutex_init ( pthread_mutex_t * _mutex , const pthread_mutex_t * mutexatter );  
功能:初始化互斥锁, 注意互斥锁一般是全局的,初始化在创建线程之前
返回值:成功 0 ,失败非 0
参数:
mutex : 互斥锁
mutexatter : 属性一搬NUL L,使用默认的互斥锁属性
 
 pthread_mutex_lock()函数

#include

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:申请锁, 阻塞
返回值:成功 0 ,失败非 0
参数: mutex : 互斥锁
 pthread_mutex_unlock()函数
#include
int pthread_mutex_unlock ( pthread_mutex_t * mutex );
功能: 解锁, 阻塞
返回值:成功 0 ,失败非 0
参数: mutex : 互斥锁
pthread_mutex_destroy()函数

#include

pthread_mutex_destroy (pthread_mutex_t *mutex);

功能:销毁锁
返回值:成功 0 ,失败非 0
参数: mutex : 互斥锁

互斥锁示例 

#include 
#include 
#include 
int a = 0;

pthread_mutex_t mutex;

void* thread_func(void * arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        a *= 2 ;
        if( a % 2 != 0)
            printf("A\n");
        pthread_mutex_unlock(&mutex);
    }
}

int main(int argc, char *argv[])
{ 
    pthread_t thread;
    
    if(0 != pthread_mutex_init(&mutex, NULL))
    {
        printf("mutex init if default\n");
        exit(1);
    }

    if(0 != pthread_create(&thread, NULL, thread_func, NULL))
    {
        printf("create is default\n");
        exit(1);
    }
    
    while(1)
    {
        pthread_mutex_lock(&mutex);
        a = a*2 + 1;
        if(a % 2 == 0)
            printf("B\n");
        pthread_mutex_unlock(&mutex);
    }
    
    if( 0 != pthread_mutex_destroy(&mutex))
    {
        printf("destor is default\n");
        exit(1);
    }

    return 0;
} 

2、同步锁 

注意:同步锁只能确保谁先谁后,但不保证生产1个,消费1个,可能会生产多个 

概念:是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据 

 sem_init()函数

#include

int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:初始化信号量,信号量一般是全局,初始化在开辟线程之前
返回值:成功 0 ,失败 -1
参数:
sem : 信号量
pshared 共享
value: 指定信号量的初始值,它表示可用资源的数量。 value 必须是一个非负数,0表示没有资源,1表示有1个资源
sem_post ()函数

#include

int sem_post(sem_t *sem);

功能:对信号量做 p 操作,消费
返回值:成功 0 ,失败 -1
参数: sem : 信号量
 
注意:

sem_post()函数用于增加(释放)由信号量表示的资源计数。每次调用sem_post()函数都会将信号量的计数值加1,表示有一个资源可用。

sem_post()函数调用后,如果有其他线程在等待该信号量(通过sem_wait()函数),则其中一个等待线程会被唤醒,并继续执行。如果没有线程在等待该信号量,那么信号量的计数值就会增加,但不会有任何其他操作被阻塞。

需要注意的是,sem_post()函数并不能保证某个特定的线程会被唤醒,它只是通知等待线程中的一个可以继续执行。具体唤醒哪个线程是由操作系统调度决定的。

因此,sem_post()函数并不会阻塞当前线程,它只是简单地增加信号量的计数,可能导致等待线程被唤醒。

sem_wait()函数

#include

int sem_wait(sem_t *sem);

功能:对信号量做 v 操作,生产
返回值:成功 0 ,失败 -1
参数: sem : 信号量

同步锁示例 

嵌入式之网络编程_第17张图片

结果:终端输入一次,程序就打印一次 

下面代码为什么不是依次打印的?(打印了多次 )

嵌入式之网络编程_第18张图片

分析:因为同步锁只能确保对共享资源访问的先后顺序,只有生产了才能消费,但是生产了多少次不确定,sem_post函数是非阻塞且用于增加(释放)由信号量表示的资源计数。每次调用sem_post()函数都会将信号量的计数值加1,表示有一个资源可用,所以a++就生产了多次,信号量也有很多次,打印了许多相同的值。而上面程序之所以可以按顺序,是因为gets函数是阻塞函数,会等待输入

 正确代码

#include 
#include 
#include 
#include 

char buf[100];
int a;
sem_t sem_r, sem_w;

void* thread_func(void * arg)
{
    while(1)
    {
        if(-1 == sem_wait(&sem_r))
        {
            perror("wait");
            pthread_exit(NULL);
        }
        /*
        puts(buf);
        */
        printf("a=%d\n", a);
        if(0 != sem_post(&sem_w))
        {
            perror("post");
            exit(1);
        }
    }
}

int main(int argc, char *argv[])
{ 
    pthread_t thread;
    
    if(0 != sem_init(&sem_r, 0, 0))
    {
        perror("sem_init");
        exit(1);
    }
    if(0 != sem_init(&sem_w, 0, 1))
    {
        perror("sem_init");
        exit(1);
    }
    
    if(0 != pthread_create(&thread, NULL, thread_func, NULL))
    {
        printf("create is default\n");
        exit(1);
    }
    
    while(1)
    {
        //gets(buf);
        if(-1 == sem_wait(&sem_w))
        {
            perror("wait");
            pthread_exit(NULL);
        }
        
        a++;
        if(0 != sem_post(&sem_r))
        {
            perror("post");
            exit(1);
        }
    }
    

    return 0;
} 

分析:定义两个sem,将其中一个sem里面的资源设置为1,当两个线程都在wait时就会去争夺资源,而子线程资源为0,主线程资源为1,所以先执行主线程。 

 结果:有序的

嵌入式之网络编程_第19张图片

(六)进程通信

文件系统传统通信方式 (同一主机)

本质通过内核来进行通信,所以必须要用文件IO进行系统调用 

1、管道

1. 有唯一读端和写端,相当于队列
2. 当读端不存在,写端存在,再写入就会管道破裂(向进程发送 信号)
3 写端不存在读端存在,读端会把最后的消息读出来后返回 0
无名管道

概念:文件系统不可见,只支持亲缘进程通信(只能在一个文件中通信,不能 1.c 和 2.c 之间通信)

int pipe(int pipefd[2]);  

功能:创建一个无名管道,返回读端和写端
参数:pipefd:存储读端写段的数组
 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    int fds[2];

    if(-1 == pipe(fds)) //fork不会拷贝内核的内容
    {
        perror("pipe");
        exit(1);
    }

    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        exit(1);
    }
    else if(0 == pid)
    {
        close(fds[0]);//关闭读端
        while(1)
        {
            char buf[100];
            gets(buf);
            write(fds[1], buf, sizeof(buf));
        }
        exit(1);
    }
    else
    {
        close(fds[1]);//关闭写端
        while(1)
        {
            char buf[100];
            read(fds[0], buf, sizeof(buf));
            puts(buf);
        }
    }

    return 0;
} 
有名管道 

概念:文件系统可见, 支持非亲缘(可以由 1.c 和 2.c 之间通信,也可以亲缘之间通信)

创建方法一:使用命令 

 使用mkfifo创建有名管道

嵌入式之网络编程_第20张图片

创建方法二:使用函数 

 int mkfifo(const char *pathname, mode_t mode);

功能: 创建有名管道
参数: pathname :路径
            mode :权限 0666
 
#include 
#include 
#include 
#include 
#include 

#define FIFO_NAME "/tmp/myfifo"

int main() {
    int fd;
    char message[] = "Hello, named pipe!";
    char readbuf[80];

    // 创建命名管道
    mkfifo(FIFO_NAME, 0666);

    // 打开命名管道以供写入
    fd = open(FIFO_NAME, O_WRONLY);
    if (fd == -1) {
        perror("open");
        exit(1);
    }

    // 写入数据到命名管道
    write(fd, message, sizeof(message));
    printf("Sent message: %s\n", message);

    // 关闭命名管道
    close(fd);

    // 打开命名管道以供读取
    fd = open(FIFO_NAME, O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(1);
    }

    // 从命名管道中读取数据
    read(fd, readbuf, sizeof(readbuf));
    printf("Received message: %s\n", readbuf);

    // 关闭命名管道
    close(fd);

    // 删除命名管道
    unlink(FIFO_NAME);

    return 0;
}

2、信号

int kill ( pid_t pid , int sig );
功能: 给 pid 进程发送 sig 信号
返回值;成功 0 , 失败 -1
参数: pid :进程号
            sig:信号的编号
 

int raise(int sig); 

功能: 给自己发送 sig 信号
返回值:成功 0 , 失败 -1
参数: sig:信号的编号
 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    sleep(3);
 /*   
    if(-1 == kill(getpid(), SIGKILL))
        perror("kill");
*/
    if(-1 == raise(SIGKILL))
        perror("raise");
    while(1);
    return 0;
} 
typedef void ( * sighandler_t )( int );
sighandler_t signal ( int signum , sighandler_t handler ); 
功能: 规定对某个信号的处理方式
返回值:成功返回上一次的处理方式,失败 -1
参数: signum : 信号的编号
           handler: SIG_IGN :忽略该信号。
           SIG_DFL:采用系统默认方式处理信号。
           自定义的信号处理函数指针
 
#include 
#include 

 void sighandler_t(int sig)
{
    printf("hello\n");
}


int main(int argc, char *argv[])
{ 

/*
    if(SIG_DFL != signal(SIGINT, SIG_IGN))
        perror("signal");
*/
    if(SIG_DFL != signal(SIGINT, sighandler_t))
        perror("signal");
    while(1);

    return 0;
} 

unsigned int alarm(unsigned int seconds)  

功能 : 设置闹钟
返回值: -1 失败, >=0 成功, >0 前一次时钟的剩余时间
参数: seconds : 秒
 
#include 
#include 
#include 
void func(int sig)
{
    printf("hello\n");
    alarm(3);
}

int main(int argc, char *argv[])
{ 

    signal(SIGALRM, func);

    
    alarm(3);
    
    while(1);

    return 0;
} 

IPC对象通信 (同一主机)​

1、共享内存

速度非常快,由于共享内存是直接访问内存(通过内存映射方式),因此它的通信效率比管道高,适用于大量数据的传输。而管道则需要进行数据拷贝,放在缓存区,所以相对来说通信效率较低。

步骤:1.创建ipc对象/打开共享内存,2.申请共享内存,3.内存映射,4.取消映射,5.删除共享内存 

IPC对象的概念: 

在C语言中,IPC(Inter-Process Communication)对象是用于进程间通信的一种机制,它可以让多个进程之间共享数据或者协调操作。常见的IPC对象包括:

  1. 信号量(semaphore):用于进程间的同步和互斥,可以通过对信号量的操作来实现进程的等待和唤醒。

  2. 共享内存(shared memory):允许多个进程访问同一块内存区域,可以通过读写共享内存来进行数据交换。

  3. 消息队列(message queue):用于进程间的异步通信,可以通过消息队列来传递数据和控制信息。

  4. 管道(pipe):用于进程间的单向通信,可以在一个方向上传输数据。

  5. 套接字(socket):用于进程间或网络间的通信,可以建立客户端-服务器模型或点对点通信。

 key_t ftok(const char *pathname, int proj_id);

功能: 根据索引号和 id 生成一个唯一值
成功: 返回 key 失败 -1
参数: pathname :存在的文件
            proj_id: 一般常用 ‘A’ 这样的字符,使用字符的ASCII码(字符好记忆)
 

 int shmget(key_t key, int size, int shmflg);

功能: 创建共享内存
返回值:成功返回共享内存的 id 失败 -1
参数: key :打开哪个共享内存,给唯一的一个值,一般是 ftok 的返回值
            size:共享内存的大小
            shmflg: 创建或访问共享内存段时的控制标志
                           IPC_CREAT:没有文件则创建,有文件则打开
                           IPC_EXCL:文件已存在则提示错误信息
 

共享内存是不可见的,使用ipcs命令查看共享内存: 

嵌入式之网络编程_第21张图片

 void *shmat(int shmid, const void *shmaddr, int shmflg);

功能: 映射共享内存
返回值:成功返回映射的地址,失败 NULL
参数: shmid : 共享内存 id
           shmaddr: NULL 系统分配
           shmflg: 0默认读写 SHM_RDONLY
 

 int shmdt(const void *shmaddr);

功能: 取消映射
返回值:成功 0 , 失败 -1
参数:被映射的地址
 

 int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能:操作共享内存
返回值:成功 0 , 失败 -1
参数:
shmid : 共享内存 id
cmd:  IPC_STAT (获取对象属性)
            IPC_SET (设置对象属性 )
            IPC_RMID (删除对象 )
buf:删除可以填NULL(选IPC_STAT要定义结构体,不用初始化;选IPC_STAT要定义结构体,要初始化;选IPC_RMID删除,直接填NULL)
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SHMSIZE 10

int main(int argc, char *argv[])
{ 

    key_t key = ftok("1.c", 'a');
    if(-1 == key)
    {
        perror("key");
        exit(1);
    }
    int shmid = shmget(key, SHMSIZE, IPC_CREAT| 0666);
    if(-1 == shmid)
    {
        perror("shmget");
        exit(1);
    }
    void* addr = NULL;
    if(NULL == (addr = shmat(shmid, NULL, 0)))
    {
        perror("shmat");
        exit(1);
    }

    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        exit(1);
    }
    else if(0 == pid)
    {
    
        while(1)
        {
            puts((char*)addr);
        }
        exit(1);
    }
    else
    {
        int i = 3;
        while(i--)
        {
            fgets((char*)addr, SHMSIZE, stdin);
        }
        if(-1 == kill(pid, SIGKILL))
        {
            perror("kill");
            exit(1);
        }
        wait(NULL);
        
        if(-1 == shmdt(addr))
        {
            perror("shmdt");
            exit(1);
        }

        if(-1 == shmctl(shmid, IPC_RMID, NULL))
        {
            perror("shmctl");
            exit(1);
        }

        exit(0);
    }

    return 0;
} 

2、消息队列

步骤:创建消息队列,读写,删除

int msgget(key_t key, int flags) 

功能: 创建消息队列
返回值:成功消息队列的 id ,失败 -1
参数:
        key
key
        flags IPC_CREAT 0666
 

int msgsnd(int msgid, void* buf, int size, int flags);

功能:给消息队列发送消息
返回值:成功 0 , 失败 -1
参数:
        msgid
: 队列 id
        msgp:指向消息的指针。常用消息结构 msgbuf 如下:
        struct msgbuf{
                long mtype; //消息类型
                char mtext[N]}; // 消息正文
        }
        size:正文的大小

        flags:0阻塞 NOWAIT

int msgrcv(int msgid, void* buf, int size,long type, int flags);

功能:接收消息队列的消息
返回值:成功返回 接收到的消息数据的长度 , 失败-1
参数:
        msgid
: 队列 id
        msgp:指向消息的指针。常用消息结构 msgbuf 如下:
                struct msgbuf{
                        long mtype; //消息类型
                        char mtext[N]}; //消息正文
                }
        size:正文的大小
        type: 规定接收的消息类型
        flags: 0 阻塞 NOWAIT
 
int msgctl ( int msgid , int cmd , struct msqid_ds * buf );
功能:设置队列属性,获取队列属性,删除队列
返回值 : 成功 0 , 失败 -1
参数:
        msgid
: 队列 id
        cmd: SETVAL
                   GETVAL
                   IPC_RMID
        buf:接收或者设置属性的结构体
#include 
#include 
#include 
#include 
#include 
#include 

#define MSG_SIZE 1024
#define MSG_TYPE 1

struct message {
    long mtype;
    char mtext[MSG_SIZE];
};

int main() {
    int msgqid; // 消息队列标识符
    struct message msg;

    // 创建或获取消息队列
    key_t key = ftok(".", 'M');
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    msgqid = msgget(key, IPC_CREAT | 0666);
    if (msgqid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }

    // 发送消息
    strcpy(msg.mtext, "Hello, world!");
    msg.mtype = MSG_TYPE;
    if (msgsnd(msgqid, &msg, strlen(msg.mtext)+1, 0) == -1) {
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }
    printf("Sent message: %s\n", msg.mtext);

    // 接收消息
    if (msgrcv(msgqid, &msg, sizeof(msg.mtext), MSG_TYPE, 0) == -1) {
        perror("msgrcv");
        exit(EXIT_FAILURE);
    }
    printf("Received message: %s\n", msg.mtext);

    // 删除消息队列
    if (msgctl(msgqid, IPC_RMID, NULL) == -1) {
        perror("msgctl");
        exit(EXIT_FAILURE);
    }

    return 0;
}

3、信号灯集(一般和共享内存一起使用) 

信号灯(Semaphore)是一种同步机制,用于协调多个进程或线程对共享资源的访问。

信号灯集(Semaphore Set)则是多个信号灯的集合,它们通常被用来控制多个进程或线程对多个共享资源的访问,一般和共享内存一起使用。

步骤:创建信号灯集,初始化信号灯集,pv操作信号灯(P操作会将信号灯的值减1,V操作会将信号灯的值加1),删除信号灯集

int semget(key_t key, int semsize, int flags); 

功能: 创建信号灯集
返回值:成功信号灯集的 id ,失败 -1
参数: key key
           semnumber:信号灯的个数
           flags: IPC_CREAT 0666
 

 int semctl(int semid, int semnum, int cmd....../union semun );

功能:对信号灯集做操作 设置 获取 删除
返回值: 成功 0 , 失败 -1
参数: semid: 灯集的 id
           semnum:操作哪一个灯
           cmd: SETVAL 设置信号灯值
                     GETVAL 获取信号灯的属性
                     IPC_RMID 删除信号灯集
           semun:
                     union semun {
                             int val;                         /* Value for SETVAL */
                             struct semid_ds * buf ;   /* Buffer for IPC_STAT, IPC_SET */
                             unsigned short * array ; /* Array for GETALL, SETALL */
                             struct seminfo * __buf ; /* Buffer for IPC_INFO (Linux-specific) */
                      };
 

 int semop(int semid, struct sembuf *opbuf, int len);

功能: 对信号灯做操作
返回值:成功 0 , 失败 -1
参数:
semid :灯集的 id
opbuf:具体操作
        struct sembuf {
                short sem_num; // 要操作的信号灯的编号
                short sem_op; // 0 : 等待,直到信号灯的值变成0
                                        // 1 : 释放资源,V操作 生产
                                        // -1 : 分配资源,P操作 消费
                short sem_flg; // 0, IPC_NOWAIT, SEM_UNDO
        };
len: 结构体的个数
 
#include 
#include 
#include 
#include 
#include 
#include 

#define NUM_SEMAPHORES 2

// 定义信号灯集操作的结构体
struct sembuf sem_op;

int main() {
    int sem_id; // 信号灯集标识符
    key_t key; // 信号灯集键值

    // 创建或获取信号灯集
    key = ftok(".", 'S'); // 创建信号灯集键值
    sem_id = semget(key, NUM_SEMAPHORES, IPC_CREAT | 0666); // 创建或获取信号灯集
    if (sem_id == -1) {
        perror("semget");
        exit(1);
    }

    // 初始化信号灯集中的信号灯
    unsigned short init_values[NUM_SEMAPHORES] = {1, 0}; // 初始化为1和0
    if (semctl(sem_id, 0, SETALL, init_values) == -1) {
        perror("semctl");
        exit(1);
    }

    // 进程间对信号灯进行操作
    // P操作,将第一个信号灯的值减1
    sem_op.sem_num = 0;
    sem_op.sem_op = -1;
    sem_op.sem_flg = 0;
    if (semop(sem_id, &sem_op, 1) == -1) {
        perror("semop P");
        exit(1);
    }

    printf("Process 1 is in critical section\n");
    sleep(2); // 模拟临界区操作

    printf("Process 1 releases the resource\n");

    // V操作,将第二个信号灯的值加1
    sem_op.sem_num = 1;
    sem_op.sem_op = 1;
    sem_op.sem_flg = 0;
    if (semop(sem_id, &sem_op, 1) == -1) {
        perror("semop V");
        exit(1);
    }

    // 删除信号灯集
    if (semctl(sem_id, 0, IPC_RMID) == -1) {
        perror("semctl IPC_RMID");
        exit(1);
    }

    return 0;
}

生产者生产两次,消费一次代码示例:

#include 
#include 
#include 
#include 
#include 
#include 

#define NUM_SEMAPHORES 3

// 定义信号灯集操作的结构体
struct sembuf sem_op;

int main() {
    int sem_id; // 信号灯集标识符
    key_t key; // 信号灯集键值

    // 创建或获取信号灯集
    key = ftok(".", 'S'); // 创建信号灯集键值
    sem_id = semget(key, NUM_SEMAPHORES, IPC_CREAT | 0666); // 创建或获取信号灯集
    if (sem_id == -1) {
        perror("semget");
        exit(1);
    }

    // 初始化信号灯集中的信号灯
    unsigned short init_values[NUM_SEMAPHORES] = {2, 0, 1}; // 初始化为2、0和1
    if (semctl(sem_id, 0, SETALL, init_values) == -1) {
        perror("semctl");
        exit(1);
    }

    // 生产者
    if (fork() == 0) {
        for (int i = 0; i < 2; ++i) {
            // P操作,申请空闲资源
            sem_op.sem_num = 0;
            sem_op.sem_op = -1;
            sem_op.sem_flg = 0;
            if (semop(sem_id, &sem_op, 1) == -1) {
                perror("semop P");
                exit(1);
            }

            // 生产
            printf("Producer produces an item\n");

            // V操作,增加一个产品
            sem_op.sem_num = 1;
            sem_op.sem_op = 1;
            sem_op.sem_flg = 0;
            if (semop(sem_id, &sem_op, 1) == -1) {
                perror("semop V");
                exit(1);
            }
        }
    }
    // 消费者
    else {
        // P操作,等待有产品可消费
        sem_op.sem_num = 1;
        sem_op.sem_op = -1;
        sem_op.sem_flg = 0;
        if (semop(sem_id, &sem_op, 1) == -1) {
            perror("semop P");
            exit(1);
        }

        // 消费
        printf("Consumer consumes an item\n");

        // V操作,释放一个空闲资源
        sem_op.sem_num = 0;
        sem_op.sem_op = 1;
        sem_op.sem_flg = 0;
        if (semop(sem_id, &sem_op, 1) == -1) {
            perror("semop V");
            exit(1);
        }
    }

    // 删除信号灯集
    if (semctl(sem_id, 0, IPC_RMID) == -1) {
        perror("semctl IPC_RMID");
        exit(1);
    }

    return 0;
}
#include 
#include 
#include 
#include 
#include 
#include 

#define SIZE 100

#define SEMSIZE 2


 union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
};



enum sem_name{
    SEM_R ,
    SEM_W
};
void sem_op(const int semid,const short op,const short semnumber)
{
    
    //第一个参数是给⬇灯做操作,第二个是做什么操作(pv),第三个是阻塞还是非阻塞
    struct sembuf buf = {semnumber, op, 0};

    if(-1 == semop(semid, &buf, 1))
    {
        perror("semop");
        exit(1);
    }

    return ;
}
int main(int argc, char *argv[])
{ 
    //获取一个key值
    key_t key = ftok("1.c", 'a');
    if(-1 == key)
    {
        perror("ftok");
        exit(1);
    }
    //创建信号灯集
    int semid = semget(key, SEMSIZE, IPC_CREAT|0666);
    if(-1 == semid)
    {
        perror("semget");
        exit(1);
    }
    //创建给信号灯初始化的值
    union semun my_semun = {0};
    //把第0个信号灯设置初始值为0
    if(-1 == semctl(semid, SEM_R, SETVAL, my_semun))
    {
        perror("set val r");
        exit(1);
    }
    //把第1个信号灯设置初值为1
    my_semun.val = 1;
    if(-1 == semctl(semid, SEM_W, SETVAL, my_semun))
    {
        perror("set val w");
        exit(1);
    }
    //创建贡献内存
    int shmid = shmget(key, SIZE, IPC_CREAT|0666);
    if(-1 == shmid)
    {
        perror("shmget");
        exit(1);
    }
    //地址映射
    void* addr = shmat(shmid, NULL, 0);
    if(NULL == addr)
    {
        perror("shmat");
        exit(1);
    }

    while(1)
    {
        //消费写的资源,如果没有写资源就阻塞
        sem_op(semid, -1, SEM_W);
        fgets((char*)addr, SIZE, stdin);
        if(strstr((char*)addr, "quit"))
            break;
        //生产读的资源
        sem_op(semid, 1, SEM_R);
    }
    //取消映射
    if(-1 == shmdt(addr))
    {
        perror("shmdt");
        exit(1);
    }    
    //删除共享内存
    if(-1 == shmctl(shmid, IPC_RMID, NULL))
    {
        perror("shmctl");
        exit(1);
    }
    exit(0);
    return 0;
} 

socket套接字通信(网络编程)(不同主机)​​​​​​

服务器:

步骤:1.创建套接字 2.绑定套接字 3.监听套接字 4.等待连接

int socket ( int domain , int type , int protocol );
功能: 创建套接字
返回值:成功返回文件描述符, 失败 -1
参数:
        domain
:地址族协议 ipv4 AF_INET
        type: 套接字类型 tcp SOCK_STREAM
        protocol:子协议 默认 0
 
int bind ( int sockfd , const struct sockaddr * addr , socklen_t addrlen );
功能: 绑定套接字
返回值 成功 0 , 失败 -1
参数:
        sockfd
socket 的返回值
        addr:套接字信息
        addrlen:结构体长度
        struct sockaddr_in {
                sa_family_t sin_family; /* address family: AF_INET */
                in_port_t sin_port ; /* port in network byte order */
                struct in_addr sin_addr ; /* internet address */
         }; /* Internet address. */
 
        struct in_addr {
                uint32_t s_addr ; /* address in network byte order */
         };
eg
struct sockaddr_in addr ;
addr . sin_family = AF_INET ;
arr . sin_port = htons ( 6666 );
arr . sin_addr . s_addr = inet_addr ("0" );//可以用 INADDR_ANY宏(这个宏就等于0)
0 地址代表本机 ip 127.0 . 0.1 本机测试回环地址 本机具体 ip
 
int listen ( int sockfd , int backlog );
功能: 创建监听队列
返回值:成功 0 , 失败 -1
参数:
        sockfd:socket
的返回值
        backlog:队列的大小 一般 3 4
 
int accept ( int sockfd , struct sockaddr * addr , socklen_t * addrlen );
功能: 被动等待连接   三次握手
返回值:成功返回客户端的文件描述符(读写都使用这个),失败 -1
参数:
        sockfd:socket返回值
        addr:接收客户端的信息
        addrlen:接收客户端信息结构体的长度 socklen_t len = sizeof(addr);
#include 
#include 
#include 
#include 
#include  
#include 


int main(int argc, char *argv[])
{ 

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port   = htons(7777);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if(-1 == bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)))
    {
        perror("bind");
        return -1;
    }

    if(-1 == listen(sockfd, 4))
    {
        perror("listen");
        return -1;
    }
    struct sockaddr_in client_addr;
    socklen_t len = sizeof(client_addr);
    
    printf("wait for a client\n");
    
    int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &len);
    if(-1 == connfd)
    {
        perror("accept");
        return -1;
    }

    printf("connected a client ip=%s port=%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    while(1)
    {
        char buf[100];

        read(connfd, buf, sizeof(buf));

        puts(buf);
    }
    return 0;
} 

客户端:

步骤:1.创建套接字 2.绑定套接字(可以省略) 3.连接服务器

int connect ( int sockfd , const struct sockaddr * addr , socklen_t addrlen );
功能: 连接服务器
返回值:成功 0 ,失败 -1
参数:
        sockfd
socket 返回值
        addr:服务器的信息
        len:结构体长度
 
#include 
#include 
#include 
#include 
#include  
#include 
#include 


int main(int argc, char *argv[])
{ 

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port   = htons((uint16_t)atoi(argv[2]));
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    
    if(-1 == connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)))
    {
        perror("connect");
        return -1;
    }

    while(1)
    {
        char buf[100];
        fgets(buf,sizeof(buf),stdin);
        
        write(sockfd, buf, sizeof(buf));

    }
    return 0;
} 

网络基础了解 

嵌入式之网络编程_第22张图片嵌入式之网络编程_第23张图片

嵌入式之网络编程_第24张图片

三次握手

嵌入式之网络编程_第25张图片

第一次握手:刚开始时,服务器和客户端都是断开的,当进行三次握手时,客户端先向服务器发送连接请求,并将SYN置为1,客户端进入 SYN-SENT(同步已发送) 状态。

第二次握手:服务器收到 syn 包,要确认建立连接,所以服务器发送一个 SYN+ACK 包给客户端,服务器进入 SYN-RCVD(同步收到)状态

第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK,此包发送完毕,客户端和服务器进入 ESTABLISHED(已建立连接)状态

四次挥手 

嵌入式之网络编程_第26张图片

第一次挥手:刚开始通信双方是 ESTABLISHED(已建立连接)状态,当进行四次挥手时,客户端的应用进程先向服务器发出连接释放报文段 ,并将FIN置为1,客户端进入 FIN-WAIT(终止等待)状态,客户端此时处于半关闭状态(应用层无法接收数据但底层还可以接收数据)

第二次挥手:服务器接收到客户端的 FIN 报文后会做两件事情:1.对客服端的FIN做出确认  2.向客户端发出连接释放报文段。所以,服务器向客户端发送确认释放连接报文,将ACK置为1,服务器进入 CLOSE-WAIT(关闭等待)状态,客户端进入 FIN-WAIT(终止等待)状态

第三次挥手:服务器向客户端发送确认释放连接报文,并将FIN置为1,之后服务器进入LASK_ACK(最后确认)状态,等待客户端的确认。

第四次挥手:客户端收到来自服务器的连接释放(FIN)报文段后,会向服务器发送一个ACK应答报文段,之后客户端进入TIME_WAIT(时间等待)状态,此时的TCP还未释放掉,需要等待2MSL后,客户端才进入CLOSE状态。服务器收到ACK应答报文段后,服务器就进入CLOSE(关闭)状态,到此服务器的连接已经完成关闭。

并发服务器模型  

 进程并发

每次连接一个客户端就开一个进程  

嵌入式之网络编程_第27张图片

 一般框架: 
1.创建套接字
2.绑定套接字
3.监听套接字
signal(17,func)
while(1)
{
        4.被动等待连接
        5.开辟子进程,做服务
        //waitpid();效率低下,用17号信号
}

特点:
执行效率高,安全
开销大,开辟进程的个数有限
 

服务器:

#include 
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void sighandler(int sig)
{
    wait(NULL);
}

int main(int argc, char *argv[])
{ 

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(7777);
    addr.sin_addr.s_addr = inet_addr("0");

    if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("bind");
        return -1;
    }

    if(-1 == listen(sockfd, 4))
    {
        perror("listen");
        return -1;
    }

    if(-1 == signal(SIGCHLD, sighandler))
    {
        perror("signal");
        return -1;
    }

    while(1)
    {
        int connfd = accept(sockfd, NULL, NULL);
        if(-1 == connfd)
        {
            perror("accept");
            return -1;
        }

        printf("connected a client\n");
        pid_t pid = fork();
        if(-1 == pid)
        {
            perror("fork");
            return -1;
        }
        else if(0 == pid)
        {
            while(1)
            {
                char buf[100];
                if(0 == read(connfd, buf, sizeof(buf)))
                {
                    close(connfd);
                    exit(0);
                }
                printf("buf=%s\n", buf);

                if(strstr(buf,"time"))
                {
                    time_t t;
                    time(&t);

                    strcpy(buf, ctime(&t));

                    write(connfd, buf, sizeof(buf));
                }
                else
                {
                    puts(buf);
                }
            }
        }

    }
    close(sockfd);

    exit(0);
    return 0;
} 

客户端: 

#include 
#include 
#include 
 #include           /* See NOTES */
       #include 
 #include 
#include 

int main(int argc, char *argv[])
{ 
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(7777);
    addr.sin_addr.s_addr = inet_addr("0");

    if(-1 == connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("connect");
        return -1;
    }

    while(1)
    {
        char buf[100];
        fgets(buf, sizeof(buf),stdin);
        write(sockfd, buf, sizeof(buf));
        
        if(strstr(buf, "time"))
        {
            read(sockfd, buf, sizeof(buf));
            puts(buf);
        }
        else if(strstr(buf, "quit"))
        {
            break;
        }
    
    }
    close(sockfd);
    exit(0);
    return 0;
} 

线程并发 

一般框架:
1.创建套接字
2.绑定套接字
3.监听套接字
while(1)
{
4.被动等待连接
5.线程,做服务
//join
detach
}

特点:
开销小
不安全

服务器: 

#include 
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
void* thread_func(void* arg)
{
    int connfd = *(int*)arg;
    while(1)
    {
        char buf[100];
        if(0 == read(connfd, buf, sizeof(buf)))
        {
            close(connfd);
            pthread_exit(NULL);
        }
        printf("buf=%s\n", buf);

        if(strstr(buf,"time"))
        {
            time_t t;
            time(&t);

            strcpy(buf, ctime(&t));

            write(connfd, buf, sizeof(buf));
        }
        else
        {
            puts(buf);
        }
    }

}


int main(int argc, char *argv[])
{ 

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(7777);
    addr.sin_addr.s_addr = inet_addr("0");

    if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("bind");
        return -1;
    }

    if(-1 == listen(sockfd, 4))
    {
        perror("listen");
        return -1;
    }


    while(1)
    {
        int connfd = accept(sockfd, NULL, NULL);
        if(-1 == connfd)
        {
            perror("accept");
            return -1;
        }

        printf("connected a client\n");
        pthread_t thread;

        if(0 != pthread_create(&thread, NULL, thread_func, &connfd))
        {
            perror("create");        
        }
        if(0 != pthread_detach(thread))
        {
            perror("detach");
        }


    }
    close(sockfd);

    exit(0);
    return 0;
} 

客户端:

#include 
#include 
#include 
 #include           /* See NOTES */
       #include 
 #include 
#include 

int main(int argc, char *argv[])
{ 
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(7777);
    addr.sin_addr.s_addr = inet_addr("0");

    if(-1 == connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("connect");
        return -1;
    }

    while(1)
    {
        char buf[100];
        fgets(buf, sizeof(buf),stdin);
        write(sockfd, buf, sizeof(buf));
        
        if(strstr(buf, "time"))
        {
            read(sockfd, buf, sizeof(buf));
            puts(buf);
        }
        else if(strstr(buf, "quit"))
        {
            break;
        }
    
    }
    close(sockfd);
    exit(0);
    return 0;
} 

IO多路复用 

 (一)select

可以同时操作多个文件描述符,可以设置阻塞或非阻塞

一般框架:

1.创建套接字
2.绑定套接字
3.监听套接字
4.创建两个位图,一个用来保存关心的文件描述符,一个用来做内核拷贝的空间fd_set A ,B;
5.清0位图 void FD_ZERO(fd_set *set);
6.把监听套接字加入位图 void FD_SET(int fd, fd_set *set);
while(1)
{
7.把位图传入内核等待响应 select
8.遍历位图,判断对应位置是否为1 int FD_ISSET(int fd, fd_set *set);
9.判断准备好的这个文件描述符是哪种类型的文件描述符
        如果是监听套接字就accept(把新的文件描述符加入关心的位图)
        如果是connfd就做相应功能 (如果客户端退出需要删除相应的位图) void FD_CLR(int fd,         fd_set *set);  
}
int select ( int nfds , fd_set * readfds , fd_set * writefds , fd_set * exceptfds , struct timeval * timeout );
功能: 把位图拷贝给内核处理,等待返回结果
返回值:成功返回准备好的文件描述符个数,失败 -1 0 代表非阻塞或者是超时
参数:
        nfds
:最大文教描述符加 1
        readfds:关心的文件描述符的读事件
        writefds:写事件
        exceptfds:其他事件
        struct timeval {
                long tv_sec; /* seconds */
                long tv_usec ; /* microseconds */
        };
0 非阻塞 > 0 超时 NULL 阻塞
#include 
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{

    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(7777);
    addr.sin_addr.s_addr = inet_addr("0");
    //2.绑定套接字
    if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("bind");
        return -1;
    }

    //3.监听套接字
    if(-1 == listen(sockfd, 4))
    {
        perror("listen");
        return -1;
    }



    //4.创建两个位图,一个用来保存关心的文件描述符,一个用来做内核拷贝的空间  fd_set A ,B;
    fd_set readfds, tempfds;
    //5.清0位图    void FD_ZERO(fd_set *set);
    FD_ZERO(&readfds);
    //6.把监听套接字加入位图   void FD_SET(int fd, fd_set *set);
    FD_SET(sockfd, &readfds);
    int nfds = sockfd+1;
    while(1)

    {
        tempfds = readfds;
        //7.把位图传入内核等待响应  select
        if(-1 == select(nfds, &tempfds, NULL, NULL, NULL))
        {
            perror("select");
            return -1;
        }
        //8.遍历位图,判断对应位置是否为1  int  FD_ISSET(int fd, fd_set *set);
        int i;
        for(i = sockfd; i < nfds; i++)
        {
            if(FD_ISSET(i, &tempfds))
            {
                //9.判断准备好的这个文件描述符是哪种类型的文件描述符 
                if(i == sockfd)
                {
                    //如果是监听套接字就accept(把新的文件描述符加入关心的位图)
                    int connfd = accept(i, NULL, NULL);
                    if(-1 == connfd)
                    {
                        perror("accept");
                    }

                    FD_SET(connfd, &readfds);
                    (connfd + 1 > nfds) ? (nfds = connfd+1) : (nfds = nfds);
                }
                else
                {
                    //如果是connfd就做相应功能 (如果客户端退出需要删除相应的位图) void FD_CLR(int fd, fd_set *set);
                    char buf[100];
                    if(0 == read(i, buf, sizeof(buf)))
                    {
                        FD_CLR(i, &readfds);
                        close(i); 
                    }
                    printf("buf=%s\n", buf);

                    if(strstr(buf,"time"))
                    {
                        time_t t;
                        time(&t);

                        strcpy(buf, ctime(&t));

                        write(i, buf, sizeof(buf));
                    }
                    else
                    {
                        puts(buf);
                    }
                }
            }
        }
    }
}

(二)poll

两次拷贝,轮询,大小没有限定(结构体数组)  

一般框架:

1.创建套接字
2.绑定套接字
3.监听套接字
4.创建结构体数组 struct pollfd readfds[SIZE];
5.给fd赋值为-1 for() readfds[i].fd = -1;
6.把监听套接字加入结构体数组 int index = 0;readfds[index].fd = sockfd,
readfds[index] .events = POLLIN index++
while(1)
{
7.把结构体数组传入内核等待响应 poll
8.遍历结构体数组,判断是否准备就绪 for() if(readfds[i].revents & POLLIN)
9.判断准备好的这个文件描述符是哪种类型的文件描述符
如果是监听套接字就accept(把新的文件描述符加入结构体数组) 遍历找前面 的空位
如果是connfd就做相应功能 (如果客户端退出需要删除相应的位图) fd=-1;
}
int poll ( struct pollfd * fds , nfds_t nfds , int timeout );
功能: 把用户空间的结构体数组拷贝到内核空间,轮询检测你关心的文件描述符,如果有准备好的文件描述符就从内核空间拷贝回用户空间
返回值:成功返回值准备好的文件描述符的个数, 失败 -1 ,, 0 超时
参数:
        fds
:结构体数组
        nfds:结构体数组的长度
        timeout: >0 超时 =0 非阻塞 < 0 阻塞
#include 
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SIZE 2048

int main()
{

    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(7777);
    addr.sin_addr.s_addr = inet_addr("0");
    //2.绑定套接字
    if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("bind");
        return -1;
    }

    //3.监听套接字
    if(-1 == listen(sockfd, 4))
    {
        perror("listen");
        return -1;
    }



    //4.创建两个位图,一个用来保存关心的文件描述符,一个用来做内核拷贝的空间  fd_set A ,B;
    //fd_set readfds, tempfds;
    struct pollfd readfds[SIZE];
    //5.清0位图    void FD_ZERO(fd_set *set);
    //FD_ZERO(&readfds);
    int i;
    for(i = 0; i < SIZE; i++)
        readfds[i].fd = -1;
    //6.把监听套接字加入位图   void FD_SET(int fd, fd_set *set);
    //FD_SET(sockfd, &readfds);
    //int nfds = sockfd+1;
    int index = 0;
    readfds[index].fd = sockfd;
    readfds[index].events = POLLIN;
    index++;
    while(1)

    {
        //tempfds = readfds;
        //7.把位图传入内核等待响应  select
        /*
        if(-1 == select(nfds, &tempfds, NULL, NULL, NULL))
        {
            perror("select");
            return -1;
        }
        */
        if(-1 == poll(readfds, index, -1))
        {
            perror("poll");
            return -1;
        }

        //8.遍历位图,判断对应位置是否为1  int  FD_ISSET(int fd, fd_set *set);
    
        for(i = 0; i < index; i++)
        {
            //if(FD_ISSET(i, &tempfds))
            if(readfds[i].revents & POLLIN)
            {
                //9.判断准备好的这个文件描述符是哪种类型的文件描述符 
                if(readfds[i].fd == sockfd)
                {
                    //如果是监听套接字就accept(把新的文件描述符加入关心的位图)
                    int connfd = accept(readfds[i].fd, NULL, NULL);
                    if(-1 == connfd)
                    {
                        perror("accept");
                    }

                    //FD_SET(connfd, &readfds);
                    //(connfd + 1 > nfds) ? (nfds = connfd+1) : (nfds = nfds);
                    int j;
                    for(j = 0; j < index; j++)
                    {
                        if(readfds[j].fd == -1)
                        {
                            readfds[j].fd = connfd;
                            readfds[j].events = POLLIN;
                            break;
                        }
                    }
                    if(j == index)
                    {
                        readfds[index].fd = connfd;
                        readfds[index].events = POLLIN;
                        index++;
                    }
                }
                else
                {
                    //如果是connfd就做相应功能 (如果客户端退出需要删除相应的位图) void FD_CLR(int fd, fd_set *set);
                    char buf[100];
                    if(0 == read(readfds[i].fd, buf, sizeof(buf)))
                    {
                       // FD_CLR(i, &readfds);
                        close(readfds[i].fd); 
                        readfds[i].fd = -1;
                    }
                    printf("buf=%s\n", buf);

                    if(strstr(buf,"time"))
                    {
                        time_t t;
                        time(&t);

                        strcpy(buf, ctime(&t));

                        write(readfds[i].fd, buf, sizeof(buf));
                    }
                    else
                    {
                        puts(buf);
                    }
                }
            }
        }

    }
}

(三)epoll 

int epoll_create(int size);  

功能: 创建 epoll 对象
返回值:成功返回 epoll 的句柄, 失败 -1
参数: size :大于 0
int epoll_ctl ( int epfd , int op , int fd , struct epoll_event * event );
功能:添加,修改,删除
返回值:成功 0 , 失败 -1
参数:
        epfd epoll 的句柄
        op: EPOLL_CTL_ADD
                 EPOLL_CTL_MOD
                 EPOLL_CTL_DEL
        eevent:文件描述符属性的结构体地址
int epoll_wait ( int epfd , struct epoll_event * events , int maxsize, int timeout );
功能:等待内核返回准备好的文件描述符
返回值:成功返回准备好的文件描述符个数,失败 -1 0 超时
参数:
        epfd epoll 的句柄
        events:接收准备好的文件描述符信息的结构体数组首地址
        maxsize:数组的长度
        timeout: 0 非阻塞 >0 时间 -1 阻塞

UDP

无连接,不可靠,有失序,数据报,不稳定 

单播:一对一   

客户端:

1、创建套接字
2、发送信息

ssize_t sendto ( int sockfd , const void * buf , size_t len , int flags , const struct sockaddr * dest_addr , socklen_t addrlen );
功能: 向指定接收方发送一个信息
返回值:成功返回写入字节数,失败 -1
参数:
        sockfd
:套接字文件描述符
        buf:发送信息的地址
        len:发送信息的长度
        flags:发送方式默认 0
        addr:目标的 ip 和端口号
        len:结构体的长度
#include 
#include 
#include           /* See NOTES */
#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == sockfd)
    {
        perror("sokect");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(6666);
    addr.sin_addr.s_addr = INADDR_ANY;
    socklen_t len = sizeof(addr);
    
    char buf[100];
    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        
        sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, len);
        

        if(strstr(buf, "time"))
        {
            recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
            puts(buf);

        }
        
    }

    return 0;
} 

服务器:
1.创建套接字
2.绑定
3.接收

size_t recvfrom ( int sockfd , void * buf , size_t len , int flags , struct sockaddr * src_addr , socklen_t * addrlen );
功能:接收网络信息
返回值:成功返回接收字节数,失败 -1
参数:
        sockfd
:套接字文件描述符
        buf:接收信息的地址
        len:接收信息的长度
        flags:接收方式默认 0
        addr:目标的 ip 和端口号
        len:结构体的长度
#include 
#include 
#include           /* See NOTES */
#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == sockfd)
    {
        perror("sokect");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(6666);
    addr.sin_addr.s_addr = INADDR_ANY;
    socklen_t len = sizeof(addr);
    if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("bind");
        return -1;
    }
    char buf[100];
    while(1)
    {
        recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, &len);
        if(strstr(buf, "time"))
        {
            time_t T;
            time(&T);
            strcpy(buf, ctime(&T));

            sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, len);
        }
        
    }

    return 0;
} 

广播:一对多  

接收方:绑定广播地址,一般直接绑定0地址

服务器可以做发送方,也可以做接收方,但一般是发送方,此处是服务器作为接收方

#include 
#include 
#include           /* See NOTES */
#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == sockfd)
    {
        perror("sokect");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(6666);
    addr.sin_addr.s_addr = INADDR_ANY;
    socklen_t len = sizeof(addr);
    if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("bind");
        return -1;
    }
    char buf[100];
    while(1)
    {
        recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, &len);
        if(strstr(buf, "time"))
        {
            time_t T;
            time(&T);
            strcpy(buf, ctime(&T));

            sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, len);
        }
        puts(buf);
        
    }

    return 0;
} 
发送方:需要设置允许广播放送,发送的消息需要放送到广播地址

客户端可以做发送方,也可以做接收方,但一般是发送方,此处是客户端作为发送方

#include 
#include 
#include           /* See NOTES */
#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == sockfd)
    {
        perror("sokect");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(6666);
    addr.sin_addr.s_addr = inet_addr("192.168.2.255");
    socklen_t len = sizeof(addr);
    int val = 1;
    if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST,&val,sizeof(val)))
    {
        perror("opt");
        return -1;
    }

    char buf[100];
    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        
        sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, len);
        

        if(strstr(buf, "time"))
        {
            recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
            puts(buf);

        }
        
    }

    return 0;
} 

组播:多对多

ip地址的分类

网络号+主机号 == ip地址 主机号为255的叫做广播地址 为0的代表本机地址
A类 大型网络 前一个字节为网络号 最后三个字节为主机号 最高位为0
0.0.0.0 - 127.255.255.255 0.0.0.0本机 127.0.0.1本机回环 127.255.255.255.广播地址
B类 中型网络 前两个字节为网络号,后两个字节为主机号 10
128.0.0.0-191.255.255.255

C类 小型网络 前三个字节为网络号, 后一个字节为主机号110

192.0.0.0 - 223.255.255

D类 组播地址 1110   

224.0.0.0 - 239.255.255.255

E类 11110 保留地址 

nt setsockopt ( int sockfd , int level , int optname , const void * optval , socklen_t optlen );

设置属性表: level
SOL_SOCKET
参数optname
宏的作用 对应参数optval的类型
SO_BROADCAST
允许发送广播数据
int
SO_DEBUG
循序调试
int
SO_DONTROUTE
不查找路由
int
SO_ERROR
获的套接字错误
int
SO_KEEPALIVE
保持连接
int
SO_LINGER
延迟关闭连接
struct linger
SO_OOBINLINE
带外数据放入正常数据流
int
SO_RCVBUF
接收缓冲区大小
int
SO_SNDBUF
发送缓冲区大小
int
SO_RCVLOWAT
接收缓冲区下限
int
SO_SNDWAIT
发送缓冲区下限
int
SO_RCVTIMEO
接收超时
struct timeval
SO_SNDTIMEO
发送超时         
struct timeval
SO_REUSEADDR
允许重用本机地址和端口
int
SO_TYPE
获得套接字类型
int
SO_BSDCOMPAT
BSD 系统兼容
int
                ======================================================================
 
 IPPROTO_IP
参数 optname
宏的作用
对应参数 optval 的类型
IP_ADD_MEMBERSHIP
加入到组播组中
struct ip_mreq
IP_MULTICAST_IF
允许开启组播报文的接口
struct ip_mreq
======================================================================
typedef uint32_t in_addr_t ;
struct in_addr
{
        in_addr_t s_addr ;
};
// ip_mreq 是一个旧的数据结构,但目前仍然可用
struct ip_mreq
{
        /* IP multicast address of group. */
        struct in_addr imr_multiaddr ;
        // 设置加入多播组的的网卡 ip, 注意这里并不表示 socket 同该网卡绑定
        // 该 socket 仍然能够接收到不是该网卡的数据包,该设置仅仅表示该 ip
        // 对应的网卡能够接收对应多播组的数据包
        struct in_addr imr_interface ;
};
接收方:加入组播
#include 
#include 
#include           /* See NOTES */
#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == sockfd)
    {
        perror("sokect");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(6666);
    addr.sin_addr.s_addr = INADDR_ANY;
    socklen_t len = sizeof(addr);
    if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("bind");
        return -1;
    }
    struct ip_mreq muladdr;
    muladdr.imr_multiaddr.s_addr = inet_addr("230.1.1.1");
    muladdr.imr_interface.s_addr = INADDR_ANY;

    if(-1 == setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&muladdr, sizeof(muladdr)))
    {
        perror("opt");
        return -1;
    }

    char buf[100];
    while(1)
    {
        recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, &len);
        if(strstr(buf, "time"))
        {
            time_t T;
            time(&T);
            strcpy(buf, ctime(&T));

            sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, len);
        }
        puts(buf);
        
    }

    return 0;
} 
发送方:允许发送组播,向组播地址发送信息
#include 
#include 
#include           /* See NOTES */
#include 
#include 
#include 

int main(int argc, char *argv[])
{ 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == sockfd)
    {
        perror("sokect");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(6666);
    addr.sin_addr.s_addr = inet_addr("230.1.1.1");
    socklen_t len = sizeof(addr);
    
    struct ip_mreq muladdr;
    muladdr.imr_multiaddr.s_addr = inet_addr("230.1.1.1");
    muladdr.imr_interface.s_addr = INADDR_ANY;

    if(-1 == setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF,&muladdr, sizeof(muladdr)))
    {
        perror("opt");
        return -1;
    }
    
    char buf[100];
    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        
        sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, len);
        

        if(strstr(buf, "time"))
        {
            recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
            puts(buf);

        }
        
    }

    return 0;
} 

超时检查

setsockopt
io多路复用
信号 alarm

unix域套接字/本地套接字

作用:相同主机进行交互(传输文件描述符,传输软件证书)

接收方:

#include 
#include 
#include           /* See NOTES */
#include 
#include 



int main(int argc, char *argv[])
{ 

    int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_un addr;
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path,"1.txt");

    if(-1 == bind(sockfd,(struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("bind");
        return -1;
    }
    char buf[100];
    while(1)
    {
        recvfrom(sockfd, buf,sizeof(buf),0, NULL, NULL);
        puts(buf);
    }

    return 0;
} 

发送方: 

#include 
#include 
#include           /* See NOTES */
#include 
#include 



int main(int argc, char *argv[])
{ 

    int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_un addr;
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path,"1.txt");

    char buf[100];
    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        sendto(sockfd, buf,sizeof(buf),0, (struct sockaddr*)&addr, sizeof(addr));
    }

    return 0;
} 

 sqlite数据库

sql语句 

创建 create table ligintable(name char, passwd char);
insert into ligintable values("xiaoming", "123");
delete from ligintable where name="xiaoming";
update ligintable set name="xiaohua" where name="xiaoming";
select *from ligintable;     
     select *from ligintable where name="xiaoming" and passwd="123";
     select *from ligintable where name="xiaoming" or passwd="123";
删除表 drop table + tablename;

数据库操作函数 

int sqlite3_open ( const char * zFilename , //< 文件名字 sqlite3 ** ppDb //< 操作句柄 )
// 返回值:成功 0 , 失败非 0

const char *sqlite3_errmsg(sqlite3 *db)

//打印错误信息  

int sqlite3_exec (
sqlite3 * db , /* 数据库的句柄 */
const char * zSql , /* sql 语句 */
sqlite3_callback xCallback , /* 做查询的回调函数 */
void * pArg , /* 回调函数的参数 */
char ** pzErrMsg /* 错误信息的返回 */
);
/*
如果不是做查找 第 3 个和第四个参数可以是 NULL
成功返回 0 , 失败非 0
*/
int sqlite3_get_table ( sqlite3 * db , char* sql , char* ** dResult , int * nRow , int * nColnm , char ** errmsg );  
/*
db: 数据库
sql: 控制语句
**dResult: 二维数组
nRow: 查询结果条数
nColnm: 每条结果包含多少数据
errmsg: 报错信息
*/

sqlite3_close();  

//关闭数据库

#include 
#include 
#define SIZE 100
int main(int argc, char *argv[])
{ 
    
    sqlite3* db = NULL;

    if(0 != sqlite3_open("./my.db", &db))
    {
        printf("%s\n", sqlite3_errmsg(db));
        return -1;
    }
    char name[100];
    char passwd[100];
    char sql[SIZE+200];
   
    fgets(name,sizeof(name),stdin);
    fgets(passwd,sizeof(passwd),stdin);
    
    sprintf(sql,"insert into ligintable values('%s', '%s');", name, passwd);
    char*err = NULL;
    if(0 != sqlite3_exec(db, sql, NULL, NULL, &err))
    {
        printf("%s\n", err);
        return -1;
    }

    return 0;
} 
#include 
#include 
#define SIZE 100
int main(int argc, char *argv[])
{ 

    sqlite3* db = NULL;

    if(0 != sqlite3_open("./my.db", &db))
    {
        printf("%s\n", sqlite3_errmsg(db));
        return -1;
    }
    while(1)
    {
        char sql[200];
        printf("input sql\n");
        gets(sql);
        puts(sql);

        char*err = NULL;
        if(0 != sqlite3_exec(db, sql, NULL, NULL, &err))
        {
            printf("%s\n", err);
        }
    }
    return 0;
} 
#include 
#include 
#include 
#define SIZE 100
struct msg{
        char name[100];
        char passwd[100];
};

int cback(void* arg, int n, char** val, char** key)
{
    //printf("arg=%d  n=%d\n val=%s key=%s\n", *(int*)arg, n, val[1], key[1]);
/*
    int i;
    for(i = 0; i < n; i++)
    {
        printf("%s=%s ", key[i], val[i]);
    }
    printf("\n");
*/

    if((strcmp(val[0], ((struct msg*)arg)->name) == 0) && (strcmp(val[1], ((struct msg*)arg)->passwd) == 0))
        printf("login is ok\n");
    else
        printf("login is default\n");


    return 0;
}


int main(int argc, char *argv[])
{ 
    
    sqlite3* db = NULL;

    if(0 != sqlite3_open("./my.db", &db))
    {
        printf("%s\n", sqlite3_errmsg(db));
        return -1;
    }
    struct msg msgs;
    printf("input name and passwd\n");
    scanf("%s%s", msgs.name, msgs.passwd);

    char sql[SIZE+200];
    sprintf(sql,"select *from ligintable where name='%s' and passwd='%s';", msgs.name, msgs.passwd);
    puts(sql);
   /* 
    char*err = NULL;
    int i = 5;
    if(0 != sqlite3_exec(db, sql, cback, &msgs, &err))
    {
        printf("%s\n", err);
        return -1;
    }
    */
    char**result = NULL;
    int nrow, ncolnm;
    char* err = NULL;
    if(-1 ==  sqlite3_get_table(db,sql,&result,&nrow,&ncolnm,&err))
    {
        printf("%s\n", err);
    }
    /*
    int i, j;
    for(j = 0; j < nrow*ncolnm; j+=ncolnm)
    {
        for(i = 0; i < ncolnm; i++)
        {
            printf("%s=%s ", result[i], result[ncolnm+i+j]);
        }

    }
    */
    int i;
    for(i = 0; i < nrow*ncolnm+ncolnm; i++)
    {
        printf("%s\n", result[i]);
    }


    return 0;
} 

你可能感兴趣的:(网络,linux,c语言)