Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >2-UNIX网络编程-进阶学习前的基础知识储备

2-UNIX网络编程-进阶学习前的基础知识储备

作者头像
zoujunjie202
发布于 2022-07-27 07:41:49
发布于 2022-07-27 07:41:49
44800
代码可运行
举报
文章被收录于专栏:zoujunjie202zoujunjie202
运行总次数:0
代码可运行

诚实点,并不是假设读的人C语言基础很差,而事实是,自己的C语言基础实在太差,要补学基础知识之后才可以进行后续的学习,惭愧!

- C语言错误处理

C 语言不提供对错误处理的直接支持,而是以返回值的形式来表示错误。发生错误时,大多数的 C 或 UNIX 函数调用返回 1 或 NULL,同时会设置一个全局变量错误代码 errno,表示在函数调用期间发生了错误。errno.h 头文件中找到各种各样的错误代码,如下截图展示其中一小部分错误码及对应含义。

程序可以通过检查返回值决定采取哪种错误处理的动作。以下以封装socket函数为例,描述一个错误处理的常见手段。代码基于上一个章节的sever端代码来扩展:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// 错误处理有关的头文件
#include <errno.h>
#include <stdarg.h>        /* ANSI C header file */
#include <syslog.h>        /* for syslog() */

#define MAXLINE     4096
#define LISTENQ     1024

int daemon_proc; // 一个全局变量,用于控制应用日志是否输出到系统日志,本案例不使用系统日志,因此变量会保持为常量0.
// 4、打印错误日志
static void err_doit(int errnoflag, int level, const char *fmt, va_list ap){
    int     errno_save;
    long    n;
    char    buf[MAXLINE + 1];
    
    errno_save = errno; // 5、记录全局错误码
#ifdef    HAVE_VSNPRINTF
    vsnprintf(buf, MAXLINE, fmt, ap);/* safe */
#else
    vsprintf(buf, fmt, ap);/* not safe 按格式打印日志 */ // 本案例没有定义 HAVE_VSNPRINTF ,所以直接走这个函数
#endif
    n = strlen(buf);
    // 6、如果错误码存在,则获取错误码对应的描述
    if (errnoflag)
        snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save));
    strcat(buf, "\n");
    
    if (daemon_proc) {
        syslog(level, buf);//打印日志到系统日志,系统日志默认是/var/log/messages,具体知识点使用的时候再翻资料
    } else {
        fflush(stdout);        /* in case stdout and stderr are the same */
        fputs(buf, stderr);
        fflush(stderr);
    }
    return;
}

//3、系统调用发生致命错误时,往控制台打印日志并退出系统。【Tips】fmt参数可以是一个带格式的字符串
void err_sys(const char *fmt, ...){
    va_list ap;
    va_start(ap, fmt); // 用省略号指定参数列表时,用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束
    err_doit(1, LOG_ERR, fmt, ap);
    va_end(ap);
    exit(1); // 正常退出的代码是 0
}

// 2、socket包裹函数,命名为Socket,方便区分使用
int Socket(int family, int type, int protocol){
    int n;
    if ( (n = socket(family, type, protocol)) < 0)
        err_sys("socket error");
    return(n);
}

int main(int argc , char **argv ){
    int                 listenfd,connfd;
    ssize_t             n;
    char                buff[MAXLINE+1];
    struct sockaddr_in  servaddr;
    // socket
    listenfd = Socket(AF_INET, SOCK_STREAM, 0); // 1、替换为包裹函数
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(13);
    // bind
    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    // listen
    listen(listenfd,LISTENQ);
    for(;;){
        connfd = accept(listenfd,NULL,NULL);
        while( ( n = read(connfd,buff,MAXLINE)) > 0 ){
            printf("receive message from client. message = %s",buff);
            write(connfd,buff,strlen(buff));
            bzero(&buff,sizeof(buff));
        }
        close(connfd);
    }
}

- 项目文件组织

添加Socket包裹函数之后,单个文件的代码变多,而且代码的职责的各异,在进一步添加代码之前,把代码目录组织好调理会更清晰。如下截图,抽离了一个头文件global.h,预留一个main.c作为程序主入口,把包裹函数和error处理函数放到lib目录,server和client保留在app目录。

再贴一次全部的代码:

--global.h

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//
//  global.h
//
//ifndef 这段代码的作用是方式重复引入global.h文件
#ifndef global_h
#define global_h

#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// 错误处理有关的头文件
#include <errno.h>
#include <stdarg.h>        /* ANSI C header file */
#include <syslog.h>        /* for syslog() */

#define MAXLINE     4096
#define LISTENQ     1024

// socket 包裹函数
int     Socket(int, int, int);

// ** error 错误处理函数列表
void    err_sys(const char *, ...);

// app
void    start_client(void);
void    start_server(void);

#endif /* global_h */

--main.c

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "global.h"

int main(int argc, const char * argv[]){
    if(strcmp(argv[1],"start_server") == 0){
        start_server();
    }
    if(strcmp(argv[1],"start_client") == 0){
        start_client();
    }
}

--lib/wraper.c

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//
//  wraper.c
//  c
//
//  Created by Junjie Zou on 2019/7/31.
//  Copyright © 2019 Junjie Zou. All rights reserved.
//

#include "global.h"

// 2、socket包裹函数,命名为Socket,方便区分使用
int Socket(int family, int type, int protocol){
    int n;
    if ( (n = socket(family, type, protocol)) < 0)
        err_sys("socket error");
    return(n);
}

--lib/error.c

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//
//  error.c
//  c
//
//  Created by Junjie Zou on 2019/7/31.
//  Copyright © 2019 Junjie Zou. All rights reserved.
//

#include "global.h"

int daemon_proc; // 一个全局变量,用于控制应用日志是否输出到系统日志,本案例不使用系统日志,因此变量会保持为常量0.
// 4、打印错误日志
static void err_doit(int errnoflag, int level, const char *fmt, va_list ap){
    int     errno_save;
    long    n;
    char    buf[MAXLINE + 1];
    
    errno_save = errno; // 5、记录全局错误码
#ifdef    HAVE_VSNPRINTF
    vsnprintf(buf, MAXLINE, fmt, ap);/* safe */
#else
    vsprintf(buf, fmt, ap);/* not safe 按格式打印日志 */ // 本案例没有定义 HAVE_VSNPRINTF ,所以直接走这个函数
#endif
    n = strlen(buf);
    // 6、如果错误码存在,则获取错误码对应的描述
    if (errnoflag)
        snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save));
    strcat(buf, "\n");
    
    if (daemon_proc) {
        syslog(level, buf);//打印日志到系统日志,系统日志默认是/var/log/messages,具体知识点使用的时候再翻资料
    } else {
        fflush(stdout);        /* in case stdout and stderr are the same */
        fputs(buf, stderr);
        fflush(stderr);
    }
    return;
}

//3、系统调用发生致命错误时,往控制台打印日志并退出系统。【Tips】fmt参数可以是一个带格式的字符串
void err_sys(const char *fmt, ...){
    va_list ap;
    va_start(ap, fmt); // 用省略号指定参数列表时,用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束
    err_doit(1, LOG_ERR, fmt, ap);
    va_end(ap);
    exit(1); // 正常退出的代码是 0
}

--app/client.c

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "global.h"

void start_client(){
    int                 sockfd;
    ssize_t             n;
    char                sendline[MAXLINE+1] ,recvline[MAXLINE+1];
    struct sockaddr_in  servaddr;
    // socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(13);
    inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);
    // connect
    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
    while( fgets(sendline,MAXLINE,stdin) != NULL ){
        write(sockfd,sendline,strlen(sendline));
        bzero(&recvline,sizeof(recvline));
        if( (n = read(sockfd,recvline,MAXLINE) ) > 0 ){
            recvline[n] = 0 ;
            printf("receive message from server. message = %s",recvline);
            // fputs(recvline,stdout);
        }
        bzero(&sendline,sizeof(sendline));
    }
    close(sockfd);
    exit(0);
}

--app/server.c

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "global.h"

void start_server(){
    int                 listenfd,connfd;
    ssize_t             n;
    char                buff[MAXLINE+1];
    struct sockaddr_in  servaddr;
    // socket
    listenfd = Socket(AF_INET, SOCK_STREAM, 0); // 1、替换为包裹函数
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(13);
    // bind
    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    // listen
    listen(listenfd,LISTENQ);
    for(;;){
        connfd = accept(listenfd,NULL,NULL);
        while( ( n = read(connfd,buff,MAXLINE)) > 0 ){
            printf("receive message from client. message = %s",buff);
            write(connfd,buff,strlen(buff));
            bzero(&buff,sizeof(buff));
        }
        close(connfd);
    }
}

代码准备好之后,编译、链接得到可以执行文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 编译脚本
 gcc -I ./ -c app/server.c -o app/server.o
 gcc -I ./ -c app/client.c -o app/client.o
 gcc -I ./ -c lib/wraper.c -o lib/wraper.o
 gcc -I ./ -c lib/error.c -o lib/error.o
 gcc -I ./ -c main.c -o main.o
 gcc -o main app/server.o app/client.o lib/wraper.o lib/error.o main.o

示例

1)错误提示

要出现上面这种报错很简单,如下,修改以下Socket的协议类型即可。

2)正常输出

- 编写Makefile

关注回上面的编译脚本,首先需要逐个文件进行编译,然后链接所有的文件,项目文件很多的情况下编译工作将会非常繁琐,所以需要引入Makefile来协助编译、链接生成可执行文件。

编写第一个版本的makefile,结合上面执行过的编译脚本,这个makefile文件久比较好理解了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#文件名是makefile
#生成main,右边为目标,左边是所依赖项。(#gcc之前需要用Tab,不是空格,在vc中编辑的话不好输入,直接使用vi makefile命令进行编辑更方便)
main:lib/wraper.o lib/error.o app/server.o app/client.o main.o
  gcc -o main lib/wraper.o lib/error.o app/server.o app/client.o main.o
wraper.o:lib/wraper.c  global.h
  gcc -c lib/wraper.c -o lib/wraper.o -I./
error.o:lib/error.c  global.h
  gcc -c lib/error.c -o lib/error.o -I./
server.o:app/server.c  global.h
  gcc -c app/server.c -o app/server.o -I./
client.o:app/client.c  global.h
  gcc -c app/client.c -o app/client.o -I./
main.o:main.c global.h
  gcc -c main.c -o main.o -I./
#清理命令
clean:
  rm -f main.o lib/*.o app/*.o

本想找一个通用版的makefile文件的,但是发现有几项语法不大清晰,所以现在使用这个简陋版本先,留一个学习任务后面再补充。

- 完成server和client中其他函数的包裹

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

int Bind(int listenfd, const struct sockaddr *servaddr, socklen_t socklen){
    int n;
    if( (n = bind(listenfd, servaddr, socklen)) < 0 )
        err_sys("bind error");
    return (n);
}

int Listen(int listenfd, int length){
    int n ;
    if( (n = listen(listenfd , length ) ) < 0 )
        err_sys("Listen error");
    return (n);
}

int Accept(int listenfd, struct sockaddr * addr, socklen_t * length){
    int n ;
    if( (n = accept(listenfd,addr,length )) < 0 )
        err_sys("accept error");
    return (n);
}

int Connect(int fd, const struct sockaddr *sa, socklen_t salen){
    int n;
    if ( (n = connect(fd, sa, salen)) < 0)
        err_sys("connect error");
    return (n);
}

到现在为止,基础知识就准备到这里,花了不少时间,基础知识太薄弱,效率也不高。

下周继续进行网络编程的知识点:

1、改造为并发服务器

2、读写状态

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 zoujunjie202 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
2025最新卷积神经网络(CNN)详细介绍及其原理详解
本文详细介绍了卷积神经网络(CNN)的基础概念和工作原理,包括输入层、卷积层、池化层、全连接层和输出层的作用。通过举例和图解,阐述了CNN如何处理图像,提取特征,以及如何进行手写数字识别。此外,讨论了池化层的平移不变性和防止过拟合的重要性。 本文是关于卷积神经网络(CNN)技术教程,整体内容从基础概念到实际示例,逐层剖析 CNN 的各个组成部分与作用,并通过手写数字识别案例帮助大家更直观地理解其工作原理。
猫头虎
2025/06/08
1.5K0
2025最新卷积神经网络(CNN)详细介绍及其原理详解
资源 | 如何只用NumPy码一个神经网络
注:本文将包含大量用 Python 编写的代码片段。希望读起来不会太无聊。:)所有源代码都可以在作者的 GitHub 上找到。链接:https://github.com/SkalskiP/ILearnDeepLearning.py
机器之心
2018/12/05
4580
AI从入门到放弃:BP神经网络算法推导及代码实现笔记
作者 | @Aloys (腾讯员工,后台工程师) 本文授权转自腾讯的知乎专栏 ▌一. 前言: 作为AI入门小白,参考了一些文章,想记点笔记加深印象,发出来是给有需求的童鞋学习共勉,大神轻拍! 【毒鸡汤】:算法这东西,读完之后的状态多半是 --> “我是谁,我在哪?” 没事的,吭哧吭哧学总能学会,毕竟还有千千万万个算法等着你。 本文货很干,堪比沙哈拉大沙漠,自己挑的文章,含着泪也要读完! ▌二. 科普: 生物上的神经元就是接收四面八方的刺激(输入),然后做出反应(输出),给它一点☀️就灿烂。 仿生嘛,于是
用户1737318
2018/07/20
1.1K0
手工计算神经网络第三期:数据读取与完成训练
小伙伴们大家好呀~~用Numpy搭建神经网络,我们已经来到第三期了。第一期文摘菌教大家如何用Numpy搭建一个简单的神经网络,完成了前馈部分。第二期为大家带来了梯度下降相关的知识点。
大数据文摘
2019/06/10
6390
手工计算神经网络第三期:数据读取与完成训练
深度学习基础入门——神经网络
深度学习近几年来越来越火热,相信很多人都想加入到这个滚滚洪流当中。但深度学习具体要怎么入门呢,相信很多人还不知道。本节课老shi就带大家了解深度学习到底是什么(期待地搓手手.gif)要入门深度学习,首先得从基础的神经网络学起。感知机就是最简单的神经网络,如下图所示。
用户7569543
2020/07/17
1K0
深度学习与TensorFlow:实现卷积神经网络
在上一篇文章,我们介绍了CNN的一些基本概念和lenet神经网络的架构,今天这一篇文章我们就模仿lenet网络去微调,使其符合mnist数据集的要求,并且达到我们练手的目的. 因为mnist的数据集的
云时之间
2018/06/06
5950
太天才了,把感知机组装在一起是不是就是神经网络了?
上一篇文章当中我们讲了感知机,由于文章比较久了,估计很多同学没有看过,没有关系,可以点击下方传送门回去补课。
TechFlow-承志
2020/12/08
4490
太天才了,把感知机组装在一起是不是就是神经网络了?
吴恩达《神经网络与深度学习》精炼笔记(4)-- 浅层神经网络
上节课我们主要介绍了向量化、矩阵计算的方法和python编程的相关技巧。并以逻辑回归为例,将其算法流程包括梯度下降转换为向量化的形式,从而大大提高了程序运算速度。本节课我们将从浅层神经网络入手,开始真正的神经网络模型的学习。
红色石头
2022/01/12
4120
吴恩达《神经网络与深度学习》精炼笔记(4)-- 浅层神经网络
Coursera吴恩达《神经网络与深度学习》课程笔记(4)-- 浅层神经网络
上节课我们主要介绍了向量化、矩阵计算的方法和python编程的相关技巧。并以逻辑回归为例,将其算法流程包括梯度下降转换为向量化的形式,从而大大提高了程序运算速度。本节课我们将从浅层神经网络入手,开始真
红色石头
2017/12/28
1.2K0
Coursera吴恩达《神经网络与深度学习》课程笔记(4)-- 浅层神经网络
人工神经网络ANN
该文介绍了如何使用深度学习实现图像分类。首先介绍了传统神经网络和深度学习的基本概念,然后介绍了如何使用深度学习模型进行图像分类。文章还讨论了训练过程中的超参数设置,包括隐层数量、每层神经元数量以及激活函数选择等,并给出了一个具体的例子,使用MNIST数据集进行训练和测试。
企鹅号小编
2017/12/26
8840
人工神经网络ANN
机器学习的大局:用神经网络和TensorFlow分类文本
该文章介绍了如何使用深度学习将文本分类为三个类别。首先,作者介绍了神经网络的基础知识和架构,然后提供了实现文本分类的代码示例。文章使用了MNIST数据集进行训练和测试,并使用不同的超参数进行优化。最后,作者评估了模型的性能,并提供了示例输出。"
花落花飞去
2017/12/20
3.1K1
神经网络编程 - 前向传播和后向传播(附完整代码)
【导读】本文的目的是深入分析深层神经网络,剖析神经网络的结构,并在此基础上解释重要概念,具体分为两部分:神经网络编程和应用。在神经网络编程部分,讲解了前向传播和反向传播的细节,包括初始化参数、激活函数
WZEARW
2018/04/16
1.5K0
神经网络编程 - 前向传播和后向传播(附完整代码)
人工智能:深层神经网络
对于人脸识别等应用,神经网络的第一层从原始图片中提取人脸的轮廓和边缘,每个神经元学习到不同边缘的信息;网络的第二层将第一层学得的边缘信息组合起来,形成人脸的一些局部的特征,例如眼睛、嘴巴等;后面的几层逐步将上一层的特征组合起来,形成人脸的模样。随着神经网络层数的增加,特征也从原来的边缘逐步扩展为人脸的整体,由整体到局部,由简单到复杂。层数越多,那么模型学习的效果也就越精确。
Lansonli
2021/10/09
2450
TensorFlow实现深层神经网络
深度神经网络(Deep Neural Networks,DNN)可以理解为有很多隐藏层的神经网络,又被称为深度前馈网络,多层感知机。
SimpleAstronaut
2022/08/09
4170
【TensorFlow篇】--DNN初始和应用
正向传播:在开始的每一层上都有一个参数值w,初始的时候是随机的,前向带入的是每一个样本值。
LhWorld哥陪你聊算法
2018/09/13
8570
【TensorFlow篇】--DNN初始和应用
李理:卷积神经网络之Dropout
本系列文章面向深度学习研发者,希望通过 Image Caption Generation,一个有意思的具体任务,深入浅出地介绍深度学习的知识。本系列文章涉及到很多深度学习流行的模型,如CNN,RNN/LSTM,Attention等。本文为第10篇。 作者:李理 目前就职于环信,即时通讯云平台和全媒体智能客服平台,在环信从事智能客服和智能机器人相关工作,致力于用深度学习来提高智能机器人的性能。 上文介绍了Batch Normalization技术。Batch Normalization是加速训练收敛速度的非
用户1737318
2018/06/06
1.7K0
深度学习从小白到入门 —— 基于keras的深度学习基本概念讲解
神经网络中的每个神经元 对其所有的输入进行加权求和,并添加一个被称为偏置(bias) 的常数,然后通过一些非线性激活函数来反馈结果。
机械视角
2019/10/23
7110
深度学习从小白到入门 —— 基于keras的深度学习基本概念讲解
吴恩达-神经网络和深度学习( 第三周 浅层神经网络:)
学习使用前向传播和反向传播搭建出有一个隐藏层的神经网络。 hidden layer Neural Network ###3.1 神经网络概览 ###3.2 神经网络表示 双层神经网络(只有一个
双愚
2018/05/28
6220
Python BP神经网络实现
人工神经网络模型种类很多,其中根据网络内数据流向进行分类可以分为前馈网络、反馈网络和自组织网络。
用户7886150
2020/12/28
1.4K0
笔记 | 吴恩达Coursera Deep Learning学习笔记
作者:Lisa Song 微软总部云智能高级数据科学家,现居西雅图。具有多年机器学习和深度学习的应用经验,熟悉各种业务场景下机器学习和人工智能产品的需求分析、架构设计、算法开发和集成部署。 吴恩达Coursera Deep Learning学习笔记 1 (上) 【学习心得】 Coursera和deeplearning.ai合作的Deep Learning Specialization出得真是慢啊……现在只出了Course 1:Neural Networks and Deep Learning,之后还有
IT派
2018/03/28
7850
笔记 | 吴恩达Coursera Deep Learning学习笔记
推荐阅读
相关推荐
2025最新卷积神经网络(CNN)详细介绍及其原理详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档