栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Lesson 2 网络编程的基本概念

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Lesson 2 网络编程的基本概念

根据教师ppt,介绍网络编程(Network Programming)的基本概念,展示一些简单的程序和一些基本的概念(进程,系统调用,文件标识符,信号)。

目录

一.Network Programming

二.使用Linux进行C语言程序开发的基本知识

三.网络编程相关的基本概念

3.1socket套接字

3.2process进程

3.2.1 进程,母进程,PID

3.2.2 相关系统调用

3.2.3 代码示例,重点解释fork和execve这两个函数

3.3file descriptor文件描述符

3.3.1文件描述符和文件读写方式

3.3.2 文件访问权限

3.3.3 文件权限常量,文件打开方式常量,光标位置常量

3.3.4 与文件读写相关的系统调用

3.3.5 代码示例

3.4system call系统调用​

3.5signal信号


一.Network Programming

教师给出的定义:

可见:1.首先,网络编程的目的是为了写出能够利用network进行远程通信的程序(program)(程序一旦运行就可以被认为是进程,而进程是通信的对象或说端点)。

2.其次,这里由于是在讲编程,所以用了术语program。准确的说法应该是process,因为网络通信的端点是进程。进程的概念在本篇文章的下半部分会讲。

教师进一步限定了我们这门课程所说的NP的范围:我们基于Linux OS、C语言、Ethernet和TCP/IP协议进行网络编程,并且为了隐藏运输层及其以下各层的实现细节,我们使用socket(套接字)编程,这样我们就可以如上图所示利用各种系统调用(system call)来使用底层的协议(TCP/UDP/IP等等)。可见我们专注于应用层协议的实现(写出的程序是应用层的)。

二.使用Linux进行C语言程序开发的基本知识

编译源文件,并指定产生的可执行文件的名字:

gcc helloworld.c -o helloworld.out

man查询手册
man 1 +命令 这里的1表示为查询的是Linux命令

man 2 xxx 这里的2表示为查询的是linux api

man 3 xxx 这里的3表示为查询的是C库函数

三.网络编程相关的基本概念

3.1socket套接字

具体的解释在我的这篇文章里面:(13条消息) C/Linux网络编程III--套接字与两个重要的结构体_竹某的博客-CSDN博客

目前就理解为“进程模型”,等于“主机IP地址+进程端口号”,标识了通信的一个端点。

一个socket pair标识了一个通信过程。

3.2process进程

3.2.1 进程,母进程,PID

教师给出的定义:

 理解process的定义,就需要分清楚process和program。两者的关系就像Java中object和class的关系,前者是系统分配内容的基本单位,而后者只是静态的代码而已。所以,一个程序自然可以对应很多进程;而一个进程可以调用很多程序,使之变为进程(其子进程,对应一个母进程)。

每一个进程都有其唯一的标识符PID(process identification),类型为整型。范围为[0,32767]。而且每一个进程都有其母进程(parent process)。不过这里有一个问题:我们在不断求进程的母进程时一定会有一个终点。这个终点是唯一的吗?这个终点的母进程是谁?先放着。

3.2.2 相关系统调用

与进程有关的系统调用:

System call

函数原型解释
创建一个子进程pid_t fork();

pid_t就是int类型,无参数;创建成功,在父进程中返回子进程的pid,在子进程中返回0;失败,返回负数。(一次调用,返回两次)

返回当前进程的pidpid_t getpid();返回当前进程的pid,类型为int(typedef pid_t int)。
返回当前进程的父进程的pidpid_t getppid();
结束进程并释放所有相关资源int exit(int);参数是什么,返回值就是什么。exit(0)表明该进程正常退出;exit(-1)或exit(1)表明程序非正常退出。
新程序执行函数。之所以叫新程序的执行,原因是这部分内容一般发生在fork()之后,在子进程中通过系统调用execve()可以将新程序加载到子进程的内存空间。这个操作会丢弃原来的子进程execve()之后的部分,而子进程的栈、数据会被新进程的相应部分所替换。即除了进程ID之外,这个进程已经与原来的进程没有关系了。
 
 int execve(const char *filename, char *const argv[ ], char *const envp[ ]); 函数执行成功时没有返回值,执行失败时的返回值为-1。argv和envp是传给新程序的参数,均以NULL为最后一个元素。注意后两个参数的数据类型都是字符串数组,即char**。filename是可执行文件的路径。

3.2.3 代码示例,重点解释fork和execve这两个函数
#include 
#include 
#include 
#include 
//here is the program helping me learn about system calls related to PROCESS--FORK() GETPID() GETPPID()
int main(void){
        int count = 0;//variable that will be duplicated to its child process
        pid_t t = -1;//variable that will be duplicated to its child process

        //the branch starts: child process parrallels with its parent process 
        t = fork();
        printf("fork returned: %dn",t);
        if(t < -1){
                puts("fail to create a child process");
        }
        else if(t == 0){
                puts("----------------------------");
                puts("it's now in Child Process");
                printf("t = %dn",t);//to verify fork() returns 0 in the child process
                printf("Child Process PID: %dn",getpid());
                printf("Its Parent Process PID: %dn",getppid());
                count++;
                printf("COUNT = %dn",count);//tp verify the child process get a copy of data from its parent
                puts("----------------------------");
        }
        else{
                puts("----------------------------");
                puts("it's now in Parent Process");
                printf("t = %dn",t);//to verify fork() returns child process's pid in parent prcoess
                printf("Parent Process: %dn",getpid());
                printf("Its Children Process: %dn",t);
                printf("Its Parent Process PID: %dn",getppid());
                printf("COUNT = %dn",count);
                puts("----------------------------");
        }
        return 0;
}

对应的结果是:

问题在于子进程的父进程居然是1,这个让人不理解。先留着。不过看一下教师的ppt吧,那个例子可以。 

 这说明:

1.父进程执行到fork()函数时,产生进程的并行分支(该父进程与自己创建的子进程并行,父进程的变量被复制给子进程。注意,分支是在fork()处就开始了:fork以及fork之后的代码会被父子进程分别执行)。

2.fork()在父子进程中分别有返回值(在父进程中返回子进程pid,在子进程中返回0)。

3.想让父子进程分别执行不同的代码,可以利用fork不同的返回值进行if判断,如上所示。

#include 
#include 
#include 
#include 
int main(void){
        char* argv[] = {"family","dad","mun","son",NULL};
        char* envp[] = {0,NULL};
        int t = fork();
        if(t == 0){
                puts("It's now in Child Process!");
                execve("./processImage.out",argv,envp);
                puts("this line will never be performed!");
        }
        puts("this line performed in parent process but not in child process!");
        return 0;

}
#include 
int main(int argc,char* argv[],char* envp[]){
        int i;
        puts("It's now in the process image running a new program!");
        for(i = 0;i < argc;++i){
                printf("argv[%d]=%sn",i,argv[i]);
        }

        return 0;
}

结果为:

1.很明显,execv.out在子进程中调用了processImage.out,并将含有family等字符串的数组交给了它。 这个打印功能是由processImage.out提供的。

2.puts("this line will never be performed!");是子进程中execve后的代码,这个被舍弃掉了,不会执行。子进程中同样没有执行puts("this line performed in parent process but not in child process!");。这个也被舍弃了,只在父进程中被执行了一次。
                                 

3.3file descriptor文件描述符

3.3.1文件描述符和文件读写方式

在Linux OS中,一切皆文件。不光是输入输出设备(标准输入设备是键盘,标准输出设备是显示屏)被看作是文件,可以进行读写操作;就连一个套接字也可以被看作是一个文件。

在一个点对点的通信过程中,通信的两个端点各自建立一个socket,作为参与通信的进程的输入/输出缓冲区。如果向要A点向B点发送字符串"I'm here!",就需要向系统为A进程分配的输入缓冲区写入该字符串;通信过程如果没有丢包或是延迟的话,B进程的输出缓冲区就会得到这一字符串。

正如栈中的内存空间需要变量名标识,文件也需要文件描述符标识。这样我们就可以在读写操作中指定文件了。不过这里需要对文件读写方式进行说明。事实上,Linux系统和C语言库分别为我们提供了一套文件读写函数:

 Linux OS为我们提供的文件读写方式是使用file descriptor来标识文件的,而C语言的读写方式是基于文件流来标识文件的。本门课程使用Linux OS提供的文件读写方式。

3.3.2 文件访问权限

 在存放文件的目录下键入Linux命令ls -l,能够显示当下目录中所有文件/子目录的详细信息。在这里我们只关心前十个字符,比如“-rw-rw-r--”。这个十个字符可以被拆为两部分:第一个字符标识类型,d代表文件夹,-代表普通文件;后九个字符标识了文件的读写权限。

后九个字符可以三三三分组,分别标识u、g、o三类用户的访问权限。u表示创建该文件的用户对文件的权限,g表示创建该文件的用户所在的组的用户对该文件的权限,o表示其他人的权限。r表示读的权限,w表示写的权限,-表示执行的权限。执行的权限只对可执行文件有意义。

比如client.out这一可执行文件的权限是:user访问权限为可读可写可执行,group的执行权限为可读可写可执行,而其他人可读可写不可执行。

3.3.3 文件权限常量,文件打开方式常量,光标位置常量

在相关的系统调用前先说明这些概念,这样有助于我们更好地理解后续的系统调用。

文件权限常量

我们创建文件时,需要指定文件的权限。相应的常量是:

S_IRUSR
用户可以读    =OX(00400)
S_IWUSR
用户可以写    =OX(00200)
S_IXUSR
用户可以执行    =OX(00100)
S_IRWXU
用户可以读、写、执行    =OX(00700)
S_IRGRP
组可以读    =OX(00040)
S_IWGRP
组可以写    =OX(00020)
S_IXGRP
组可以执行    =OX(00010)
S_IRWXG
组可以读写执行    =OX(00070)
S_IROTH
其他人可以读    =OX(00004)
S_IWOTH
其他人可以写    =OX(00002)
S_IXOTH
其他人可以执行    =OX(00001)
S_IRWXO    
其他人可以读、写、执行    =OX(00007)
S_ISUID            //这个不用管    =OX(10000)
设置用户执行ID
S_ISGID            //这个不用管    =OX(01000)
设置组的执行ID

后面用五位八进制表示了该常量对应的数值(这些常量全都是int类型的)。这五位八进制数OX(abcde)的各位分别对应:a对应是否设置用户的执行ID(本课程不要求了解,设成0就好了),b对应是否设置组的执行ID(本课程不要求了解,设成0就好了),cde分别代表ugo(user,group和others)的权限。r权限被视为4(八进制的4和十进制一样),w权限被视为2,执行权限被视为1。这样,c=7表明用户同时具有rwx权限,c=6表明用户只有rw权限,c=5表明用户只有rx权限,c=4表明用户只有r权限,c=3表明用户只有wx权限,c=2表明用户只有w权限,c=1表明用户只有x权限。组和其他人也是一样。        另外由于对于二进制而言,或运算可以认为是加法运算,而且r+w+x最高为7,不会产生进位,所以10701等价于S_ISUID | S_IRWXU | S_IXOTH。

文件打开方式常量

O_RDONLY
以只读的方式打开文件
O_WRONLY
以只写的方式打开文件
O_RDWR
以读写的方式打开文件
O_APPEND
以追加的方式打开文件
O_CREAT
创建一个文件
O_TRUNC
如果文件已经存在,则删除文件的内容

我们在打开文件时,需要指定文件打开方式,以确定我们本次操作的权限——这是受限于我们对该文件拥有的权限的。比如你如果只对文件拥有rx权限,就不能在打开该文件时使用O_WRONLY。

这些也都是int常量。其中O_RDONLY、O_WEONLY和O_RDWR不可以相或。我们这门课只用的到只读、只写和读写以及创建。

光标位置常量

我们在往文件中写入内容时需要指定光标的位置。我们往文件中添加的内容是直接在光标前的,不信的话你现在打以下字,你就会发现你打出的字符出现在光标的前面。下面的这些常量全都是long型的。光标相对于文件开头的位置由光标偏移量(offset)定义。光标在文件开头处的offset为0,每多一个字符offset++。

SEEK_SET文件开头   0
SEEK_CUR当前光标位置
SEEK_END文件尾

3.3.4 与文件读写相关的系统调用
函数原型参数返回值库作用
int open (char *pathname, int flag, int mode); int open(const char *pathname, int flag);

return:创建/打开文件失败返回-1,否则返回文件标识符。

parameter:pathname为文件的路径。flag指定文件的打开方式(可以使用文件的打开方式常数)。mode用于在创建文件时(flag = O_CREATE)指定文件的权限(可以使用文件权限常数)。

三个参数的形式用于创建文件;两个参数的形式用于打开文件。
int close (int filedes);

return:0表示文件关闭成功,-1表示文件关闭失败。

parameter:filedes表示想要关闭的文件的标识符。

用于关闭文件
int read (int filedes, char *buff, unsigned int nbytes);

reutrn:返回-1表示读取失败;成功返回nbytes即读取的字符数。

parameter:filedes表示进行读取的文件的文件标识符,buff用于存放读取内容的字符数组,nbytes本次读取的字符数。

用于从指定文件中读取指定长度的内容并存放到指定的字符串中
int write (int filedes, char *buff, unsigned int nbytes);

reutrn:返回-1表示读取失败;成功返回nbytes即写入的字符数。

parameter:filedes表示进行写入的文件的文件标识符,buff用于存放写入内容的字符数组,nbytes本次写入的字符数。

用于向指定文件中写入指定长度的内容
long lseek (int filedes, long offset, int whence);

return:返回光标相对于文件开头偏移量

parameter:filedes为文件标识符,offset为相对于参照点的光标偏移量(光标与参照点间的字符数量),whence为参照点,可以设置为光标位置常量。

用于调整光标的位置。
int creat(char *pathname,int mode);

return:负数表示失败,而且不同的值对于不同的失败原因;正数表示创建成功,且为文件标识符。

parameter:参见open函数

创建文件

3.3.5 代码示例

程序一:理解open和create使用的文件权限常量。file1.out通过creat(注意没有e,不是create)和open分别创建文件file1_1.txt和file1_2.txt。希望的结果是file1_1.txt的权限为rw-r--r--,file1_2.txt的权限是r---w----。

#include 
#include 
#include 
#include 
#include 
#include 
int main(void){
        //create file1_1.txt
        int fd1 = creat("./file1_1.txt",0644);//the highest bit 0 stands for this is in OCT
        if(fd1 < 0){
                printf("fail to create file1_1.txtn");
        }

        //create file1_2.txt
        int fd2 = open("./file1_2.txt",O_CREAT,0420);
        if(fd2 < 0){
                printf("fail to create file1_1.txtn");
        }
        return 0;
}

 程序二.写源文件file2.c,作用是往file1_1中中写入一些内容:"Los Angels FC and PhiladelphiaXXXXn"要求先写入Philadelphia Union,再写入Los Angels。

#include 
#include 
#include 
#include 
#include 
int main(void){
        char club1[] = "Los Angeles ";
        char club2[] = "PhiladelphiaXXXXn";
        int fd = open("file1_1.txt",O_RDWR);
        if(fd < -1){
                printf("fail to open file1_1.txtn");
                exit(1);
        }
        if(fd > 0){
                write(fd,club2,sizeof(club2) - 1);
                lseek(fd,0,SEEK_SET);
                write(fd,club1,sizeof(club1) - 1);

        }


        return 0;
}

这表明,O_WRONLY和O_RDWR都是覆写(清空全部内容,之后写),而不是续写(append)。第二,光标向前移动,并且键入内容时,光标不会“挤开”后续的字符,而是会直接覆写。

第三个程序为课件上的。

 

3.4system call系统调用

 理解为Linux/Unix内核提供的函数就行。

3.5signal信号

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/871626.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号