码迷,mamicode.com
首页 > 其他好文 > 详细

Socket编程实践(7)   --TCP粘包解决方法2

时间:2014-12-05 12:46:02      阅读:276      评论:0      收藏:0      [点我收藏+]

标签:socket   linux   tcp   cc++   异常处理   

包尾加\n编程实践

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

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

与read相比,只能用于套接字文件描述符,而且多了一个flags


Flags常用取值:

MSG_OOB(紧急指针,带外数据)

 This flag requests receipt of out-of-band data that would not be received  in the normal data stream.  Some protocols place expedited data at the head of the normal data queue, and  thus  this flag cannot be used with such protocols.

 

MSG_PEEK(可以读数据,不从缓存区中读走,利用此特点可以方便的实现按行读取数据;一个一个字符的读,方法不好;多次调用系统调用read方法)

 This  flag  causes the receive operation to return data from the beginning of the receive queue without removing that  data  from the queue.  Thus, a subsequent receive call will return the same data.

 

1.原客户端代码分析

  ....
    //从键盘输入数据:调用fgets函数,行尾的/n是默认自带的,请参看下图gdb调试的截图
    while (fgets(sendBuf.m_text,sizeof(sendBuf.m_text),stdin) != NULL)
    {
        //保存的是真实报文的长度
        sendBuf.m_length = strlen(sendBuf.m_text);
        //向server发送数据....+4的原因:需要添加报首的4个字节报头的长度
        if (writen(sockfd,&sendBuf,sendBuf.m_length+4) == -1)
        {
            err_exit("write socket error");
        }
.....
bubuko.com,布布扣

2.readline函数实现及解析

//只是查看一下网络中的数据,并不是将之真正取走:MSG_PEEK
ssize_t recv_peek(int fd, void *buf, size_t count)
{
    int nRead = 0;
    //如果读取网络数据出错,则继续读取
    while ((nRead = recv(fd,buf,count,MSG_PEEK)) == -1);
    return nRead;
}

ssize_t readline(int fd, void *buf, size_t maxline)
{
    char *pBuf = (char *)buf;
    int nLeft = maxline;

    while (true)
    {
        //查看缓冲区中的数据,并不真正取走
        int nTestRead = recv_peek(fd,pBuf,nLeft);

        //检测这次读来的数据中是否包含‘\n‘;
        //如果有,则将之全部读取出来
        for (int i = 0; i < nTestRead; ++i)
        {
            if (pBuf[i] == ‘\n‘)
            {
                //真正的从缓冲区中将数据取走
                if (readn(fd,pBuf,i+1) != i+1)
                {
                    err_exit("readn error");
                }
                else
                {
                    return i + 1;
                }
            }
        }

        //如果这次读的缓冲区中没有‘\n‘

        //如果读超了:读道德数目大于一行最大数,则做异常处理
        if (nTestRead > nLeft)
        {
            exit(EXIT_FAILURE);
        }

        nLeft -= nTestRead; //若缓冲区没有‘\n‘,则将剩余的数据读走
        if (readn(fd,pBuf,nTestRead) != nTestRead)
        {
            exit(EXIT_FAILURE);
        }

        pBuf += nTestRead;
    }

    return -1;
}

3.server完整代码及解析

#include "commen.h"

//echo 服务器readline版
int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if (sockfd == -1)
    {
        err_exit("socket error");
    }

    //添加地址复用
    int optval = 1;
    if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == -1)
    {
        err_exit("setsockopt SO_REUSEADDR error");
    }

    //绑定
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8002);
    serverAddr.sin_addr.s_addr = INADDR_ANY;    //绑定本机的任意一个IP地址
    if (bind(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1)
    {
        err_exit("bind error");
    }

    //启动监听套接字
    if (listen(sockfd,SOMAXCONN) == -1)
    {
        err_exit("listen error");
    }

    struct sockaddr_in peerAddr;
    socklen_t peerLen = sizeof(peerAddr);

    while (true)
    {
        //接受链接
        int peerSockfd = accept(sockfd, (struct sockaddr *)&peerAddr,&peerLen);
        if (peerSockfd == -1)
        {
            err_exit("accept error");
        }

        //打印客户信息
        cout << "Client:" << endl;
        cout << "\tsin_port: " << ntohs(peerAddr.sin_port) << endl;
        cout << "\tsin_addr: " << inet_ntoa(peerAddr.sin_addr) << endl;
        cout << "\tsocket: " << peerSockfd << endl;

        //每有一个客户端连接进来,就fork一个子进程,
        //相应的业务处理由子进程完成,父进程继续监听
        pid_t pid = fork();
        if (pid == -1)
        {
            close(sockfd);
            close(peerSockfd);
            err_exit("fork error");
        }
        else if (pid == 0)  //子进程,处理业务
        {
            close(sockfd);  //子进程关闭监听套接字,因为子进程不负责监听任务

            char recvBuf[BUFSIZ];
            ssize_t readCount = 0;
            while (true)
            {
                memset(recvBuf,0,sizeof(recvBuf));

                //读取一行数据(会根据数据流中的\n而终止读取)
                if ((readCount = readline(peerSockfd,recvBuf,sizeof(recvBuf))) == -1)
                {
                    err_exit("readn error");
                }
                else if (readCount == 0)
                {
                    peerClosePrint("client connect closed");
                }

                //将整体报文回写回客户端
                if (writen(peerSockfd,recvBuf,strlen(recvBuf)) == -1)
                {
                    err_exit("writen error");
                }

                recvBuf[readCount] = 0;
                //写至终端
                fputs(recvBuf,stdout);
            }
        }
        else if (pid > 0)   //父进程
        {
            close(peerSockfd);
        }
    }

    close(sockfd);
    return 0;
}

4.新版client完整代码实现及解析

#include "commen.h"

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if (sockfd == -1)
    {
        err_exit("socket error");
    }

    //填写好服务器地址及其端口号
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8002);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (connect(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1)
    {
        err_exit("connect error");
    }

    int readCount = 0;
    char sendBuf[BUFSIZ];
    char recvBuf[BUFSIZ];

    //从键盘输入数据:调用fgets函数,行尾的/n是默认自带的,请参看下图gdb调试的截图
    while (fgets(sendBuf,sizeof(sendBuf),stdin) != NULL)
    {
        //向server发送数据(会自动附带\n)
        if (writen(sockfd,sendBuf,strlen(sendBuf)) == -1)
        {
            err_exit("write socket error");
        }

        //从server端接收一行数据
        if ((readCount = readline(sockfd,recvBuf,sizeof(recvBuf))) == -1)
        {
            err_exit("read socket error");
        }
        else if (readCount == 0)
        {
            peerClosePrint("client connect closed");
        }

        recvBuf[readCount] = 0;
        //将其回写到终端
        fputs(recvBuf,stdout);

        memset(sendBuf,0,sizeof(sendBuf));
        memset(recvBuf,0,sizeof(recvBuf));
    }

    close(sockfd);
    return 0;
}

1-commen.h代码及解析

#ifndef COMMEN_H_INCLUDED
#define COMMEN_H_INCLUDED

#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <sys/socket.h>

#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <iostream>
using namespace std;

void err_exit(std::string str)
{
    perror(str.c_str());
    exit(EXIT_FAILURE);
}
void peerClosePrint(std::string str = "peer connect closed")
{
    cout << str << endl;
    _exit(0);
}

ssize_t readn(int fd,void *buf,size_t count)
{
    size_t nLeft = count;
    ssize_t nRead = 0;

    char *ptr = static_cast<char *>(buf);

    while (nLeft > 0)
    {
        if ((nRead = read(fd,ptr,nLeft)) < 0)
        {
            //一点东西都没读
            if (nLeft == count)
            {
                return -1;  //error
            }
            else
            {
                break;  //error, return amount read so far
            }
        }
        else if (nRead == 0)
        {
            break;  //EOF
        }

        nLeft -= nRead;
        ptr += nRead;
    }

    return count - nLeft;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
    size_t nLeft = count;
    ssize_t nWritten;

    const char *ptr = static_cast<const char *>(buf);

    while (nLeft > 0)
    {
        if ((nWritten = write(fd,ptr,nLeft)) < 0)
        {
            //一点东西都没写
            if (nLeft == count)
            {
                return -1;  //error
            }
            else
            {
                break;  //error, return amount write so far
            }
        }
        else if (nWritten == 0)
        {
            break;  //EOF
        }

        nLeft -= nWritten;
        ptr += nWritten;
    }

    return count - nWritten;
}

//只是查看一下网络中的数据,并不是将之真正取走:MSG_PEEK
ssize_t recv_peek(int fd, void *buf, size_t count)
{
    int nRead = 0;
    //如果读取网络数据出错,则继续读取
    while ((nRead = recv(fd,buf,count,MSG_PEEK)) == -1);
    return nRead;
}

ssize_t readline(int fd, void *buf, size_t maxline)
{
    char *pBuf = (char *)buf;
    int nLeft = maxline;

    while (true)
    {
        //查看缓冲区中的数据,并不真正取走
        int nTestRead = recv_peek(fd,pBuf,nLeft);

        //检测这次读来的数据中是否包含‘\n‘;
        //如果有,则将之全部读取出来
        for (int i = 0; i < nTestRead; ++i)
        {
            if (pBuf[i] == ‘\n‘)
            {
                //真正的从缓冲区中将数据取走
                if (readn(fd,pBuf,i+1) != i+1)
                {
                    err_exit("readn error");
                }
                else
                {
                    return i + 1;
                }
            }
        }

        //如果这次读的缓冲区中没有‘\n‘

        //如果读超了:读道德数目大于一行最大数,则做异常处理
        if (nTestRead > nLeft)
        {
            exit(EXIT_FAILURE);
        }

        nLeft -= nTestRead; //若缓冲区没有‘\n‘,则将剩余的数据读走
        if (readn(fd,pBuf,nTestRead) != nTestRead)
        {
            exit(EXIT_FAILURE);
        }

        pBuf += nTestRead;
    }

    return -1;
}

#endif // COMMEN_H_INCLUDED

2-Mafile文件

CC = g++ 
CPPFLAGS = -Wall -g -pthread

BIN = server client
SOURCES = $(BIN.=.cpp)

.PHONY: clean all 

all: $(BIN)

$(BIN): $(SOURCES)

clean:
    -rm -rf $(BIN) bin/ obj/ core


Socket编程实践(7)   --TCP粘包解决方法2

标签:socket   linux   tcp   cc++   异常处理   

原文地址:http://blog.csdn.net/zjf280441589/article/details/41745453

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!