这篇文章是在上篇的基础上进行封装
https://blog.csdn.net/weixin_45479946/article/details/108485924
1.到达上次的服务器的目录之下
2.> server.c (>+文件名可清空文件内容而不删除文件)
3.把下面服务器修改包装后的代码放进去
4.ls -a 显示所有的隐藏文件
错误封装函数其实很简单,就是重新建立两个文件wrap.c和wrap.h。
然后把所有的函数重新包装一下,在原函数的基础上添加上错误警告即可。其中的注意事项都在代码里面里了。
wrap.h文件
#ifndef __WRAP_H_
#define __WRAP_H_ //防止重复定义
#include<stdio.h>//输入输出
#include<stdlib.h>//动态内存分配
#include<string.h>
//linux下有这个头文件,vc下没有
//它包含很多UNIX系统服务的函数原型,eg:read write函数等
#include<unistd.h>
#include<errno.h>
#include<pthread.h>//包含有关多线程的函数
#include <sys/socket.h>//套接字函数、bind函数头文件
#include<ctype.h>//转小写转大写函数的头文件,在第二个地址结构的时候用
#include<arpa/inet.h>//bind的头文件
void perr_exit(const char *s);
int Socket(int family, int type, int protocol);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
//int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
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);
#endif
wrap.c文件
//#include <stdlib.h>
//#include <errno.h>
//#include <sys/socket.h>
#include"wrap.h"
void perr_exit(const char *s)
{
perror(s);
exit(1);
}
//void sys_err(const char *str)
//{
// //perror是用来输出错误的,如果某些函数调用不正确的话,调用perror会先输出错误号,然后输出你在perror()参数中指定的内容
// perror(str);//包含在stdlib.h这个头文件中
// exit(1);
//}
//man socket 这个指令就找到了socket函数的原型
int Socket(int family, int type, int protocol)
{
int n;
if ( (n = socket(family, type, protocol)) < 0)
perr_exit("socket 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;
}
//!man 2 listen 这个指令就找到listen原始地
int Listen(int fd, int backlog)
{
int n;
if ((n = listen(fd, backlog)) < 0)
perr_exit("listen error");
return n;
}
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
perr_exit("accept error");
}
return n;
}
//32位系统:typedef unsigned int size_t;64位系统:typedef unsigned long size_t;
//ssize_t:表示signed size_t,即有符号的整型
//注意读写错误的时候是返回-1,还不是上面的输出一个错误!
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ( (n = read(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
//注意读写错误的时候是返回-1,还不是上面的输出一个错误!
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;
}
服务器文件:
#include"wrap.h"
#define SERV_PORT 9527 //bind函数中的端口号
char buf[BUFSIZ], clint_IP[1024];//用来保存读到的客户端的数组,BUFSIZ默认4096
//上面第二个数组用来接收客户端IP地址
//void sys_err(const char *str)
//{
// //perror是用来输出错误的,如果某些函数调用不正确的话,调用perror会先输出错误号,然后输出你在perror()参数中指定的内容
// perror(str);//包含在stdlib.h这个头文件中
// exit(1);
//}
int main(int argc, char *argv[])
{
int ifd =0,cfd=0,ret;//保存文件描述符,用于后期和服务器建立连接
socklen_t clit_addr_len;
//定义bind函数中用到的ip地址结构体
struct sockaddr_in serv_addr, clit_addr;
serv_addr.sin_family = AF_INET;//协议簇地址,和第三个参数的区别在于,这个是地址类型,第三个是有效实际地址
//TCP/IP是一个网络通讯协bai议群,它包含了很多通信协议。这些协du议制定了网zhi络设备、计算机连入网络以及数据是如何dao在它们之间进行传输的标准。
//TCP协议又叫传输控制协议,作用于传输层。IP协议又叫网络间互联协议,工作在网络层。它们都是TCP/IP协议簇中非常重要的协议。
serv_addr.sin_port = htons(SERV_PORT);//端口这里需要字节序的转换!需要来回传的都需要转换!
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);//取出系统中任意有效的IP地址
//sin_family和sin_addr.s_addr的地址有什么区别?
//socket目的:创建一个套接字
//第一个参数是个地址,IPv4的地址。用来产生套接字ip地址的协议
//第二个参数是个协议,数据传输协议
//第三个参数是:你所选用的数据传输协议的代表协议,比如流式数据传输协议(SOCK_STREAM)的代表协议是tcp
//成功:返回指向新创建的socket的文件描述符,失败:返回-1,
ifd = Socket(AF_INET, SOCK_STREAM, 0);
/*if(ifd==-1)
{
sys_err("socket error");
}*/
//bind作用:将socket和bind的第二个参数中的地址、端口号捆绑起来,以便监听
//第一个参数套接字描述符、 第二个参数是个结构体指针,里面包含服务器的IP地址+端口号、第三个参数是这个地址的长度
Bind(ifd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));//和上面返回描述符不同,这个返回的是0
Listen(ifd, 128);//第二个参数是它所能监听的最大上限数
clit_addr_len=sizeof(serv_addr);
//第二个参数是接收链接客户端地址信息,含IP地址和端口号,所以前面不需要专门定义
//返回一个套接字
cfd=Accept(ifd, (struct sockaddr*)&clit_addr ,&clit_addr_len);//注意第三个参数类型,相比第bind多一个指针
//把接收到的客户端IP和端口号打印出来,都在上面的clit_addr里面
//inet_ntop目的是将二进制转换为10进制输出,第二个参数是客户的IP地址,第三个参数是用来储存转换后IP地址的数组,第四个是长度
printf("clint IP:%s port:%d\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,clint_IP,sizeof(clint_IP)),
ntohs(clit_addr.sin_port));
while(1)
{
ret=Read(cfd,buf,sizeof(buf));//返回字节数
Write(STDOUT_FILENO,buf,ret);标准输出到屏幕
for(int i=0; i<ret;i++)
{
buf[i]=toupper(buf[i]);
}
Write(cfd,buf,ret);//把buf的内容写回到客户端
}
Close(ifd);
Close(cfd);
return 0;
}
客户端的文件不需要改动。