服务器、客户端通信套接字代码超详解(错误处理函数封装后)

这篇文章是在上篇的基础上进行封装

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;
}

客户端的文件不需要改动。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值