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

服务器的代码:



#include<stdio.h> 
#include<ctype.h> 
#include<unistd.h> 
#include<sys/types.h> 
#include<arpa/inet.h> 
#include<sys/socket.h> 
#include<stdlib.h> 
  
#defineSERV_PORT//不能过大也不能过小 
  
intvoid) 
{ 
int//服务器的socket文件描述符 
int//客户端的socket文件描述符 
structsockaddr_in//服务器的地址结构体 
structsockaddr_in//客户端的地址结构体 
socklen_t//客户端的地址结构的大小 
charBUFSIZ]; //用于储存客户端发来的信息 
  
//第一个参数是指定IPV4协议族,第二个参数是指定TCP协议,第三个参数是使用默认的协议,一般都是用0 
ifAF_INET, SOCK_STREAM, 0)))//创建服务器的socket文件 
    { 
"socket error"); 
        exit(1); 
    } 
  
//赋值服务器地址结构体 
AF_INET; //选择协议族位IPV4 
SERV_PORT); //监听本地所有的IP地址;INADDR_ANY表示的是一个服务器上所有的网卡(服务器可能不止一个网卡)多个本地ip地址都进行绑定端口号,进行侦听。 
INADDR_ANY); //绑定我们自定义的端口号9527 
  
//第一个参数是服务器的socket文件描述符,第二个参数要强转为sockaddr,原因后面会说;第三个参数是服务器地址结构体的大小 
ifstructsockaddr*)&serv_addr, sizeof(serv_addr))))//绑定服务器地址结构体 
    { 
"bind error"); 
        exit(1); 
    } 
//服务器能够同时接受多少个客户端连接,默认128. 
if (-1 == (listen(sfd, 32))) 
    { 
"listen error"); 
        exit(1); 
    } 
  
//确定客户端地址结构大小,用于accept函数使用 
sizeof(clie_addr); 
  
//连接客户端,返回一个新的socket文件描述符。第一个参数时服务器socket文件描述符,第二个时客户端地址结构体,这里也要强转,第三个是客户端地址的大小,注意的是:第二个是传出参数,第三个 是传入传出参数 
ifstructsockaddr*)&clie_addr, &clie_len))) 
    { 
"accept error"); 
        exit(1); 
    } 
  
while (1) 
    { 
intsizeof(buf));//从客户端读 
forint i = 0; i < n; i++) 
            buf[i] = toupper(buf[i]); 
//写到客户端 
    } 
//关闭文件描述符 
    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> 
#defineADDR_POST 9527 
intvoid) 
{ 
int c_fd; 
int len; 
charBUFSIZ]; 
structsockaddr_in clie_addr; 
socklen_t addr_len; 
  
ifAF_INET, SOCK_STREAM, 0)) == -1) 
    { 
"socket error"); 
        exit(1); 
    } 
  
  
ifAF_INET, "127.0.0.1", &clie_addr.sin_addr.s_addr))) 
    { 
"inet_pton error"); 
        exit(1); 
    } 
AF_INET; 
ADDR_POST); 
  
ifstructsockaddr*)&clie_addr, sizeof(clie_addr)))) 
    { 
"connect error"); 
        exit(1); 
    } 
  
while (1) 
    { 
sizeof(buf), stdin); 
        write(c_fd, buf, strlen(buf)); 
sizeof(buf)); 
STDOUT_FILENO, buf, len); 
    } 
    close(c_fd); 
  
return 0; 
}



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

方法就是:封装!

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



#ifndef __WRAP_H_ 
#define__WRAP_H_ 
voidconstchars); 
intintfd, structsockaddrsa, socklen_tsalenptr); 
intintfd, conststructsockaddrsa, socklen_tsalen); 
intintfd, conststructsockaddrsa, socklen_tsalen); 
intintfd, intbacklog); 
intintfamily, inttype, intprotocol); 
ssize_tintfd, voidptr, size_tnbytes); 
ssize_tintfd, constvoidptr, size_tnbytes); 
intintfd); 
ssize_tintfd, voidvptr, size_tn); 
ssize_tintfd, constvoidvptr, size_tn); 
ssize_tintfd, charptr); 
ssize_tintfd, voidvptr, size_tmaxlen); 
#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> 
voidconstchars) 
{ 
s); 
    exit(1); 
} 
intintfd, structsockaddrsa, socklen_tsalenptr) 
{ 
int n; 
again: //accrpt是慢速系统调用,在阻塞期间可能会被信号杀死 
iffd, sa, salenptr)) < 0) { 
iferrnoECONNABORTED) || (errnoEINTR))//进一步判断返回值,EINTR代表函数被信号中断;ECONNABORTED代表连接中断,这两种情况不算是异常,所以重启 
goto again; 
else 
"accept error"); 
    } 
return n; 
} 
intintfd, conststructsockaddrsa, socklen_tsalen) 
{ 
int n; 
iffd, sa, salen)) < 0) 
"bind error"); 
return n; 
} 
intintfd, conststructsockaddrsa, socklen_tsalen) 
{ 
int n; 
iffd, sa, salen)) < 0) 
"connect error"); 
return n; 
} 
intintfd, intbacklog) 
{ 
int n; 
iffd, backlog)) < 0) 
"listen error"); 
return n; 
} 
intintfamily, inttype, intprotocol) 
{ 
int n; 
iffamily, type, protocol)) < 0) 
"socket error"); 
return n; 
} 
ssize_tintfd, voidptr, size_tnbytes) 
{ 
ssize_t n; 
again: //read也是慢速系统调用。 
iffd, ptr, nbytes)) == -1) { 
iferrnoEINTR) 
goto again; 
else 
return -1; 
    } 
return n; 
} 
ssize_tintfd, constvoidptr, size_tnbytes) 
{ 
ssize_t n; 
again: 
iffd, ptr, nbytes)) == -1) { 
iferrnoEINTR) 
goto again; 
else 
return -1; 
    } 
return n; 
} 
intintfd) 
{ 
int n; 
iffd)) == -1) 
"close error"); 
return n; 
} 
  
/* 
应用场景:以太网帧一次最多发送1500字节的数据,若是我们要读取4096字节的数据,但是4096字节的数据需要四次才能完全发送过来,如果只调用一次read,那就只能读到1500就返回不读了,所以我们需要让系统调用多次,必须读够那么多数据。所以这次调用,n要等于4096 
  
//参数三:应该读取到的字节数*/ 
ssize_tintfd, voidvptr, size_tn) 
{ 
size_t//unsigned int 剩余未读取的字节数 
ssize_t//int 实际读取到的字节数 
char *ptr; 
  
char*)vptr; 
n; //n 未读取到的字节数 
  
while (nleft > 0) { 
iffd, ptr, nleft)) < 0) { 
iferrnoEINTR)// EINTR(表"被信号中断") 
// 读到了0个字节 
else 
return//其他错误 
        } 
elseif// 文件读取完 
break; 
        nleft -= nread; 
        ptr += nread; 
    } 
returnn//返回实际读取到的字节数 
} 
  
ssize_tintfd, constvoidvptr, size_tn) 
{ 
size_t//剩余未写的字节数 
ssize_t//实际写的字节数 
constchar *ptr; 
  
char*)vptr; 
n; //未写的字节数 
  
while (nleft > 0) { 
iffd, ptr, nleft)) <= 0) { 
iferrnoEINTR) 
                nwritten = 0; 
else 
return -1; 
        } 
        nleft -= nwritten; 
        ptr += nwritten; 
    } 
returnn; 
} 
  
staticssize_tconstintfd, charptr) 
{ 
staticint read_cnt; 
staticchar *read_ptr; 
staticchar//读一次可以传出100字节,但是不是一次性传出100字节,实际上是一次传一个字符出去 
  
if (read_cnt <= 0) { 
    again: 
iffd, read_buf, sizeof(read_buf))) < 0) { 
iferrnoEINTR) 
goto again; 
return -1; 
        } 
elseif (read_cnt == 0) 
return 0; 
        read_ptr = read_buf; 
    } 
    read_cnt--; 
ptr = *read_ptr++; 
return 1; 
} 
/* 
应用场景:fgets只能从普通文件或者标准输入输出设备读去数据,他不能从socket中读取数据,所以用readline代替fgets来读取一行 
*/ 
ssize_tintfd, voidvptr, size_tmaxlen) 
{ 
ssize_t n, rc; 
char c, *ptr; 
char*)vptr; 
  
formaxlen; n++) { 
iffd, &c)) == 1) { 
            *ptr++ = c; 
if'\n') 
break; 
        } 
elseif (rc == 0) { 
            *ptr = 0; 
return n - 1; 
        } 
else 
return -1; 
    } 
    *ptr = 0; 
return//返回读到的字节数 
}



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