LINUX系统编程初探(补充中)

4 篇文章 0 订阅
4 篇文章 0 订阅
本文介绍了Linux操作系统中的文本编辑器Vim的使用,包括其三种模式、基本操作和文件编辑流程。同时,详细讲解了Linux文件系统、错误处理、文件描述符、文件权限管理等概念,以及stat函数、open、close、read、write等系统调用。此外,还涉及到了进程、进程间通信、线程和进程调度的基础知识,以及Socket通信机制和常用函数如memset、memcpy、strcmp等。
摘要由CSDN通过智能技术生成

一:vim-gcc-动态库静态库

1. gedit

gedit是一个GNOME桌面环境下兼容UTF-8的文本编辑器。它使用GTK+编写而成,因此它十分的简单易用,有良好的语法高亮,对中文支持很好,支持包括gb2312gbk在内的多种字符编码。gedit是一个自由软件

这是 Linux 下的一个纯文本编辑器,但你也可以把它用来当成是一个集成开发环境 (IDE), 它会根据不同的语言高亮显现关键字和标识符。

gedit是一个Linux环境下的文本编辑器,类似windows下的写字板程序,在不需要特别复杂的编程环境下,作为基本的文本编辑器比较合适。

deng@itcast:~$ gedit txt &

2. VIM

vi有三种基本工作模式: 命令模式、文本输入模式(编辑模式)、末行模式

(1)命令模式

任何时候,不管用户处于何种模式,只要按一下ESC键,即可使vi进入命令模式。我们在shell环境(提示符为$)下输入启动vim命令,进入编辑器时,也是处于该模式下。

在命令模式下,用户可以输入各种合法的vi命令,用于管理自己的文档。

但需注意的是,所输入的命令并不在屏幕上显示出来。若输入的字符不是vi的合法命令,vi会响铃报警。

(2)文本输入模式(编辑模式)

在命令模式下输入插入命令i(I)、附加命令a(A) 、打开命令o(O)、替换命s(S)都可以进入文本输入模式,此时vi窗口的最后一行会显示“插入”。

在文本输入过程中,若想回到命令模式下,按键ESC即可。

 (3)末行模式

在命令模式下,输入冒号即可进入末行模式。此时vi窗口的状态行会显示出冒号,等待用户输入命令。用户输入完成后,按回车执行,之后vi编辑器又自动返回到命令模式下。

(4)打开文件/创建文件--->编辑文件---->保存文件

vim filename:打开或新建文件,并将光标置于第一行行首,如果文件不存在,则会新建文件。

在命令模式输入i编辑文件

一定要先退出插入模式(按Esc进入命令模式),然后(小写状态下),shift + zz (按住 “shift” + 按两下“z”键),或者(大写状态下:ZZ) 即可保存退出当前文件。

第一步:进入命令模式(ESC)

第二步:shifit + z z

(5)实用操作

1)切换到编辑模式

按键功能
i光标位置当前处插入文字
I光标所在行首插入文字
o(字母)光标下一行插入文字(新行)
O(字母)光标上一行插入文字(新行)
a光标位置右边插入文字
A光标所在行尾插入文字
s删除光标后边的字符,从光标当前位置插入
S删除光标所在当前行,从行首插入

2) 光标移动

按键功能
Ctrl + f向前滚动一个屏幕
Ctrl + b向后滚动一个屏幕
gg到文件第一行行首
G(大写)到文件最后一行行首,G必须为大写
mG或mgg到指定行,m为目标行数
0(数字)光标移到到行首(第一个字符位置)
$光标移到到行尾
l(小写L)向右移动光标
h向左移动光标
k向上移动光标
j向下移动光标
^光标移到到行首(第一个有效字符位置

3)复制粘贴

按键功能
[n]yy复制从当前行开始的 n 行
p把粘贴板上的内容插入到当前行

4)删除

按键功能
[n]x删除光标后 n 个字符
[n]X删除光标前 n 个字符
D删除光标所在开始到此行尾的字符
[n]dd删除从当前行开始的 n 行(准确来讲,是剪切,剪切不粘贴即为删除)
dG删除光标所在开始到文件尾的所有字符
dw删除光标开始位置的字,包含光标所在字符
d0(0为数字)删除光标前本行所有内容,不包含光标所在字符
dgg删除光标所在开始到文件首行第一个字符开始的所有字符

5)撤销恢复

按键功能
.(点)执行上一次操作
u撤销前一个命令
ctrl+r反撤销
100 + .执行上一次操作100次

6)保存退出

按键功能
ZZ(shift+z+z)保存退出

7)查找

按键功能
/字符串从当前光标位置向下查找(n,N查找内容切换)
?字符串从当前光标位置向上查找(n,N查找内容切换)

8)替换

按键功能
r替换当前字符
R替换当前行光标后的字符(ESC退出替换模式)

9)可视模式

按键功能
v按字符移动,选中文本,可配合h、j、k、l选择内容,使用d删除,使用y复制
Shift + v行选(以行为单位)选中文本,可配合h、j、k、l选择内容,使用d删除,使用y复制
Ctrl + v列选 选中文本,可配合h、j、k、l选择内容,使用d删除,使用y复制

二. LINUX文件和系统调用

(1)错误处理函数

errno 是记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定义。查看错误代码errno是调试程序的一个重要方法。

#include <stdio.h>  //fopen
#include <errno.h>  //errno
#include <string.h> //strerror(errno)
​
int main()
{
    FILE *fp = fopen("xxxx", "r");
    if (NULL == fp)
    {
        printf("%d\n", errno);  //打印错误码
        printf("%s\n", strerror(errno)); //把errno的数字转换成相应的文字
        perror("fopen err");    //打印错误原因的字符串
    }
​
    return 0;
}

(2)文件描述符

        在 Linux 的世界里,一切设备皆文件。我们可以系统调用中 I/O 的函数I:input,输入;O:output,输出),对文件进行相应的操作( open()、close()、write() 、read() 等)。

        程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的。

#define STDIN_FILENO  0 //标准输入的文件描述符
#define STDOUT_FILENO 1 //标准输出的文件描述符
#define STDERR_FILENO 2 //标准错误的文件描述符

:在程序运行起来后打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。调用这个返回的文件描述符可以对文件进行相应的操作

(3)常见的IO函数

  open:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
​
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:
    打开文件,如果文件不存在则可以选择创建。
参数:
    pathname:文件的路径及文件名
    flags:打开文件的行为标志,必选项 O_RDONLY, O_WRONLY, O_RDWR
    mode:这个参数,只有在文件不存在时有效,指新建文件时指定文件的权限
返回值:
    成功:成功返回打开的文件描述符
    失败:-1

必选项:

取值含义
O_RDONLY以只读的方式打开,如果文件不存在,会报错。
O_WRONLY以只写的方式打开
O_RDWR以可读、可写的方式打开

可选项,和必选项按位或起来

取值含义
O_CREAT文件不存在则创建文件,使用此选项时需使用mode说明文件的权限
O_EXCL如果同时指定了O_CREAT,且文件已经存在,则出错
O_TRUNC如果文件存在,则清空文件内容
O_APPEND写文件时,数据添加到文件末尾
O_NONBLOCK对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O

mode补充说明

1) 文件最终权限:mode & ~umask

2) shell进程的umask掩码可以用umask命令查看

Ø umask:查看掩码(补码)

Ø umask mode:设置掩码,mode为八进制数

Ø umask -S:查看各组用户的默认操作权限

取值八进制含义
S_IRWXU00700文件所有者的读、写、可执行权限
S_IRUSR00400文件所有者的读权限
S_IWUSR00200文件所有者的写权限
S_IXUSR00100文件所有者的可执行权限
S_IRWXG00070文件所有者同组用户的读、写、可执行权限
S_IRGRP00040文件所有者同组用户的读权限
S_IWGRP00020文件所有者同组用户的写权限
S_IXGRP00010文件所有者同组用户的可执行权限
S_IRWXO00007其他组用户的读、写、可执行权限
S_IROTH00004其他组用户的读权限
S_IWOTH00002其他组用户的写权限
S_IXOTH00001其他组用户的可执行权限

例如:我们创建了txt文件,编译如下代码,会返回文件描述符3,因为0,1,2已经被占用了。

 

如下是几种常见的open打开方法:

 

 close:

#include <unistd.h>
​
int close(int fd);
功能:
    关闭已打开的文件
参数:
    fd : 文件描述符,open()的返回值
返回值:
    成功:0
    失败: -1, 并设置errno

write:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:
    把指定数目的数据写到文件(fd)
参数:
    fd :  文件描述符
    buf : 数据首地址
    count : 写入数据的长度(字节)
返回值:
    成功:实际写入数据的字节个数
    失败: - 1

read:

#include <unistd.h>
​
ssize_t read(int fd, void *buf, size_t count);
功能:
    把指定数目的数据读到内存(缓冲区)
参数:
    fd : 文件描述符
    buf : 内存首地址
    count : 读取的字节个数
返回值:
    成功:实际读取到的字节个数
    失败: - 1

其中memset是一个初始化函数,作用是将某一块内存中的全部设置为指定的值。这里将buf数组设置全部为0。

注:阻塞和非阻塞的概念

读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。

从终端设备或网络读则不一定,如果没有读出数据(例如用户一直没输入),调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。

同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。

# 以非阻塞方式打开文件程序示例:

#include <unistd.h> //read
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h> //EAGAIN
​
int main()
{
    // /dev/tty --> 当前终端设备
    // 以不阻塞方式(O_NONBLOCK)打开终端设备
    int fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
​
    char buf[10];
    int n;
    n = read(fd, buf, sizeof(buf));
    if (n < 0)
    {
        // 如果为非阻塞,但是没有数据可读,此时全局变量 errno 被设置为 EAGAIN
        if (errno != EAGAIN)
        {
            perror("read /dev/tty");
            return -1;
        }
        printf("没有数据\n");
    }
​
    return 0;
}

lseek:

#include <sys/types.h>
#include <unistd.h>
​
off_t lseek(int fd, off_t offset, int whence);
功能:
    改变文件的偏移量
参数:
    fd:文件描述符
    offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
​
    whence:其取值如下:
        SEEK_SET:从文件开头移动offset个字节
        SEEK_CUR:从当前位置移动offset个字节
        SEEK_END:从文件末尾移动offset个字节
返回值:
    若lseek成功执行, 则返回新的偏移量
    如果失败, 返回-1
 

 stat函数(重点)

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
​
int stat(const char *path, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
功能:
    获取文件状态信息
    stat和lstat的区别:
        当文件是一个符号链接时,lstat返回的是该符号链接本身的信息;
        而stat返回的是该链接指向的文件的信息。
参数:
    path:文件名
    buf:保存文件信息的结构体
返回值:
    成功: 0
    失败: -1
struct stat结构体说明:

struct stat {
    dev_t           st_dev;     //文件的设备编号
    ino_t           st_ino;     //节点
    mode_t          st_mode;            //文件的类型和存取的权限
    nlink_t         st_nlink;       //连到该文件的硬连接数目,刚建立的文件值为1
    uid_t           st_uid;     //用户ID
    gid_t           st_gid;     //组ID
    dev_t           st_rdev;        //(设备类型)若此文件为设备文件,则为其设备编号
    off_t           st_size;        //文件字节数(文件大小)
    blksize_t       st_blksize;     //块大小(文件系统的I/O 缓冲区大小)
    blkcnt_t        st_blocks;      //块数
    time_t          st_atime;       //最后一次访问时间
    time_t          st_mtime;       //最后一次修改时间
    time_t          st_ctime;       //最后一次改变时间(指属性)
};

st_mode(16位整数)参数说明:

 

 测试:

int main()
{
    struct stat buf;
    stat("a.txt", &buf);
​
    if (S_ISREG(buf.st_mode))
    {
        printf("%s\n", "这是普通文件");
    }
​
    return 0;
}

其他常用函数: 

1 access函数
#include <unistd.h>
​
int access(const char *pathname, int mode);
功能:测试指定文件是否具有某种属性
参数:
    pathname:文件名
    mode:文件权限,4种权限
        R_OK:   是否有读权限
        W_OK:   是否有写权限
        X_OK:   是否有执行权限
        F_OK:   测试文件是否存在
返回值:
    0:  有某种权限,或者文件存在
    -1:没有,或文件不存在
access("txt", F_OK);
 

2 chmod函数
#include <sys/stat.h>
​
int chmod(const char *pathname, mode_t mode);
功能:修改文件权限
参数:
    filename:文件名
    mode:权限(8进制数)
返回值:
    成功:0
    失败:-1
 

3 chown函数
#include <unistd.h>
​
int chown(const char *pathname, uid_t owner, gid_t group);
功能:修改文件所有者和所属组
参数:
    pathname:文件或目录名
    owner:文件所有者id,通过查看 /etc/passwd 得到所有者id
    group:文件所属组id,通过查看 /etc/group 得到用户组id
返回值:
    成功:0
    失败:-1

4 truncate函数
#include <unistd.h>
#include <sys/types.h>
​
int truncate(const char *path, off_t length);
功能:修改文件大小
参数:
    path:文件文件名字
    length:指定的文件大小
        a)比原来小, 删掉后边的部分
        b)比原来大, 向后拓展
返回值:
    成功:0
    失败:-1
 

5 link函数
#include <unistd.h>
​
int link(const char *oldpath, const char *newpath);
功能:创建一个硬链接
参数:
    oldpath:源文件名字
    newpath:硬链接名字
返回值:
    成功:0
    失败:-1
 

6 symlink函数
#include <unistd.h>
​
int symlink(const char *target, const char *linkpath);
功能:创建一个软链接
参数:
    target:源文件名字
    linkpath:软链接名字
返回值:
    成功:0
    失败:-1

7  readlink函数
#include <unistd.h>
​
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
功能:读软连接对应的文件名,不是读内容(该函数只能读软链接文件)
参数:
    pathname:软连接名
    buf:存放软件对应的文件名
    bufsiz :缓冲区大小(第二个参数存放的最大字节数)
返回值:
    成功:>0,读到buf中的字符个数
    失败:-1
​
8 unlink函数
#include <unistd.h>
​
int unlink(const char *pathname);
功能:删除一个文件(软硬链接文件)
参数:
    pathname:删除的文件名字
返回值:
    成功:0
    失败:-1
 

9 rename函数
#include <stdio.h>
​
int rename(const char *oldpath, const char *newpath);
功能:把oldpath的文件名改为newpath
参数:
oldpath:旧文件名
newpath:新文件名
返回值:
成功:0
失败:-1
 

10 文件描述符复制(重点)
10.1 概述
dup() 和 dup2() 是两个非常有用的系统调用,都是用来复制一个文件的描述符,使新的文件描述符也标识旧的文件描述符所标识的文件。

这个过程类似于现实生活中的配钥匙,钥匙相当于文件描述符,锁相当于文件,本来一个钥匙开一把锁,相当于,一个文件描述符对应一个文件,现在,我们去配钥匙,通过旧的钥匙复制了一把新的钥匙,这样的话,旧的钥匙和新的钥匙都能开启这把锁。

对比于 dup(), dup2() 也一样,通过原来的文件描述符复制出一个新的文件描述符,这样的话,原来的文件描述符和新的文件描述符都指向同一个文件,我们操作这两个文件描述符的任何一个,都能操作它所对应的文件。

 

10.2 dup函数
#include <unistd.h>
​
int dup(int oldfd);
功能:
    通过 oldfd 复制出一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符,最终 oldfd 和新的文件描述符都指向同一个文件。
参数:
    oldfd : 需要复制的文件描述符 oldfd
返回值:
        成功:新文件描述符
        失败: -1
 

10.3 dup2函数
#include <unistd.h>
​
int dup2(int oldfd, int newfd);
功能:
    通过 oldfd 复制出一个新的文件描述符 newfd,如果成功,newfd 和函数返回值是同一个返回值,最终 oldfd 和新的文件描述符 newfd 都指向同一个文件。
参数:
    oldfd : 需要复制的文件描述符
    newfd : 新的文件描述符,这个描述符可以人为指定一个合法数字(0 - 1023),如果指定的数字已经被占用(和某个文件有关联),此函数会自动关闭 close() 断开这个数字和某个文件的关联,再来使用这个合法数字。
返回值:
    成功:返回 newfd
    失败:返回 -1


10.4. fcnlt函数
#include <unistd.h>
#include <fcntl.h>
​
int fcntl(int fd, int cmd, ... /* arg */);
功能:改变已打开的文件性质,fcntl针对描述符提供控制。
参数:
    fd:操作的文件描述符
    cmd:操作方式
    arg:针对cmd的值,fcntl能够接受第三个参数int arg。
返回值:
    成功:返回某个其他值
    失败:-1
fcntl函数有5种功能:

1) 复制一个现有的描述符(cmd=F_DUPFD)

2) 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)

3) 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)

4) 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)

5) 获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)

 

参考示例:

// 等价于dup()
    int new_fd = fcntl(fd, F_DUPFD, 0);
​
    // 获取文件状态标志
    int flag = fcntl(fd, F_GETFL, 0);
    switch (flag & O_ACCMODE)
    {
    case O_RDONLY:
        printf("read only\n");
        break;
    case O_WRONLY:
        printf("write only\n");
        break;
    case O_RDWR:
        printf("read and write\n");
        break;
    default:
        break;
    }
​
    if (flag & O_APPEND)
    {
        printf("append\n");
    }
​
    flag |= O_APPEND; // 追加flag
    fcntl(fd, F_SETFL, flag); //设置文件状态标记
​
 

11. 目录相关操作(掌握)
5.1 getcwd函数
#include <unistd.h>
​
char *getcwd(char *buf, size_t size);
功能:获取当前进程的工作目录
参数:
    buf : 缓冲区,存储当前的工作目录
    size : 缓冲区大小
返回值:
    成功:buf中保存当前进程工作目录位置
    失败:NULL
 

12 chdir函数
#include <unistd.h>
​
int chdir(const char *path);
功能:修改当前进程(应用程序)的路径
参数:
    path:切换的路径
返回值:
    成功:0
    失败:-1
 

13 opendir函数
#include <sys/types.h>
#include <dirent.h>
​
DIR *opendir(const char *name);
功能:打开一个目录
参数:
    name:目录名
返回值:
    成功:返回指向该目录结构体指针
    失败:NULL
​
 

14 closedir函数
#include <sys/types.h>
#include <dirent.h>
​
int closedir(DIR *dirp);
功能:关闭目录
参数:
    dirp:opendir返回的指针
返回值:
    成功:0
    失败:-1
​
 

15 readdir函数
#include <dirent.h>
​
struct dirent *readdir(DIR *dirp);
功能:读取目录
参数:
    dirp:opendir的返回值
返回值:
    成功:目录结构体指针
    失败:NULL
​
相关结构体说明:

struct dirent
{
    ino_t d_ino;                  // 此目录进入点的inode
    off_t d_off;                    // 目录文件开头至此目录进入点的位移
    signed short int d_reclen;      // d_name 的长度, 不包含NULL 字符
    unsigned char d_type;           // d_type 所指的文件类型 
    char d_name[256];               // 文件名
};
d_type文件类型说明:

取值	含义
DT_BLK	块设备
DT_CHR	字符设备
DT_DIR	目录
DT_LNK	软链接
DT_FIFO	管道
DT_REG	普通文件
DT_SOCK	套接字
DT_UNKNOWN	未知
 

16. 时间相关函数
utime

time

 

   char *asctime(const struct tm *tm);
   char *asctime_r(const struct tm *tm, char *buf);
​
   char *ctime(const time_t *timep);
   char *ctime_r(const time_t *timep, char *buf);
​
   struct tm *gmtime(const time_t *timep);
   struct tm *gmtime_r(const time_t *timep, struct tm *result);
​
   struct tm *localtime(const time_t *timep);
   struct tm *localtime_r(const time_t *timep, struct tm *result);
​
   time_t mktime(struct tm *tm);

三:线程和进程调度

(1)什么是多线程?

如下图所示,cpu在三个程序之间作快速的切换,这就是一种多线程。

 (2)什么是并发和并行?

 并行:顾名思义,并行就是多个cpu同时执行。

并发:是在一个cpu上的一种高速的交替执行。

(3) 进程和线程的区别?(线程是属于进程的)

进程是并发执行的,也就是高速的交替执行。

线程是一条执行路径,例如360可以同时开始多个功能,就表示具有多个线程。

 

 

 (4)进程(了解)

了解内容:

        进程运行时,内核为进程每个进程分配一个PCB(进程控制块),维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。

重点:

调用进程的基本状态的方法。

进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。

在三态模型中,进程状态分为三个基本状态,即运行态,就绪态,阻塞态

在五态模型中,进程分为新建态、终止态,运行态,就绪态,阻塞态

 (5)进程间的通信(了解

        进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源。

        但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信( IPC:Inter Processes Communication )。

1. 管道通信

        管道也叫无名管道,它是是 UNIX 系统 IPC(进程间通信) 的最古老形式,所有的 UNIX 系统都支持这种通信机制。

2. 共享存储映射

mmap函数

四:Socket通信

Socket属于管道之类的,t就是插座(中文翻译成套接字有点莫名其妙),运行在计算机中的两个程序通过socket建立起一个通道,数据在通道中传输。

(1)socket通信机制

socket提供了流(stream)和数据报(datagram)两种通信机制,即流socket和数据报socket。

流socket基于TCP协议,是一个有序、可靠、双向字节流的通道,传输数据不会丢失、不会重复、顺序也不会错乱。就像两个人在打电话,接通后就在线了,您一句我一句的聊天。

数据报socket基于UDP协议,不需要建立和维持连接,可能会丢失或错乱。UDP不是一个可靠的协议,对数据的长度有限制,但是它的速度比较高。就像短信功能,一个人向另一个人发短信,对方不一定能收到。

注:一般都使用流socket

(2)客户/服务端模式

 服务端:

1)创建服务端的socket。

2)把服务端用于通信的地址和端口绑定到socket上。

3)把socket设置为监听模式。

4)接受客户端的连接。

5)与客户端通信,接收客户端发过来的报文后,回复处理结果。

6)不断的重复第5)步,直到客户端断开连接。

7)关闭socket,释放资源。

/*
 * 程序名:server.cpp,此程序用于演示socket通信的服务端
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
int main(int argc,char *argv[])
{
  if (argc!=2)
  {
    printf("Using:./server port\nExample:./server 5005\n\n"); return -1;
  }
 
  // 第1步:创建服务端的socket。
  int listenfd;
  if ( (listenfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); return -1; }
 
  // 第2步:把服务端用于通信的地址和端口绑定到socket上。
  struct sockaddr_in servaddr;    // 服务端地址信息的数据结构。
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;  // 协议族,在socket编程中只能是AF_INET。
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);          // 任意ip地址。
  //servaddr.sin_addr.s_addr = inet_addr("192.168.190.134"); // 指定ip地址。
  servaddr.sin_port = htons(atoi(argv[1]));  // 指定通信端口。
  if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 )
  { perror("bind"); close(listenfd); return -1; }
 
  // 第3步:把socket设置为监听模式。
  if (listen(listenfd,5) != 0 ) { perror("listen"); close(listenfd); return -1; }
 
  // 第4步:接受客户端的连接。
  int  clientfd;                  // 客户端的socket。
  int  socklen=sizeof(struct sockaddr_in); // struct sockaddr_in的大小
  struct sockaddr_in clientaddr;  // 客户端的地址信息。
  clientfd=accept(listenfd,(struct sockaddr *)&clientaddr,(socklen_t*)&socklen);
  printf("客户端(%s)已连接。\n",inet_ntoa(clientaddr.sin_addr));
 
  // 第5步:与客户端通信,接收客户端发过来的报文后,回复ok。
  char buffer[1024];
  while (1)
  {
    int iret;
    memset(buffer,0,sizeof(buffer));
    if ( (iret=recv(clientfd,buffer,sizeof(buffer),0))<=0) // 接收客户端的请求报文。
    {
       printf("iret=%d\n",iret); break;  
    }
    printf("接收:%s\n",buffer);
 
    strcpy(buffer,"ok");
    if ( (iret=send(clientfd,buffer,strlen(buffer),0))<=0) // 向客户端发送响应结果。
    { perror("send"); break; }
    printf("发送:%s\n",buffer);
  }
 
  // 第6步:关闭socket,释放资源。
  close(listenfd); close(clientfd);

客户端:

1)创建客户端的socket。

2)向服务器发起连接请求。

3)与服务端通信,发送一个报文后等待回复,然后再发下一个报文。

4)不断的重复第3)步,直到全部的数据被发送完。

5)第4步:关闭socket,释放资源。

/*
 * 程序名:client.cpp,此程序用于演示socket的客户端
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
int main(int argc,char *argv[])
{
  if (argc!=3)
  {
    printf("Using:./client ip port\nExample:./client 127.0.0.1 5005\n\n"); return -1;
  }
 
  // 第1步:创建客户端的socket。
  int sockfd;
  if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); return -1; }
 
  // 第2步:向服务器发起连接请求。
  struct hostent* h;
  if ( (h = gethostbyname(argv[1])) == 0 )   // 指定服务端的ip地址。
  { printf("gethostbyname failed.\n"); close(sockfd); return -1; }
  struct sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通信端口。
  memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
  if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)  // 向服务端发起连接清求。
  { perror("connect"); close(sockfd); return -1; }
 
  char buffer[1024];
 
  // 第3步:与服务端通信,发送一个报文后等待回复,然后再发下一个报文。
  for (int ii=0;ii<3;ii++)
  {
    int iret;
    memset(buffer,0,sizeof(buffer));
    sprintf(buffer,"这是第%d个超级女生,编号%03d。",ii+1,ii+1);
    if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0) // 向服务端发送请求报文。
    { perror("send"); break; }
    printf("发送:%s\n",buffer);
 
    memset(buffer,0,sizeof(buffer));
    if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0) // 接收服务端的回应报文。
    {
       printf("iret=%d\n",iret); break;
    }
    printf("接收:%s\n",buffer);
  }
 
  // 第4步:关闭socket,释放资源。
  close(sockfd);
}

(3)几种常用的函数

  • memset()函数

memset 函数原型是

extern void* memset(void *buffer, int length, int buf)

buffer 是指针或者数组,
void是要初始化的首内存的首地址;
length是要初始化的变为的数据,
buf是buffer的长度,即大小。

用法: 

网络编程中memset函数多用于socket套接字编程中清空数组,如:

memset(IPNumber, 0, 4) ;	//将IPNumber数组中的4位置为0

该函数用于将一段内存空间全部设置为某个字符,一般设置为‘ ’或者 ‘/0’

char length[100];
memset(length,’/0’,sizeof(length));

memset()函数可以很方便地清空一个数组或结构体类型
数组类型:

struct test_struct arr[10];
memset(arr,0,sizeof(struct test_struct)*10); //是乘以十

结构体类型:

struct test_struct
{
char m_Name[16];
int m_Seq;
int m_Type;
};

 对于变量:struct test_srtuct stTest
一般情况下,清空stTest的方法:

stTest.m_Name[0]=‘0’;
stTest.m_Seq=0;
stTest.m_Type=0;

而使用用memset()函数方法:

memset(&stTest,0,sizeof(struct(struct test_struct));
  • memcpy(内存拷贝函数)

c和c++使用的内存拷贝函数
memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。

void* memcpy(void* destination, const void* source, size_t num);
void* dest 目标内存    
const void* src  源内存    
size_t num  字节个数

    库中实现的memcpy函数:


struct {
	char name[40];
	int age;
} person, person_copy;
 
int main()
{
	char myname[] = "Pierre de Fermat";
	/* using memcpy to copy string: */
	memcpy(person.name, myname, strlen(myname) + 1);
	person.age = 46;
	/* using memcpy to copy structure: */
	memcpy(&person_copy, &person, sizeof(person));
	printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
	system("pause");
	return 0;

  •  strcmp函数

 strcmp函数语法为:

int strcmp(char *str1,char *str2)

其作用是比较字符串str1和str2是否相同,
如果相同则返回0,如果不同,前者大于后者则返回1,否则返回-1。

例如:

char a[]="abcd";
char *b="abcd";
char *d="abcde";
int d=strcmp(a,b); //那么d的值是0
d=strcmp(b,d); //d的值是-1 因为 '\0' 比'e' 小
d=strcmp(d,b); //d的值是1,因为 'e' 比'\0'大

#include 
#include  
int main(){
    char str1[50] = { 0 };
    char str2[50] = { 0 };
    int i = 1;
    do {
        printf("******第%d次输入******\n", i);
        gets(str1);
        gets(str2);
        i ;
    } while ( strcmp(str1, str2) );
    return 0;
}
  • strcpy函数

strcpy函数的作用是把含有转义字符\0即空字符作为结束符,
然后把src该字符串复制到dest,且返回值的类型为“char*”;
strcpy是“string copy”(字符串复制)的缩写。

char *strcpy(char *dest, const char *src) 
把 src 所指向的字符串复制到 dest。

dest -- 指向用于存储复制内容的目标数组。
src -- 要复制的字符串。
该函数返回一个指向最终的目标字符串 dest 的指针。

注:需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。

例如:

#include <stdio.h>
#include <string.h>
 
int main(void){
 
    char dest[6] = "Hello";
    char src[20] = "World\0Hi";
 
    strcpy(dest, src);
 
    printf("将src的字符串赋值到dest:%s\n",dest);
 
    return 0;
}
  • strcpy、memcpy的用法与区别

 strcpy和memcpy主要有以下3个方面的区别。(memcpy功能更强大

  1. 复制的内容不同。 strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
  2. 复制的方法不同。 strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度
  3. 用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
  • signal()函数
signal(参数1, 参数2);

参数1:我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l查看(共64个)。其实这些信号时系统定义的宏。

参数2:我们处理的方式(是系统默认还是忽略还是捕获)

几种常见的信号:

signal(SIGCLD,SIG_IGN)
signal(SIGHUP,SIG_IGN)
signal(SIGINT,SIG_IGN)
signal(SIGQUIT,SIG_IGN)

处理的方式:SIG_IGN:

服务器close连接后,客户端发送信息得到的信号是terminate(终止、退出), 所以client会退出。

使用SIG_IGN就不会被退出。

SIGCLD信号:

  • 子进程状态改变(终止或停止)后产生此信号(SIGCLD/SIGHCLD),传递给父进程。
  • 如果父进程想要捕捉此信号,可以捕获该信号并进行处理。

注:

  • SIGCLD是System V采用的信号名
  • SIGCHLD是BSD采用的信号名。POSIX.1也采用BSD的SIGCHLD信号
  • 只有Linux 3.2.0和Solaris 10定义了SIGCLD,SIGCLD等同于SIGCHLD

SIGHUP信号:

        SIGHUP 信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联. 系统对SIGHUP信号的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。

 SIGINT信号:

程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。

SIGQUIT信号:

 和SIGINT类似, 但由QUIT字符(通常是Ctrl-\)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值