网络编程常用函数的封装 from 黑马程序员
1.socket通信函数的封装
#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
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;
}
代码中的知识点:
1.exit
- exit(0)表示程序正常退出;除了0之外,其他参数均代表程序异常退出,如:exit(1),exit(-1)。
- return与exit的区别:return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
2.perror
- C 库函数
void perror(const char *str)
把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格。 - 0表示标准输入。1表示标准输出。2标准错误。1和2都是默认是输出到屏幕。参考:C++和C中的输入输出总结、标准输入/标准输出/标准错误与重定向
3.Accept
函数中的(errno == ECONNABORTED) || (errno == EINTR)
,即问题:为什么信号中断时,就不能终止读取?
答:由于没有东西可以读时,就会阻塞在那里,但是却由于信号过来了,本进程要中断阻塞状态去处理别的事件。这时候此阻塞状态就会被打断。
所以我们应该让进程处理完中断信号后,继续处于待读数据的阻塞状态。
- ECONNABORTED:在TCP连接过程中,客户端意外关闭了连接。
- EINTR:在执行一个系统调用时,被一个信号中断。
2.黏包
黏包:接收到两个数据包,但是不清楚从哪开始是一个数据包,从哪开始是另一个数据包。
黏包解决方法:
- 约定好,一次发送固定的字节数
- 数据的结尾加一个标记
- 头部加上数据的大小
要实现上述功能,就有必要实现读取固定字节大小的函数,如下所示:
#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>
// 从fd中读取nbytes个字节,并用ptr指向
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;
}
// 向fd中写入nbytes个字节,并用ptr指向带写入数据
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;
}
/*应该读取固定的字节数数据*/
ssize_t Readn(int fd, void *vptr, size_t 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;
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 = 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(int fd, char *ptr)
{
static int read_cnt; // 静态的变量初始化为零
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) {
if (errno == EINTR)
goto again;
return -1;
} else if (read_cnt == 0) // 当read_cnt为零时,说明fd中无数据,只有一个头部——这说明客户端发送了关闭报文
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
// 读取一行,即遇到\n就结束
ssize_t Readline(int fd, void *vptr, size_t maxlen) // 读到的数据存储在vptr中
{
ssize_t n, rc;
char c, *ptr;
ptr = 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; // 最后加上个0
return n;
}