博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
socket05---recv && send使用,回射客户端
阅读量:4220 次
发布时间:2019-05-26

本文共 6525 字,大约阅读时间需要 21 分钟。

开始复习网络编程这一块,话说有一段时间了,那不多说了,开始吧!

这一节,我们学习的是recv和send函数的使用,如果对初始化socket,绑定,连接,write和read等基本操作不太熟悉的话,可以参考前几篇博文,还有网络字节序和本地字节序的转换,需要注意的点也不少,多写才能熟悉。

先介绍一下recv和send函数

recv函数

提供了和read一样的功能,不同的是它多了一个参数

ssize_t recv(int sockfd,void *buf,size_t len,int flags)

主要区别在第四个参数,前面的参数可以说是一样的

recv对应的flags有3个选项:    MSG_PEEK:查看数据,并不从系统缓冲区移走数据    MSG_WAITALL:等待所有数据,等到所有的信息到达时才返回,使用它时,recv返回一直阻塞,直到指定的条件满足时,或者发生错误    MSG_OOB:接受或者发送带外数据

send函数

同理,和write函数的功能一样,主要区别在于第四个参数:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
第四个参数flags,有2个选项:    MSG_DONTROUTE:不查找表,它告诉ip,目的主机在本地网络上,没必要查找表。(一般用在网络诊断和路由程序里面)    MSG_OOB:接受或者发送带外数据

总结一下:

这两个函数就是在基础的read和write上加上了第四个可供选择的参数,增加了一些扩展的功能。下面我们需要使用到recv的PEEK属性来封装一个读取一整行的函数readline,在写readline之前把还需要使用到的自己封装的readn和writen函数,为了更好地理解read和write的工作原理,我们还是使用自己写的功能一样的readn和writen而不直接用read和write。

readn和writen函数

ssize_t readn(int fd,void *buf,size_t count){    size_t nleft = count;    ssize_t nread;    char* bufp = (char*)buf;    while(nleft > 0)    {        if((nread = read(fd,bufp,nleft)) < 0)        {            if(errno == EINTR)                continue;            return -1;        }        else if(nread == 0)            return count-nleft;        bufp += nread;          //指针偏移,一般来说是直接偏完count字节,没有出错的话        nleft -= nread;         //这一步之前理解错了,只是使得跳出循环,而不是什么多次读取,一般我们这里一次读nleft    }    return count;}ssize_t writen(int fd,const void *buf,size_t count){    size_t nleft = count;    ssize_t nwritten;    char* bufp = (char*)buf;    while(nleft > 0)    {        if((nwritten = write(fd,bufp,nleft)) < 0)        {            if(errno == EINTR)                continue;            return -1;        }        else if(nwritten == 0)            continue;        bufp += nwritten;        nleft -= nwritten;       //同理,见上,仅仅是为了跳出循环,返回count    }    return count;}

封装的recv_peek和readline函数

recv_peek

//recv在read上增加了第四个参数,peek是不清空缓冲区ssize_t recv_peek(int sockfd,void *buf,size_t len){    while(1)    {        int ret = recv(sockfd,buf,len,MSG_PEEK);        if(ret == -1 && errno == EINTR)            continue;        return ret;    }}//这个函数的作用相当是先去瞟一眼缓冲区,看见有多少字符,返回这个字符数

readline

ssize_t readline(int sockfd,void *buf,size_t maxline){    int ret;    int nread;    char* bufp = buf;   //指针指向buf缓冲区    int nleft = maxline;    while(1)    {        ret = recv_peek(sockfd,bufp,nleft);        if(ret < 0)            return ret;        else if(ret == 0)            return ret;      //ret==0,当按下ctrl+d退出时候会发送一个信号终止,没写入字符时按下enter换行,会将换行符发送过去(这个发送过程后面会重点提到),下面for循环中的readn会读取换行符        nread = ret;        int i;        for(i = 0;i
nleft) exit(EXIT_FAILURE); nleft -= nread; ret = readn(sockfd,bufp,nread); if(ret != nread) exit(EXIT_FAILURE); bufp += nread; } return -1;}

上面的这两段代码在server和client段均需要包含,因为接受和发送数据的函数调用是一样的

这时候,我们把server端的代码补全一下:

封装一个do_service(int sockfd)

void do_service(int conn){    char recvbuf[1024];      while(1)    {        memset(recvbuf,0,sizeof(recvbuf));        int ret = readline(conn,recvbuf,sizeof(recvbuf));         if(ret == -1)            ERR_EXIT("readline");        else if(ret == 0)    //ret==0表示啥都接收不到        {            printf("client close,原因可能是关闭了,也可能是连接断了\n");            break;        }        fputs(recvbuf,stdout);  //向标准输出打印读到的recvbuf        writen(conn,recvbuf,strlen(recvbuf));  //向客户端回射,说明server已经接收到你的信息了    }}

main函数:暂时还是使用多进程的方式

int main(void){    int listenfd;     if( (listenfd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) <0 )        ERR_EXIT("socket");    struct sockaddr_in servaddr;   //ipv4的地址家族    memset(&servaddr , 0 , sizeof(servaddr));     servaddr.sin_family = AF_INET; //暂时我们把AF_INET和PF_INET看成一样,细微的区别    servaddr.sin_port = htons(5188);  //指定port为5188,并且将其转换为网络字节序,一个整形占2个字节,用s(hort)    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY表示使用本机任意地址    int on = 1;    //解决关闭服务器立即重启时候的需要等待TIME_WAIT消失过程,除此之外,还有很多改善socket健壮性的选项,这里暂不讨论    if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0)        ERR_EXIT("setsockopt");    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)        ERR_EXIT("bind");    if(listen(listenfd,SOMAXCONN) < 0)        ERR_EXIT("listen");    struct sockaddr_in peeraddr;    socklen_t peerlen = sizeof(peeraddr);    int conn;    pid_t pid;    while(1)        {        if((conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen)) < 0)            ERR_EXIT("accept");        printf("ip地址是:%s \t 端口是:%d \n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));        pid = fork();        if(pid == -1)            ERR_EXIT("fork");        if(pid == 0) //fork这个函数很特别(一次执行返回两个值,后续深入学习下这个函数),创建成功时它向子进程返回0        {            close(listenfd);    //关闭主进程的listenfd            do_service(conn);  //操作连接的conn套接字,读取数据或是回射数据,见上            exit(EXIT_SUCCESS); //跳出了上面的循环,那就意味着连接关闭了        }        else            close(conn);   //fork返回给父进程的是pid在正真系统中的唯一值,在父进程中和conn无关,直接关闭    }        return 0;

下面写客户端client.c:

直接写main函数

int main(){    int sock;    if((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)        ERR_EXIT("socket");    struct sockaddr_in servaddr;    memset(&servaddr,0,sizeof(servaddr));    servaddr.sin_family = AF_INET;    servaddr.sin_port = htons(5188);      servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");     if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)        ERR_EXIT("connect");    char sendbuf[1024] = {
0}; char recvbuf[1024] = {
0}; while(fgets(sendbuf,sizeof(sendbuf),stdin) != NULL) { writen(sock,sendbuf,strlen(sendbuf)); //注意这里用的strlen,只读实际上写入的数据长度(\n字符也是一个有效字符,在没有到底容量限制的时候, //比如我发送了1023个字节+\n,最后一个字节\n会被截断成为\0) //这样会导致一系列的问题,下一篇博文单独讨论,这里先实现通信功能即可 for(int i = 0;i < strlen(sendbuf);i++) { printf("%c\t",sendbuf[i]); } int ret = readline(sock,recvbuf,sizeof(recvbuf)); if(ret == -1) ERR_EXIT("readline"); else if(ret == 0) { printf("client close,原因可能关闭了.\n"); break; } fputs(recvbuf,stdout); //打印server回射过来的数据 memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } close(sock); return 0;}

最后给两个.c文件加上如下的头文件ERR_EXIT函数

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)

最后就能正常通信了,一个回射多个客户端的server和多个client端,运行截图如下:

这里写图片描述

下一节讨论在本章代码中需要注意的几点常见的误区,和bug。

所有代码都经测试,可用,如有错误,还请指出,谢谢了。

你可能感兴趣的文章
51 中断系统 外部中断0 外部中断1
查看>>
51 单片机 时间/计数器中断
查看>>
腾讯云本地还原mysql物理冷备
查看>>
算法图解 第1章 算法简介
查看>>
算法图解 第3章 递归
查看>>
Java反转整数
查看>>
解释 Zuul 的 zuul.strip-prefix 属性
查看>>
翻译 AbstractQueuedSynchronizer ( AQS )类注释
查看>>
HighCharts线型设定
查看>>
把win7 资源管理器的导航树改成xp的样式
查看>>
highcharts 内存泄露的解决
查看>>
blockUI 模态窗口
查看>>
网络通讯堵塞情况下的定时刷新
查看>>
手动将Apache注册为系统服务
查看>>
jdbc中Datetime与java.util.Date的相互转换
查看>>
hibernate中取得connection的方法
查看>>
如何使用log4j输出单个级别的log到指定文件
查看>>
表单元素与提示文字无法对齐的解决方法
查看>>
图片按钮消除边框
查看>>
关于汉字的正则表达式
查看>>