前言
本课程主要会讲Socket编程,利用C语言,在Linux系统下
虽然用了三年的mac系统,对linux多多少少也接触了不少,但还没系统的学习过。
这篇博客算是一篇给自己的扫盲,将以前接触过的东西串联起来
0. Linux简介
目前主流的电脑的操作系统分为 Windows,macOS,Linux
这三者都基于Unix
我们所熟悉的win和mac都是收费的,Linux诞生的目的就是打造免费的,不受任何商品化软件的版权制约的、全世界都能自由使用的Unix兼容产品。
三特点:免费,高效灵活,Unix全部功能
这使得计算机相关人士得以学习,探索计算机系统内核。
而且发展至今,Linux的应用软件越来越丰富,且代码都是免费开源的。可以说,Linux本身包含的应用程序以及移植到Linux上的应用程序包罗万象,任何一位用户都能从有关Linux的网站上找到适合自己特殊需要的应用程序及其源代码
1. 网络通信基础
这部分略过,上学期互联网协议基本都讲过
2. 网络编程
2.1 介绍
网络编程和其他类型的编程是有区别的,而我们将使用Linux系统,这也会带来一些区别。
我们都知道网络的OSI模型,而每层中的协议总和起来就叫做协议栈
借助百度的这段话来理解一下
网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。中间最主要的就是数据包的组装,数据包的过滤,数据包的捕获,数据包的分析,当然最后再做一些处理,代码、开发工具、数据库、服务器架设和网页设计这5部分你都要接触。
所以我们可以将网络编程理解为,使用代码,来实现网络通信(收发数据包)。我们需要了解,怎么遵守协议,怎么调用这里面涉及的API(比如socket),以及这里涉及的一些底层知识(如内存管理)...
参照下面两张图,并将他们关联到OSI模型中
1. 我们编写的都是application
Application,Presentation,Session
利用c语言编写程序,并且会将这些程序发布在以太网中的TCP/IP节点。
以太网就是局域网,我们的wifi这些啥的都是。
节点就是以太网中的设备,可以是计算机,服务器这些各种终端
2. 这些app会调用系统内核中的接口等,以实现程序的运转,而我们操作系统是Linux
Transport,Network,Data link
Linux会和其他操作系统有一些区别,比如有不一样的接口,后续有例子。这个接口可以理解为库函数,就跟c语言中的 printf() 就是 stdio.h 的库函数。
而Linux的内存管理也有一些特殊的地方,后续也有例子
3. 这些都建立在计算机的硬件上
Handware
这个就不用说了
然后的几张PPT是关于软件开发,就是软件工程的内容,略过
这里第一次涉及到Linux,稍微补充一下
这行命令,就是linux的命令行,我们经常在windows的CMD中进行命令行操作
比如编译java文件 javac filename.java
比如打开mysql数据库 mysql -u root -p password
等等
这句gcc的作用就是将test1.c这个文件编译成可执行文件 test.o
gcc test1.c -o test
可能还是有一点懵,可以先这么理解。linux这个操作系统,只需要你用这些命令行就可以进行操作,而不是用鼠标和键盘点来点去。
我们课程的要求只需要利用命令行来进行操作,也就是需要像下图这么个玩意,这是mac里面的terminal,类似于windows的CMD。他就跟linux的命令行界面一样。
当然linux也有图形界面,我安装了虚拟机就可以使用。但其实这些图形界面的本质还是命令行,只是有了实体的动画
我们利用命令行来随便试一试:
我这里随便用了三个:
ls:显示当前路径下的所有文件
date:日期
cal:日历
更多请参照:【Linux】linux常用基本命令_小怪孩的成长之路-CSDN博客_linux常用命令
所以这样就可以看到,我们所有的这些操作都可以用一行简简单单的命令来完成
包括下载软件(还记得开头提过linux的应用软件十分丰富且免费),不再需要像windows那样下载个软件点半天。
借助以上,对linux的操作就有了大致概念
3. 基本概念
3.1 进程 process
首先,程序是一个包含执行命令的静态文件,进程是正在执行的程序实例
操作系统的管理单位是进程
我们的操作系统会自动的为每一个进程分配一个pid,打开任务管理器就可以看到:
而进程是怎么产生的?
当我们调度一次程序,就会产生一个进程,如果重复调度,那么就会产生多个进程
而我们在一个进程里,同样可以调度其他程序,比如我打开qq,在qq里调用qq邮箱
所以一个程序可以拥有多个进程,而一个进程也可以调用多个程序
进程和程序并不能划等号
每一个进程是操作系统分配内存的最小单元,也就是说,对于每一个进程,都会在电脑的内存中占据下图的部分
这是一个栈(数据结构中讲过,先进后出,后进先出)
我们以这样一个程序为例
#include <stdio.h>
int main(){
int i=0;
printText(i);
char *p = (char*)malloc(100);
/* 给p赋值并输出 */
free (p);
return 0;
}
void printText(int num){
printf("%d",num);
}
我们可以看到
Text最先进来,这就是源代码,也就是上面的这个.c文件,进入栈的底部
Data,存储全局变量(main函数中声明的变量),也就是这个例子中的i
Heap,这是一块动态分配的空间。看我代码中,给p这个字符数组(c语言的数据就是指针)申请了一块100字节的空间,在free他之间,我们可以对其进行很多操作,比如赋值并输出。这么做的目的,就是为了代码的高效。因为内存空间是有限的,当使用一些较大的数据结构时,我们采用动态分配内存会得到更高的性能,而他将存放在堆中
Stack,这里存放局部变量,也就是printText函数中的num
pid刚刚提过了,ppid就是这个进程的父进程的pid。
我们刚刚提过一个程序可以调度别的程序,qq里面使用qq邮箱,这样就很好理解了
注意图中的几个特殊pid
在Linux中,可以用命令 ps -ef 来查看当前所有的进程和他们的pid,ppid
3.2 系统调用 system call
3.2.1 fork( )
有了以上的基础,我们该思考如何在程序中得到这些信息呢?
那么就需要调用系统提供的API,也就是这些函数,ppt描述的都很清楚,我们直接看例子
因为fork()创建了一个子进程,两个进程都调度这个程序,所以输出了两行
我们看一下fork函数的返回值,如果是子进程,就返回0;如果是父进程,就返回子进程的pid
通过这个点,我们就可以加以判断,来让两个进程实现不同的效果
3.2.2 exec( )
先看下面这段话就很容易理解了
首先exec是一个函数族,后面例子中用到的execv是这个族里面的一个
借助以上来理解下面的例子,也就是让我们在子进程中调用了一行linux命令:/bin/ls
也就是将bin目录下所有的文件打印出来
注意一句话:在执行完后,原调用进程的内容除了进程号外,其它全部被新程序的内容替换了
在fork==0的代码段中,执行完exevc,这个进程就变成了单纯的 ls 命令,所以那句printf不会执行
3.3 文件描述符 file descriptor
首先,文件描述符是一个整型,也就是说,当我们创建文件或者打开文件,他会返回一个整数。然后我们利用这个整数来调用相应文件并进行操作。
这张ppt可以看到,我们通过调用最下面哪些系统中的函数,如果成功的话,系统会自动分配一个整数并返回。
在以前的C语言课程中,我们一直用的都是 stdio.h 这个库,也就是standard IO。在Unix系统(最开始提过,win,mac,linux都基于unix)下,还有一个 unistd.h ,这个库就是专门与文件描述符工作的。库中的函数就是上面那张ppt中罗列的
unistd.d 总览
下面共列了6个库函数
最左边是他们的返回值,基本都是文件描述符或者-1(调用方法失败,就是调用文件失败)。对于读写,成功时返回的是读写的byte数。
lseek后面会单独讲
dup就是用来复制文件描述符的,用的好像不多
最右边可以看到open里mode 的几个参数,基本都是缩写,比如O_RDONLY (open-read only)就不赘述了。
lseek( )
这个函数用于移动文件的读写位置,
每一个已打开的文件都有一个读写位置, 当打开文件时通常其读写位置是指向文件开头, 若是以附加的方式打开文件(如O_APPEND), 则读写位置会指向文件尾. 当read()或write()时, 读写位置会随之增加,lseek()便是用来控制该文件的读写位置. 参数fildes 为已打开的文件描述词, 参数offset 为根据参数whence来移动读写位置的位移数.
参数 whence 为下列其中一种:
- SEEK_SET 参数offset 即为新的读写位置.
- SEEK_CUR 以目前的读写位置往后增加offset 个位移量.
- SEEK_END 将读写位置指向文件尾后再增加offset 个位移量.
- 当whence 值为SEEK_CUR 或 SEEK_END 时, 参数offet 允许负值的出现
直接看例子就好
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
char buf1[]="abcdefghij";
char buf2[]="ABCDEFGHIJ";
#define FILE_MODE 0644
int main(void){
int fd;
if ((fd=creat("file.hole",FILE_MODE))<0) {
printf("creat error\n");
exit(1);
}
if (write(fd,buf1,10)!=10){
printf("buf1 write error\n");
exit(1);
}
/*offset now = 10 */
if (lseek(fd,40,SEEK_SET)==-1){
printf("lseek error\n");
exit(1);
}
/*offset now = 40 */
if (write(fd,buf2,10)!=10){
printf("buf2 write error\n");
exit(1);
}
/*offset now = 50 */
exit(0);
}
代码比较直观,先写了10字节,然后调整偏移量为40,然后又写了10字节,总共就是50字节
我们看到在设定40偏移量的时候,这个40是大于文件当前的长度(10字节),这样就会扩展这个文件的长度,所以你可以看到下面中有一堆 \0 这堆东西就像一个空洞一样
我们再解释一下右上角的FILE_MODE 0644
每一位是都是2进制转化而来,0(000) 6(110) 4(100) 4(100)
四位数字,每位都代表一种用户
三位的二进制数字每一位都对应一种操作,1就是允许,0就是不允许
从左到右第一位是read,第二位是write,第三位是执行(好像是,我没查到具体的)
那么0644的意思就是允许用户读、写;组成员只读;其他用户只读
理解一下应该就行,不需要特别记,一般都是0644
然后这里又出现两行Linux命令
ls -l file.hole : 列举出文件的信息,包括权限,创建用户,时间等等
od -c file.hole :显示文件内容
这页的例子,就是从键盘输入并读写到文件中,直到你输入quit,就会停止
3.4 信号 signal
这没啥说的,看看ppt就行
理解信号就是告诉计算机要干啥,比如终结进程什么的,这些操作的实现是通过调用 signal.h 中的函数
4. 网络回顾
开始之前大概说一下这部分在讲啥,就是怎么利用C语言来实现CS模型,IP地址等等
4.1 CS模型
没啥好说的,互联网协议都讲过,读读ppt就好
记住一点 :一定是client先发起交互请求,关闭连接请求cs双方都可以
然后IP地址和域名的关系:
域名是我们比较容易看的,比如www.baidu.com
而百度的IP地址就是 192.168.102.1
他们之间的互相转化依靠DNS,网络间的通信都需要IP地址
4.2 IP地址的结构体
4.2.1 IP address
首先我们回忆一下啥是结构体,看一个例子,总之就是把这些封装成一个东西,
struct student{
char name[10];
int age;
char studentNumber[20];
};
还有typedef的用法,这样BUPTstudent就可以代指student,相当于别名
typedef struct student{
char name[10];
int age;
char studentNumber[20];
}BUPTstudent;
那么我们看这页ppt,uint32_t 就是无符号整型的别名,然后in_addr_t又是uint32_t的别名
结构体里存放了一个in_addr_t(说白了就是放了一个无符号的整型,意义是ip地址)
然后我们知道ip地址是32位的(也就是4个十进制数字),如果你是从低位存到高位就是 little-endian,反之是big-endian,ppt有一个例子很容易懂
而这两种存放方式是相反的,而且在主机和网络上使用的也是不同的存放方式,那么就必须互相转化,就可以使用这页图中的函数
先看赋给s的值 :0x0102
c语言中,开头是0x,说明这是一个十六进制的数字,真实的数字位是后面的0102,0x只是一个前缀没有实际意义
那么这个0x0102,一个数字占4bit,所以四个数字(0x是前缀不算)占16bit,也就是2byte
我们再来看这是一个union,它跟结构体的区别就是他的数据必须存放在同一块内存中
所以你这快内存里要么是按照big endian存放,要么是按照 little endian存放
你可以看这个例子,修改了i的值后,l的值也变成了6。如果是结构体的话,l的值依然会是5
总之union中,他的一个地址上的值只有一个,那么我们就可以根据地址上值的顺序来判断是big还是little
有点绕,理解一下还是很好懂
#include<stdio.h>
union var{
long int l;
int i;
};
int main(){
union var v;
v.l = 5;
printf("v.l is %d\n",v.i);
v.i = 6;
printf("now v.l is %ld! the address is %p\n",v.l,&v.l);
printf("now v.i is %d! the address is %p\n",v.i,&v.i);
return 0;
}
结果:
v.l is 5
now v.l is 6! the address is 0xbfad1e2c
now v.i is 6! the address is 0xbfad1e2c
4.2.2 Domain Name
这个结构体稍微有点复杂,有了指针和二维指针
但可以这么理解,我们都知道数组就是一个指针
那么指针就相当于一个数组,二维指针(指针的指针)就是二维数组(元素是数组的数组)
所以我们看这张图
h_name 这个指针指向了一个字符串数组,存放了主机名
h_aliases 这个二维指针指向一个二维数组,因为可以有多个别名,把这些别名用一个数组存起来,而每一个别名又需要一个数组来存
...后面就不赘述了
我们可以通过Linux命令 nslookup 来查看域名结构体中的信息
后面又讲了两个dns的方法,以及cs的一些补充,读一下就好
最后讲了下三个命令,跟着ppt看看实例然后自己试一下就好
ifconfig
Ping
netstat
下一章应该就要开始安装linux,后续接着更新
by @绕圈