C++Linux网络编程基础

这篇具有很好参考价值的文章主要介绍了C++Linux网络编程基础。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

动态库和静态库

当动态库和静态库同时存在的时候,会优先使用动态库

静态库

1. 制作静态库

g++ -c -o lib库名.a 源文件代码清单

-c表示只编译,-o则是说明需要指定文件名

2. 使用静态库
g++ 选项 源代码文件名清单 -l库名 -L库文件所在的目录名
3. 库文件的概念

程序在编译时,会将库文件的二进制代码链接到目标程序中,这种方式称为静态编译
如果多个程序中用到了同一个静态库中的函数,就会存在多份拷贝。

4. 静态库的特点
  • 静态库的链接是在编译时期完成的,执行的时候代码加载速度快。
  • 目标程序的可执行文件比较大,浪费空间
  • 程序的更新和发布不方便,如果某一个静态库更新了,所有使用它的程序都需要重新编译

动态库

1. 制作动态库
g++ -fPIC -shared -o lib库名.so 源代码文件清单
2. 使用动态库
g++ 选项 源代码文件名清单 -l库名 -L库文件所在的目录名

需要注意的是:运行可执行程序的时候,需要提前设置LD_LIBRARY_PATH环境变量。

3. 动态库的概念

程序在编译时不会把库的二进制代码链接到目标程序中,而是在运行的时候才被载入。
如果多个程序中用到了同一动态库中的函数,那么在内存中只有一份,避免了空间浪费问题。

4. 动态库的特点
  • 程序在运行的过程中,需要用到动态库的时候才把动态库的二进制代码载入内存
  • 可以实现进程之间的代码共享,因此动态库也称为共享库
  • 程序升级比较简单,不需要重新编译程序,只需要更新动态库就行

makefile

makefile是一个编译规则文件,用于实现自动化编译
[[…/杂项/Makefile|Makefile]]中有写

main函数的参数

main函数有三个参数,分别是argc、argv和envp:

int main(int argc, char* argv[], char* envp[]){ }
  • argc:存放了程序参数的个数,包括程序本身
  • argv:字符串数组,存放了每个参数的值,包括程序名本身
  • envp:字符串数组,存放了环境变量,数组的最后一个元素是空

什么叫包括程序本身?
在Linux中,我们想要运行这个程序,就需要在终端中使用指令:

./程序名

其实这就相当于将程序名作为一个参数传递给main函数,因此不管什么时候,argc最小都为1,但是我们在终端输入的时候可能还有别的情况:

./程序名 Hello World

此时这个main函数就接收了三个参数,即:argc = 3,此时的argv为:

argv[0] = "./程序名"
argv[1] = "hello"
argv[2] = "world"

操作环境变量

1. 设置环境变量

使用函数setenv():(这个函数是POSIX提供的,因此只能够在Linux系统中使用

int setenv(const char* name, const char* value, int overwrite);
  • name:环境变量名
  • value:环境变量的值
  • overwrite:这个变量的值有两种情况:0和非0
    • 0:如果环境变量不存在,则增加新的环境变量;如果环境变量已经存在,不替换它的值
    • 非0:如果环境变量不存在,则增加新的环境变量;如果环境变量已经存在,替换它的值
  • 返回值:0(成功),-1(失败)
注意事项

此函数设置的环境变量只对本进程有效,不会影响shell的环境变量
也就是说,如果执行了setenv()函数后关闭了该程序,上次的设置失效。

获取环境变量的值

char* getenv(const char* name);

这个函数就更简单了,好像也没什么好说的。
但是这个函数与setenv不同,getenv()是C/C++库提供的,在stdlib.h(cstdlib)中

gdb常用命令

gdb(GNU symbolic debugge)是C/C++最常用的调试工具,gdb通常需要手动安装。

1. 安装gdb

sudo apt install gdb

2. gdb常用命令

如果希望程序可调试,编译的时候需要添加-g(gdb的缩写)选项,并且不能够使用-O选项进行优化
在开始调试之前,需要输入指令:

gdb 目标程序
命令 简写 命令说明
set args 设置程序运行的参数,例如:set args 需要输入的参数
break b 设置断点(可以有多个),例如:b 20,表示在第20行设置断点
run r 开始运行程序,或在程序运行结束后重新开始执行
next n 执行当前行语句,如果该语句为函数调用,不会进入函数内部
step s 执行当前行语句,如果该语句为函数调用,则会进入函数内部(有源码才能进)
print() p 显示变量或者表达式的值,如果p后面是表达式,会执行这个表达式
continue c 继续运行程序,遇到下一个断点停止,如果没有遇到断点,程序将会一直运行
set var 设置变量的值
quit q 退出gdb模式

Linux的时间操作

UNIX操作系统根据计算机产生的年代把1970年1月1日作为UNIX的纪元时间,1970年1月1日是时间的中间点,将从1970年1月1日起经过的秒数用一个整数存放

time_t

time_t用于表示事件类型,它是long类型的别名,在头文件time.h中定义,用于表示1970年1月1日到0时0秒到现在的秒数

time()

time函数用于获取操作系统的当前时间,需要使用头文件time.h
它有两种使用方法:

  1. 将空地址传给time(),并将time的返回值赋值给now:
    #include <time.h>
    
    time_t now = time(0);
    
  2. 将变量的地址作为参数传递给time():
    #include <time.h>
    time_t now;
    time(&now);
    

tm结构体,localtime()和mktime()

time_t是一个长整数,不符合人类的使用习惯,需要转换成tm结构体,tm结构体在头文件time.h中:

struct tm{
	int tm_year; // 年份:其值等于实际年份减去1970
	int tm_mon;  // 月份:取值区间为[0, 11]
	int tm_mday; // 日期:一个月中的日期,取值区间为[1, 31]
	int tm_hour; // 时:取值区间为[0, 23]
	int tm_min;  // 分:取值区间为[0, 59]
	int tm_sec;  // 秒:取值区间为[0, 59]
	int tm_wday; // 星期:取值区间为[0, 6],0是星期天,6是星期六
	int tm_yday; // 从每年的1月1日开始算起的天数,取值区间为[0, 365]
	int tm_isdst;// 夏令时标识符(没啥用)
}

想要将time_h转换为tm结构体,需要使用库函数localtime,需要使用头文件time.h
需要注意的是:loacaltime()不是线程安全的(因为它使用一个静态的结构来存储转换后的本地时间,并返回指向该结构的指针),而localtime_r()是线程安全的(它接受一个指向存储结构的指针作为参数,并将转换后的本地时间存储在该结构中,而不需要使用静态的存储)

struct tm *localtime(const time_t* timep);
struct tm *localtime_r(const time_t* timep, struct tm* result);

若是要将tm结构体转换成time_t,就需要使用库函数mktime,它也在time.h中:

time_t mktime(struct tm* tm);

该函数主要用于时间的计算

gettiemofday()

该函数用于获取1970年1月1日到现在的秒和当前秒钟已逝去的微妙数,可用于程序计时,该函数在头文件sys/time.h钟。

int gettimeofday(struct timeva* tv, struct timezone* tz);

struct timeval{
	time_t        tv_sec;  // seconds
	susenconds    tv_usec; // microseconds
};

struct timezone{           // 时区
	int tz_minuteswest;    // minutes west of Greenwich
	int tz_dsttime;        // type of DST correction
};

程序睡眠

如果需要将程序挂起一段时间,可以使用sleep()和usleep()两个库函数,需要使用头文件unistd.h

unsigned int sleep(unsigned int seconds); // 单位是秒
int usleep(useconds_t usec);              // 单位是微秒

目录操作函数

1. 获取当前目录函数getcwd()和get_current_dir_name()

getcwd()和get_current_dir_name(),这两个函数都在头文件unistd.h中:

char* getcwd(char* buf, size_t size);
char* get_current_dir_name(void);

这两个函数功能上没什么区别:

#include <iostream>
#include <unistd.h>
using namespace std;

int main(){
	char path1[256];    // linux系统目录的最大长度嘶255
	getcwd(path1, 256);
	cout << "path1 = " << path1 << endl;

	char* path2 = get_current_dir_name();
	cout << "path2 = " << path2 << endl;
	free(path2);        // 注意释放内存
}

注意事项:get_currrent_dir_name()会动态分配内存,需要使用char*进行接收,并且这块内存需要我们进行手动释放,并且需要注意,get_current_dir_name()中使用的是malloc进行内存分配,因此我们在释放的时候也要使用freenew、delete、malloc、free不能混用!混用可能会导致问题

2. 切换工作目录chdir()、创建目录mkdir()和删除目录rmdir()

切换工作目录chdir()

切换工作目录函数chdir需要包含头文件unistd.h

#include <unistd.h>

int chdir(const char* path);

若是返回值为0则表示切换成功,若非0则失败(目录不存在或没有权限)

创建目录mkdir()

创建目录的函数名就是Linux中创建目录的命令名mkdir,它需要使用头文件sys/stat.h

#include <sys/stat.h>

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

可以看到该函数有两个参数:

  • pathname:目录名
  • mode:访问权限的数字写法,如:0755(不能省略前置的0,因为权限数字是八进制

返回值和chdir()一样。

删除目录rmdir()

使用过rmdir()需要包含头文件unistd.h

int rmdir(const char* path);

path就是要删除的目录的路径,返回值和chdir()也是一样的。

获取目录中文件的列表

这一系列的操作都需要使用头文件dirent.h(dir event),一共有三个步骤:

步骤一:用opendir()打开目录
DIR* opendir(const char* pathname);

若是成功,返回目录的地址;若是失败,返回空地址。

步骤二:用readdir()读取目录
struct dirent* readdir(DIR* dirp);

若是成功过,返回struct dirent结构体的地址;若是失败,返回空地址。

步骤三:用closedir()关闭目录
int closedir(DIR* dirp);

相关的数据结构DIR

在上面的函数中,我们使用了目录指针DIR*,每调用一次readdir(),含税返回struct dirent的地址,存放了本次读取到的内容

struct dirent{
	long d_ino;                 // inode number索引节点号
	off_t d_off;                // offset to this dirent在目录文件中的偏移
	unsigned short d_reclen;    // length of this d_name文件长度名
	unsigned char d_type;       // the type of d_name文件类型
	char d_name[NAME_MAX + 1];  // file name文件名,最长255字符(因为是Linux系统)
};

重点在d_named_type

  • d_name是文件名或目录名
  • d_type是文件类型,有多种取值,这里我们只关注两种:
    • 8:常规文件
    • 4:目录

Linux的系统错误

在C++程序中,如果调用了库函数,可以通过函数的返回值判断调用是否成功。其实还有一个整型的全局变量errno,存放了函数调用过程中产生的错误代码。
如果调用库函数失败,可以通过errno的值来查找原因,这也是调试程序的一个重要方法。
使用errno需要包含头文件errno.h(或cerrno),配合strerror()和perror()两个库函数,可以差点出错的详细信息。

strerror()

strerror()在头文件string.h中声明,用于获取错误代码对应的详细信息。它有两个版本,一个线程安全,一个非线程安全:

char* strerror(int errnum);                             // 非线程安全
char* strerror_r(int errnum, char* buf, size_t buflen); // 线程安全

这里给出一段示例代码:

#include <string.h>
#include <iostream>
using namespace std;

int main(){
	int ii;
	for(ii=0; ii<150; ii++){ // gcc 8.3.1 一共有133个错误代码
		cout << ii << ":" << strerror(ii) << endl;
	}
}

运行这段代码,能看到0133都是有语句输出的,其中:==0表示程序正常运行,1133是错误信息==。

perror()

perror()在头文件stdio.h中声明,用于在控制台显示最近一次系统错误的详细信息,在实际开发中,服务程序在后台运行,通过控制台显示错误信息意义不大:

void perror(const char* s);

注意事项

1. 调用库函数失败不一定会设置errno

并不是全部的库函数在调用失败时都会设置errno的值,以man手册为准(不属于系统调用的函数不会设置errno,即:操作系统(OS)提供的库才会设置errno

2. errno不能作为调用函数失败的标志

errno的值只有在库函数调用发生错误时才会被设置,当库函数调用成功时,errno的值不会被修改,不会主动得置为0。
在实际开发中,判断函数执行是否成功还得靠函数的返回值,只有在返回值是失败的情况下,才需要关注errno的值。

目录和文件的更多操作

access()

access()用于判断当前用户对目录或文件的存取权限,需要包含头文件unistd.h

int access(const char* pathname, int mode);
  • pathname:目录或文件名
  • mode:需要判断的存取权限,在unistd.h中存在如下宏定义:
      #define R_OK 4 // 判断是否有读权限
      #define W_OK 2 // 判断是否有写权限
      #define X_OK 1 // 判断是否有执行权限
      #define F_OK 0 // 判断是否存在
    
  • 返回值:若是pathname满足mode权限就返回0;不满足就返回-1,并设置errno(这也说明unistd.h是Linux提供的库

stat()与stat结构体

(略)

rename()

rename()函数在头文件stdio.h中,用于重命名目录或文件,相当于操作系统的mv命令

int rename(const char* oldpath, const char* newpath);
  • oldname:原目录或文件名
  • newpath:目标目录或文件名
  • 返回值:0(成功),-1(失败,并设置errno)

在实际开发中,access()主要用于判断目录或文件是否存在。

remove()

remove()函数在头文件stdio.h中,用于删除目录或文件,相当于操作系统的rm命令

#include <stdio.h>

int remove(const char* pathname);
  • pathname:待删除的目录或文件名
  • 返回值:0(成功),-1(失败,并设置errno)

Linux中的信号

信号的基本概念

信号(signal)是软件中断,是进程之间相互传递消息的一种方法,用于通知进程发生了事件,但是,不能给进程传递任何数据。
信号产生的原因有很多,在Shell中,可以用killkillall命令发送信号:

kill -信号的类型 进程编号
killall -信号的类型 进程名

信号处理、

进程对信号的处理方法有三种:

  1. 对该信号的处理采用系统的默认操作,大部分的信号的默认操作是终止进程
  2. 设置终端的处理函数,收到信号后,由该函数来处理
  3. 忽略某个信号,对该信号不做处理,就像未发生过一样

主要是通过signal函数来设置对信号的处理方式,需要包含头文件signal.h

sighandler_t signal(int signum, sighander_t handler);
  • signum:信号的编号,在Linux中默认有64种编号(0~63,其中很大一部分属于自定义信号,默认是终止进程
  • handle:信号的处理方式
    • SIG_DFL:恢复参数signum信号的处理方式为默认行为
    • 一个自定义的处理很好的函数,函数的形参是信号的编号
    • SIG_IGN:忽略参数signum所指的信号
      这里给出一段示例:
// 如果接收到信号1,就执行func函数中的内容
signal(1, func);

发送信号

可以使用kill库函数发送信号:

int kill(pid_t pid, int sig);
  • pid:指定的进程
  • sig:所指定的需要发送的信号

其他内容后续再来补充

进程终止

一共有八种方式可以终止进程,其中5种为正常终止:

  1. main()中使用return返回
  2. 在任意函数中调用exit()
  3. 在任意函数中调用_exit()或_Exit()
  4. . 最后一个线程中其启动例程(线程主函数)用return返回
  5. 在最后一个线程中调用pthread_exit()返回

还有3种异常终止:

  1. 调用abort()终止
  2. 接收到一个信号
  3. 最后一个线程对取消请求做出响应

进程终止的状态

main()中,return的返回值即终止状态,如果没有return语句或调用exit(),那么该进程的终止状态是0
在Shell中,查看进程的终止状态:

echo &?

正常终止进程的三个函数:

  • exit()
  • _Exit()
  • _exit()

其中,前两个是ISO C说明的,_exit()是POSIX说明的:

void exit(int status);
void _exit(int status);
void _Exit(int status);

status即为进程终止的状态。
如果进程不是正常终止,打印的终止状态为非0

调用可执行程序

Linuz提供了system()和exec()函数族,在C++程序中,可以执行其他的程序(二进制文件,操作系统命令或Shell脚本)

system()

system()提供了一种简单的执行程序的方法,需要使用头文件stdlib.h,把需要执行的程序和参数用一个字符串传给system()就行了。

int system(const char* string);

system()的返回值比较麻烦:

  • 如果函数执行失败,system()返回值非0
  • 如果程序执行成功,并且被执行的程序终止状态是0,此函数的返回值即为0
注意事项

在使用此函数的时候,传递的参数最好使用全路径,这样可以避免环境变量的问题

exec函数族

exec函数族提供了另一种在进程中调用程序(可执行文件或Shell脚本)的办法:文章来源地址https://www.toymoban.com/news/detail-809050.html

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 execcv(const char* path, char* const argv[]);
int execvp(const char* file, char* const argv[]);
int execvpr(const char* file, char* const argv[], char* const envp[]);

到了这里,关于C++Linux网络编程基础的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【网络编程】Linux网络编程基础与实战第三弹——网络名词术语

    数据包从源地址到目的地址所经过的路径,由一系列路由节点组成。 某个路由节点为数据包选择投递方向的选路过程。 路由器工作原理 路由器是连接因特网中各局域网、广域网的设备,它会根据信道的情况自动选择和设定路由,以最佳路径,按前后顺序发送信号的设备。

    2024年02月08日
    浏览(45)
  • C++Linux网络编程基础

    当动态库和静态库同时存在的时候,会优先使用动态库 。 静态库 1. 制作静态库 -c表示只编译,-o则是说明需要指定文件名 2. 使用静态库 3. 库文件的概念 程序在编译时,会将库文件的二进制代码链接到目标程序中,这种方式称为 静态编译 。 如果多个程序中用到了同一个静

    2024年01月20日
    浏览(52)
  • C语言网络编程基础(linux)

    在linux操作系统下,有万物皆文件的概念,当一个进程想要打开/创建一个文件时,内核会给进程返回一个文件描述符,文件描述符是一个非负数,常用int类型表示,起到索引的作用,是为了高效管理进程打开/创建的文件的,指向的是被打开的文件。所有I/O的系统操作也都是通

    2024年02月07日
    浏览(45)
  • 【Linux】网络基础+UDP网络套接字编程

    只做自己喜欢做的事情,不被社会和时代裹挟着前进,是一件很奢侈的事。 1. 首先计算机是人类设计出来提高生产力的工具,而人类的文明绵延至今一定离不开人类之间互相的协作,既然人类需要协作以完成更为复杂的工作和难题,所以计算机作为人类的工具自然也一定需要

    2024年02月08日
    浏览(62)
  • Linux高性能服务器编程 学习笔记 第五章 Linux网络编程基础API

    我们将从以下3方面讨论Linux网络API: 1.socket地址API。socket最开始的含义是一个IP地址和端口对(ip,port),它唯一表示了使用TCP通信的一端,本书称其为socket地址。 2.socket基础API。socket的主要API都定义在sys/socket.h头文件中,包括创建socket、命名socket、监听socket、接受连接、发

    2024年02月07日
    浏览(55)
  • 多人聊天室(带私聊功能)Linux网络编程基础

    在和同学一起努力下终于完成了期末作业哈哈哈哈 文章目录 目录 前言 一、需求分析 二、功能设计 1.服务器端: 2.客户端: 三、流程图: 编程流程图: 服务器流程图: 客户端流程图: 四、运行效果: 项目源码: 服务器源码 客户端源码: 总结: Linux网络编程是我们这学

    2024年02月09日
    浏览(57)
  • 《3.linux应用编程和网络编程-第8部分-3.8.网络基础》 3.8.1.网络通信概述 3.8.3.网络通信基础知识2

        进程间通信: 管道 、 信号量、 共享内存, 技术多,操作麻烦     线程就是解决 进程间 通信 麻烦的事情,这是线程的 优势 3.8.1.网络通信概述 3.8.1.1、从进程间通信说起: 网络域套接字socket , 网络通信其实就是位于网络中不同主机上面                   的 

    2024年02月15日
    浏览(56)
  • Linux C++ 网络编程基础(2) : TCP多线程一个server对应多个client

    作者:令狐掌门 技术交流QQ群:675120140 csdn博客:https://mingshiqiang.blog.csdn.net/   tcp编程时, 一个server可以对应多个client, server端用多线程可以实现. linux下多线程可以使用POSIX的线程函数, 下面给出服务端和客户端的代码.   Linux POSIX线程库提供了一组函数来创建、管理和同步

    2024年02月13日
    浏览(48)
  • 【socket】从计算机网络基础到socket编程——Windows && Linux C语言 + Python实现(TCP+UDP)

    简单讲一下基础知识,便于后面代码的理解,建议大概浏览一下这一小节内容。这里讲的只是冰山一角,建议大家学习计算机网络相关知识,推荐几本书: 《计算机网络》(谢希仁) 《计算机网络 自顶向下方法》 《计算机网络技术》 《计算机网络基础及应用》 《Linux C从入

    2024年02月08日
    浏览(59)
  • Linux系统应用编程(五)Linux网络编程(上篇)

    1.两个网络模型和常见协议 (1)OSI七层模型(物数网传会表应) 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层(自下到上) (2)TCP/IP四层模型(网网传应) 网络接口层(链路层)、网络层、传输层、应用层 (3)常见网络协议所属层 2.字节序 (1)两种

    2023年04月25日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包