一、套接字错误处理函数的封装思想

在上篇文章中的 CS 模型,存在 bug:
先关 server,再关 client,立即再启动 server,此时会发现无法启动。原因是:先关 server,再关 client,执行 shell 命令netstat -apn | grep 端口号,会发现服务器处于
“TIME_WAIT”状态,即不是真正的退出状态,也就
意味着,原先 server 端口号并没有被释放掉,再次启动 server,因为端口号是固定的,而且正在被占用中,所以无法启动,问题出现在 bind 函数调用失败。
解决方法就是端口复用,代码为了突出逻辑性,并没有进行错误提示,这就导致使用者不知道错误的点,但如果进行错误提示,又会使代码的可读性变差,因此,采用错误处理函数封装。
错误处理函数封装本质是对库函数进行从新封装:
(1)完全按照库函数的原型重新封装函数,注意:函数名是原函数名首字母大写 。
(2)封装的函数体的内部实现添加错误处理,这样做的好处是使用时候跟原库函数没有区别(因为是按库函数进行封装的,调用时只需注意函数名即可);相较于使用原库函数,不需要自己进行错误判断,因为内部已经封装好了;利用 man 帮助文档时候,函数名不区分大小写,例如对于 man 命令,Socket 和 socket 是一样的,因此在 man_page 中可以直接查看封装后函数的使用方法。
使用方法:
对 server.c 和 client.c 中使用的函数,重新封装,一并放入 wrap.c 中(此外会有一个 wrap.h,对这些封装函数做出声明),通过联合编译实现对原库函数的功能扩展。
以server.c 为例:

gcc server.c wrap.c -o server

二、套接字错误处理函数的实现

wrap.h:

#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.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);
int tcp4bind(short port,const char *IP);
#endif

wrap.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
void perr_exit(const char *s) // 错误信息处理函数 // 这层封装的目的是为了减少下方代码量
{perror(s); // 输出错误信息exit(-1); // 退出进程
}int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) // 阻塞等待客户端连接函数
{int n; // 控制返回值again:if ((n = accept(fd, sa, salenptr)) < 0){if ((errno == ECONNABORTED) || (errno == EINTR))goto again; // 如果依据错误号,表示错误是“连接时异常断 开”或者“阻塞时被信号打断”else (这2个错误都会使accept调用失败),需要重新调用perr_exit("accept error");}return n;
}int Bind(int fd, const struct sockaddr *sa, socklen_t salen) // 绑定端口和IP
{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:if ( (n = read(fd, ptr, nbytes)) == -1) // > 0,读取的字节数, = 0,读到末尾(对端关闭),= -1 读异常{if (errno == EINTR) // 如果读阻塞时候被信号打断,恢复原读阻塞goto again;elsereturn -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;elsereturn -1; }return n;
}int Close(int fd) // 关闭套接字
{int n;if ((n = close(fd)) == -1)perr_exit("close error");return n;
}// 参数:文件描述符,存放读取数据的缓冲区的首地址(传出参数),要读多少字节(小于vptr长度)
// 返回值:成功 返回已读取的字节数 ;失败 :返回-1
ssize_t Readn(int fd, void *vptr, size_t n) // 功能:读取n个字节
{ size_t nleft; // usigned int 剩余未读取的字节数ssize_t nread; // int 实际读到的字节数char *ptr; // 指针,用于定位开始读的位置ptr = vptr; // 从缓存区头部开始存nleft = n; // 指定要读取的字节数while (nleft > 0) // 如果还有字节没有被读  一直读取{ if ((nread = read(fd, ptr, nleft)) < 0) // 如果发现读失败{ if (errno == EINTR)nread = 0; // 如果是因为信号打断造成的,认为没读到,但不退出(直接跳转到3)elsereturn -1; // 如果不是信号打断的,认为读异常,退出,返回-1} else if (nread == 0) // 如果发现全部读完(或对方关闭),退出循环,不再继续读break; // 从此处退出循环,意味着函数调用成功,返回n//  读行为正常nleft -= nread; // 更新剩余未读取的字节数ptr += nread; // 指针后移,定位在下次开始存储的位置}return n - nleft; // 返回已读取的字节数
}ssize_t Writen(int fd, const void *vptr, size_t n) // 功能:写入n个字节
{size_t nleft;ssize_t nwritten;const char *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ( (nwritten = write(fd, ptr, nleft)) <= 0) {if (nwritten < 0 && errno == EINTR)nwritten = 0; // 本质:忽略 信号打断造成的情况elsereturn -1;}nleft -= nwritten;ptr += nwritten; }return n;
}static ssize_t my_read(int fd, char *ptr) // Readline() 的子函数
{static int read_cnt; // read函数返回值,代表读到多少个字节static char *read_ptr;static char read_buf[100]; // 存放读取内容的缓存区if (read_cnt <= 0) {again:if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) // 1. 正常读,但是如果读失败{if (errno == EINTR)goto again;return -1; // 读取1异常 返回-1 }else if (read_cnt == 0) // 2. 正常读到结尾return 0; // 读取到末尾(对端关闭) 返回0read_ptr = read_buf; // 3. 既不是异常,也没读到结尾,read_ptr 指向 存储缓存区的首地址}read_cnt--; *ptr = *read_ptr++;return 1; // 读取成功 返回1
}// 参数:文件描述符,存放读取数据缓冲区的首地址,缓冲区的大小 // 返回值:成功 0; 失败 -1
ssize_t Readline(int fd, void *vptr, size_t maxlen) // 功能:读取一行
{ssize_t n, rc; // my_read 函数返回值char c, *ptr; // 定义my_read读取的字符 // 定义开始存储的位置ptr = vptr; // 最开始从缓存区头开始存储for (n = 1; n < maxlen; n++){if ( (rc = my_read(fd, &c)) == 1) // 如果读取1字节成功{ *ptr++ = c; // 将读取的结果,赋给ptr,同时ptr后移if (c == '\n')break; // 如果到达行末尾,退出if,意味着1行读取成功,补0,然后return}else if (rc == 0) { *ptr = 0; // 如果正常读到结尾,直接结尾补0return n - 1;}elsereturn -1; // 剩余情况就是读异常,返回-1 }*ptr = 0; // 最后位置补0return n;
}int tcp4bind(short port,const char *IP)
{struct sockaddr_in serv_addr;int lfd = Socket(AF_INET,SOCK_STREAM,0);bzero(&serv_addr,sizeof(serv_addr));if(IP == NULL) {//如果这样使用 0.0.0.0,任意 ip 将可以连接serv_addr.sin_addr.s_addr = INADDR_ANY; }else{if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){perror(IP); // 转换失败exit(1);}}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(port);Bind(lfd, (struct sockaddr *)&serv_addr,sizeof(serv_addr));return lfd;
}

套接字错误处理函数的封装思想及函数实现相关推荐

  1. JMeter中的HTTPS套接字错误

    Apache JMeter对启用SSL的应用程序执行性能和/或负载测试时,SSL套接字错误可能是经常遇到的麻烦,严重阻碍了您的测试工作.本文重点介绍如何通过相应地配置和调优JMeter来克服这些与连接 ...

  2. 手机与服务器协议失败,无法连接到服务器。 协议: POP3, 端口: 110, 安全(SSL): 否, 套接字错误: 10060, 错误号: 0x800CCC0E...

    无法连接到服务器. 协议: POP3, 端口: 110, 安全(SSL): 否, 套接字错误: 10060, 错误号: 0x800CCC0E0 珰甫安_UG52013.07.30浏览122次分享举报 ...

  3. 普通的PHP上传到云函数,php封装上传函数代码示例

    php封装上传函数代码示例 发布时间:2020-05-14 17:16:12 来源:亿速云 阅读:184 作者:Leah 今天小编就为大家带来一篇有关php封装上传函数的文章.小编觉得挺实用的,为此分 ...

  4. flashfxpl连接服务器失败,serv-u列表错误,套接字错误

    1.查看端口是否默认21 2.参数选项选择主动port 3.服务器防火墙端口打开40000-62235 4.serv-u服务器端ie在服务器本地服务器-关闭ie增强 5.ie添加信任站点 6.退出安全 ...

  5. 【UNIX网络编程(二)】基本TCP套接字编程函数

    基于TCP客户/server程序的套接字函数图例如以下: 运行网络I/O.一个进程必须做的第一件事就是调用socket函数.指定期望的通信协议类型. #include <sys/socket.h ...

  6. C语言socket connect()函数(初始化套接字上的连接)(未完)(如何测试socket是否已经断开,如何判断socket是否断开)

    参考文章:C网络编程socket之connect函数 需研究下这个函数超时多久才返回... 文章目录 项目中注释解释 man 2 文档解释 关于上面man 2 手册中所提到的connect()案例,在 ...

  7. 糖儿飞教你学C++ Socket网络编程——5.套接字编程步骤与函数

    TCP是一个面向连接的传输层协议,提供高可靠性的字节流传输服务,主要用于一次传输要交换大量报文的情形.为了维护传输的可靠性,TCP增加了许多开销:例如确认.流量控制.计时器以及连接管理等.TCP协议的 ...

  8. iOS - Socket 网络套接字

    1.Socket 套接字 所谓 Socket,通常称为 "套接字",网络应用程序通过套接字向网络发送请求或者应答网络请求.Socket 通常用于描述 IP 地址和端口,是应⽤层与 ...

  9. 套接字编程(VC_Win32)

    简介(源于维基) Berkeley套接字(也作BSD套接字应用程序接口)刚开始是4.2BSD Unix操作系统(于1983发布)的一套应用程序接口.然而,由于AT&T的专利保护着UNIX,所以 ...

最新文章

  1. 偏差是什么?一文读懂偏差
  2. linux系统reboot怎么退出,Linux系统肿么退出?
  3. Linux记录-普通用户下执行sudo xxx 找不到命令解决方案
  4. GitHub发布重大更新,关系到所有程序员!
  5. 25 进程同步之Event
  6. PHP命令空间namespace及use的用法实践总结
  7. JBoss Data Virtualization 6.1 Beta现在可用
  8. SpringBoot 结合 RabbitMQ 简单项目
  9. 选择多级分类_商用车齿轮油的选择与运用!!
  10. CentOS 7 多网卡绑定
  11. linux shell 脚本中变量截取
  12. Residual Attention Network--2017-【论文理解】
  13. R-CNN算法学习(步骤二:特征提取)
  14. 基于Springboot+MyBatisPlus+Vue前后端分离大学生毕业论文答辩系统
  15. HDU-4539 郑厂长系列故事——排兵布阵 状态压缩DP Or 最大团
  16. 外贸邮箱服务器设置,外贸邮客户端设置方法?
  17. 6-7 快速排序 (15 分)
  18. 用C语言写俄罗斯方块
  19. vue 多行输入框显示行号
  20. 攻城狮应该明白的浏览器工作原理~

热门文章

  1. 看雪Unicorn高级逆向与反混淆学习笔记
  2. 一文详解图对比学习(GNN+CL)的一般流程和最新研究趋势
  3. 直播预告 | 长文本知识抽取:基于语义分割的文档级三元组关系抽取
  4. 新赛题上线!2021CCF大数据与计算智能大赛全面开赛!
  5. 大咖力荐!图深度学习奠基性著作重磅上市 | 送书福利
  6. ACL 2020 | 基于不同硬件搜索更好的Transformer结构
  7. linux vnc的小黑点和鼠标不同步_vnc使用教程,在Linux中vnc使用教程的4个步骤
  8. HttpClient工具类
  9. Spring Security——根据请求Header[Accept]不同返回不同类型资源解决方案
  10. CG CTF WEB 层层递进