昨天和今天上午,我分别实现简单的服务器和客户端,运行之后表示没问题,一切正常。但是这还是有问题的,最大的一个就是没有错误检查。现在我们来加上错误检查:

服务器的代码:

#include
<stdio.h>

#include
<ctype.h>

#include
<unistd.h>

#include
<sys/types.h>

#include
<arpa/inet.h>

#include
<sys/socket.h>

#include
<stdlib.h>

 

#define
SERV_PORT 9527 //不能过大也不能过小

 

int main(void)

{

    int sfd; //服务器的socket文件描述符

    int cfd; //客户端的socket文件描述符

    struct
sockaddr_in serv_addr; //服务器的地址结构体

    struct
sockaddr_in clie_addr; //客户端的地址结构体

    socklen_t clie_len; //客户端的地址结构的大小

    char buf[BUFSIZ]; //用于储存客户端发来的信息

 

                                        //第一个参数是指定IPV4协议族,第二个参数是指定TCP协议,第三个参数是使用默认的协议,一般都是用0

    if (-1 == (sfd = socket(AF_INET, SOCK_STREAM, 0)))//创建服务器的socket文件

    {

        perror("socket error");

        exit(1);

    }

 

    //赋值服务器地址结构体

    serv_addr.sin_family = AF_INET; //选择协议族位IPV4

    serv_addr.sin_port = htons(SERV_PORT); //监听本地所有的IP地址;INADDR_ANY表示的是一个服务器上所有的网卡(服务器可能不止一个网卡)多个本地ip地址都进行绑定端口号,进行侦听。

    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //绑定我们自定义的端口号9527

 

                                                 //第一个参数是服务器的socket文件描述符,第二个参数要强转为sockaddr,原因后面会说;第三个参数是服务器地址结构体的大小

    if (-1 == (bind(sfd, (struct
sockaddr*)&serv_addr, sizeof(serv_addr))))//绑定服务器地址结构体

    {

        perror("bind error");

        exit(1);

    }

    //服务器能够同时接受多少个客户端连接,默认128.

    if (-1 == (listen(sfd, 32)))

    {

        perror("listen error");

        exit(1);

    }

 

    //确定客户端地址结构大小,用于accept函数使用

    clie_len = sizeof(clie_addr);

 

    //连接客户端,返回一个新的socket文件描述符。第一个参数时服务器socket文件描述符,第二个时客户端地址结构体,这里也要强转,第三个是客户端地址的大小,注意的是:第二个是传出参数,第三个 是传入传出参数

    if (-1 == (cfd = accept(sfd, (struct
sockaddr*)&clie_addr, &clie_len)))

    {

        perror("accept error");

        exit(1);

    }

 

    while (1)

    {

        int n = read(cfd, buf, sizeof(buf));//从客户端读

        for (int i = 0; i < n; i++)

            buf[i] = toupper(buf[i]);

        write(cfd, buf, n);//写到客户端

    }

    //关闭文件描述符

    close(sfd);

    close(cfd);

}

客户端的代码:

#include
<string.h>

#include
<sys/types.h>

#include
<sys/socket.h>

#include
<stdio.h>

#include
<stdlib.h>

#include
<unistd.h>

#include
<sys/stat.h>

#include
<arpa/inet.h>

#include
<netinet/in.h>

#define
ADDR_POST 9527

int main(void)

{

    int c_fd;

    int len;

    char buf[BUFSIZ];

    struct
sockaddr_in clie_addr;

    socklen_t addr_len;

 

    if ((c_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

    {

        perror("socket error");

        exit(1);

    }

 

 

    if (-1 == (inet_pton(AF_INET, "127.0.0.1", &clie_addr.sin_addr.s_addr)))

    {

        perror("inet_pton error");

        exit(1);

    }

    clie_addr.sin_family = AF_INET;

    clie_addr.sin_port = htons(ADDR_POST);

 

    if (-1 == (connect(c_fd, (struct
sockaddr*)&clie_addr, sizeof(clie_addr))))

    {

        perror("connect error");

        exit(1);

    }

 

    while (1)

    {

        fgets(buf, sizeof(buf), stdin);

        write(c_fd, buf, strlen(buf));

        len = read(c_fd, buf, sizeof(buf));

        write(STDOUT_FILENO, buf, len);

    }

    close(c_fd);

 

    return 0;

}

哇,好累的啊。这样检查效率好低,并且严重的扰乱了代码逻辑,并且还没检查完所有的函数,不行,得换方法了。

方法就是:封装!

    我们自己实现一个同名函数(首字母大小),在新函数中调用原来的系统函数。并且做错误处理。头文件:my_error.h

#ifndef __WRAP_H_

#define
__WRAP_H_

void perr_exit(const
char *s);

int Accept(int
fd, struct
sockaddr *sa, socklen_t *salenptr);

int Bind(int
fd, const
struct
sockaddr *sa, socklen_t
salen);

int Connect(int
fd, const
struct
sockaddr *sa, socklen_t
salen);

int Listen(int
fd, int
backlog);

int Socket(int
family, int
type, int
protocol);

ssize_t Read(int
fd, void *ptr, size_t
nbytes);

ssize_t Write(int
fd, const
void *ptr, size_t
nbytes);

int Close(int
fd);

ssize_t Readn(int
fd, void *vptr, size_t
n);

ssize_t Writen(int
fd, const
void *vptr, size_t
n);

ssize_t my_read(int
fd, char *ptr);

ssize_t Readline(int
fd, void *vptr, size_t
maxlen);

#endif

然后是实现:my_error.c

#include
<string.h>

#include
<sys/types.h>

#include
<sys/socket.h>

#include
<stdio.h>

#include
<stdlib.h>

#include
<unistd.h>

#include
<sys/stat.h>

#include
<arpa/inet.h>

#include
<netinet/in.h>

#include
<errno.h>

void perr_exit(const
char *s)

{

    perror(s);

    exit(1);

}

int Accept(int
fd, struct
sockaddr *sa, socklen_t *salenptr)

{

    int n;

again: //accrpt是慢速系统调用,在阻塞期间可能会被信号杀死

    if ((n = accept(fd, sa, salenptr)) < 0) {

        if ((errno == ECONNABORTED) || (errno == EINTR))//进一步判断返回值,EINTR代表函数被信号中断;ECONNABORTED代表连接中断,这两种情况不算是异常,所以重启

            goto again;

        else

            perr_exit("accept error");

    }

    return n;

}

int Bind(int
fd, const
struct
sockaddr *sa, socklen_t
salen)

{

    int n;

    if ((n = bind(fd, sa, salen)) < 0)

        perr_exit("bind error");

    return n;

}

int Connect(int
fd, const
struct
sockaddr *sa, socklen_t
salen)

{

    int n;

    if ((n = connect(fd, sa, salen)) < 0)

        perr_exit("connect error");

    return n;

}

int Listen(int
fd, int
backlog)

{

    int n;

    if ((n = listen(fd, backlog)) < 0)

        perr_exit("listen error");

    return n;

}

int Socket(int
family, int
type, int
protocol)

{

    int n;

    if ((n = socket(family, type, protocol)) < 0)

        perr_exit("socket error");

    return n;

}

ssize_t Read(int
fd, void *ptr, size_t
nbytes)

{

    ssize_t n;

again: //read也是慢速系统调用。

    if ((n = read(fd, ptr, nbytes)) == -1) {

        if (errno == EINTR)

            goto again;

        else

            return -1;

    }

    return n;

}

ssize_t Write(int
fd, const
void *ptr, size_t
nbytes)

{

    ssize_t n;

again:

    if ((n = write(fd, ptr, nbytes)) == -1) {

        if (errno == EINTR)

            goto again;

        else

            return -1;

    }

    return n;

}

int Close(int
fd)

{

    int n;

    if ((n = close(fd)) == -1)

        perr_exit("close error");

    return n;

}

 

/*

应用场景:以太网帧一次最多发送1500字节的数据,若是我们要读取4096字节的数据,但是4096字节的数据需要四次才能完全发送过来,如果只调用一次read,那就只能读到1500就返回不读了,所以我们需要让系统调用多次,必须读够那么多数据。所以这次调用,n要等于4096

 

//参数三:应该读取到的字节数*/

ssize_t Readn(int
fd, void *vptr, size_t
n)

{

    size_t nleft; //unsigned int 剩余未读取的字节数

    ssize_t nread; //int 实际读取到的字节数

    char *ptr;

 

    ptr = (char*)vptr;

    nleft = n; //n 未读取到的字节数

 

    while (nleft > 0) {

        if ((nread = read(fd, ptr, nleft)) < 0) {

            if (errno == EINTR)// EINTR(表"被信号中断")

                nread = 0; // 读到了0个字节

            else

                return -1;//其他错误

        }

        else
if (nread == 0)// 文件读取完

            break;

        nleft -= nread;

        ptr += nread;

    }

    return
n - nleft;//返回实际读取到的字节数

}

 

ssize_t Writen(int
fd, const
void *vptr, size_t
n)

{

    size_t nleft; //剩余未写的字节数

    ssize_t nwritten; //实际写的字节数

    const
char *ptr;

 

    ptr = (char*)vptr;

    nleft = n; //未写的字节数

 

    while (nleft > 0) {

        if ((nwritten = write(fd, ptr, nleft)) <= 0) {

            if (nwritten < 0 && errno == EINTR)

                nwritten = 0;

            else

                return -1;

        }

        nleft -= nwritten;

        ptr += nwritten;

    }

    return
n;

}

 

static
ssize_t my_read(const
int
fd, char *ptr)

{

    static
int read_cnt;

    static
char *read_ptr;

    static
char read_buf[100];//读一次可以传出100字节,但是不是一次性传出100字节,实际上是一次传一个字符出去

 

    if (read_cnt <= 0) {

    again:

        if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {

            if (errno == EINTR)

                goto again;

            return -1;

        }

        else
if (read_cnt == 0)

            return 0;

        read_ptr = read_buf;

    }

    read_cnt--;

    *ptr = *read_ptr++;

    return 1;

}

/*

应用场景:fgets只能从普通文件或者标准输入输出设备读去数据,他不能从socket中读取数据,所以用readline代替fgets来读取一行

*/

ssize_t Readline(int
fd, void *vptr, size_t
maxlen)

{

    ssize_t n, rc;

    char c, *ptr;

    ptr = (char*)vptr;

 

    for (n = 1; n < maxlen; n++) {

        if ((rc = my_read(fd, &c)) == 1) {

            *ptr++ = c;

            if (c == '\n')

                break;

        }

        else
if (rc == 0) {

            *ptr = 0;

            return n - 1;

        }

        else

            return -1;

    }

    *ptr = 0;

    return n;//返回读到的字节数

}

在这里面。尤其要引起重视的是最后四个函数。在以后的开发中是很有可能用到的。

Linux:服务器/客户端API调用错误检查的更多相关文章

  1. 通过 Jersey Http请求头,Http响应头,客户端 API 调用 REST 风格的 Web 服务

    原地址:http://blog.csdn.net/li575098618/article/details/47853263 Jersey 1.0 是一个开源的.可以用于生产环境的 JAX-RS(RES ...

  2. Jersey客户端API调用REST风格的Web服务

    Jersey 客户端 API 基础 jersey-1.14.jar 密码: cxug 要开始使用 Jersey 客户端 API,你首先需要创建一个 com.sun.jersey .api.client ...

  3. This server is in the failed servers list: localhost/127.0.0.1:16000 启动hbase api调用错误

    api 调用发现错误 Mon Nov 18 23:04:31 CST 2019, RpcRetryingCaller{globalStartTime=1574089469858, pause=100, ...

  4. JAVA客户端API调用memcached两种方式

    1. memcached client for java客户端API:memcached client for java 引入jar包:java-memcached-2.6.2.jar package ...

  5. 通过Jersey客户端API调用REST风格的Web服务

    Jersey 客户端 API 基础 要开始使用 Jersey 客户端 API,你首先需要创建一个 com.sun.jersey .api.client.Client 类的实例.下面是最简单的方法: i ...

  6. linux服务器部署svn常见错误处理→转载

    转载地址→http://blog.seweal.com/post/2013-02-04/svn-errors [开放svn端口] iptables -I INPUT -p tcp --dport 36 ...

  7. Zookeeper 客户端API调用示例(基本使用,增删改查znode数据,监听znode,其它案例,其它网络参考资料)

    9.1 基本使用 org.apache.zookeeper.Zookeeper是客户端入口主类,负责建立与server的会话 它提供以下几类主要方法  : 功能 描述 create 在本地目录树中创建 ...

  8. linux 服务器/客户端 tcp通信的简单例子

    昨天弄了sublime之后没有弄输入中文的了,学生党来着,刚好可以练练英语(英语渣渣,还要考六级),所以注释都写英文的(语法什么的就别太深究了) 服务器端: /*start from the very ...

  9. Linux服务器内存监控—每小时检查&超出发送邮件&重启占用最高的Java程式

    简介与优点 使用该脚本能自行判断系统内存使用情况是否超出设定百分比 能在超出预警值时执行重启程式的操作 能记录重启过程,并将具体LOG邮件发送给指定收信人 可以设定Crontab排程,达成每隔一段时间 ...

随机推荐

  1. DevOps安装、部署持续集成

    1.重启docker服务,开启iptables转发功能 # systemctl start docker # vi /etc/sysctl.conf # sysctl -p [root@localho ...

  2. docker容器使用

    查看容器的配置信息 # docker inspect dc4e2ff3eb58 查看容器的网络信息 # docker inspect -f {{.NetworkSettings}} node4 [ro ...

  3. Jmeter(四)NO-GUI模式运行

    在前几篇中有提到NO-GUI模式的运行,是的,Jmeter支持NO-GUI方式的运行. 如果Jmeter的环境搭建完毕,那么在命令行下执行jmeter - ?便会出现jmeter的各个参数 --? p ...

  4. 常用docker镜像

    oracle12c: mkdir -p /path/to/oradata docker run --name oracle12c \ -p 1521:1521 -p 5500:5500 \ -v /p ...

  5. object视频播放

    param name标签是在这个播放插件中嵌入的一些功能和播放参数: <param name="playcount" value="1"><! ...

  6. spark使用hadoop native库

    默认情况下,hadoop官方发布的二进制包是不包含native库的,native库是用C++实现的,用于进行一些CPU密集型计算,如压缩.比如apache kylin在进行预计算时为了减少预计算的数据 ...

  7. Linux Kafka源码环境搭建

    本文主要讲述的是如何搭建Kafka的源码环境,主要针对的Linux操作系统下IntelliJ IDEA编译器,其余操作系统或者IDE可以类推. 1.安装和配置JDK确认JDK版本至少为1.7,最好是1 ...

  8. C# 中使用锁防止多线程冲突

    在编程的时候经常会用到多线程,有时候如果多线程操作同一个资源就会导致冲突,.NET提供了多种方法来防止冲突发生,这里讲下Mutex 该类位于System.Threading命名空间,常用的方式是这样: ...

  9. Linux打开TCP BBR拥塞控制算法

    要求内核为4.9以上,如果不是,请升级内核. modprobe tcp_bbr echo "tcp_bbr" >> /etc/modules-load.d/module ...

  10. (转)C# BackgroundWorker组件的原理分析

    原文地址:http://www.cnblogs.com/chaosimple/archive/2012/11/30/2796069.html 主要属性: 1.CancellationPending 获 ...