TCP之简单回传(一)

本文介绍Tcp的简单应用:简单的 回传(即客户端发送什么,服务器就转发给客户端什么)。

主要包含以下几个函数原型:

服务器端:

//服务器端主要函数原型:
int
socket(int domain, int type, int protocol); int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen); int listen( int fd, int backlog); SOCKET PASCAL accept( SOCKET s, struct sockaddr * addr,int * addrlen);

客户端:

int socket(int domain, int type, int protocol);
int PASCAL FAR connect( SOCKET s, const struct sockaddr FAR* name, int namelen);
服务器端和客户端:以上函数错误的情况下都是 -1

服务器与客户端工作流程如下:

服务器端流程:              客户端流程:
 socket                    socket
   ↓                          ↓
  bind                     connect
   ↓                          ↓
 listen                   write/send
   ↓                          ↓
 accept                    read/recv
   ↓                          ↓
read/recv                   close
   ↓
write/send
   ↓
close    


服务器端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//错误处理,推荐做法
#define ERR_EXIT(m) 
    do { 
        perror(m);
        exit(EXIT_FAILURE);
    }while(0)

void do_service(int sockfd);

int main(int argc, const char *argv[])
{
//socket 返回一个监听的文件描述符 int listenfd = socket(PF_INET, SOCK_STREAM, 0); if(listenfd == -1) ERR_EXIT("socket");
//
地址复用--->记住即可 int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt"); //bind 实现将服务器的地址IP,端口号PORT绑定 struct sockaddr_in addr; memset(&addr, 0, sizeof addr); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1");//将IP转化为网络字节序 addr.sin_port = htons(8976);//主机字节序转化为网络字节序 if(bind(listenfd, (struct sockaddr*)&addr, sizeof addr) == -1) ERR_EXIT("bind"); //listen 监听集合(监听客户端是否发送给服务器消息) if(listen(listenfd, SOMAXCONN) == -1) ERR_EXIT("listen"); //accept 接受客户端请求,并返回另外一个文件描述符单独处理客户的请求 int peerfd = accept(listenfd, NULL, NULL); //read&write do_service(peerfd); //close close(peerfd); close(listenfd); return 0; } //read&write void do_service(int sockfd) { char recvbuf[1024] = {0}; while(1) { //read 读取客户端发送来的数据 int nread = read(sockfd, recvbuf, sizeof recvbuf); if(nread == -1)//出错情况以及处理 { if(errno == EINTR) //读取时由中断信号产生的错误 continue; ERR_EXIT("read");//error } else if(nread == 0)//客户端已发送完毕,即客户端的写端关闭 EOF { printf("close ... "); exit(EXIT_SUCCESS); } //回传给客户端 write(sockfd, recvbuf, strlen(recvbuf)); //清空缓冲区,以免发送覆盖不完全情况 memset(recvbuf, 0, sizeof recvbuf); } }


客户端:

//client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define ERR_EXIT(m) 
    do { 
        perror(m);
        exit(EXIT_FAILURE);
    }while(0)

void do_service(int sockfd);

int main(int argc, const char *argv[])
{
 //socket
    int peerfd = socket(PF_INET, SOCK_STREAM, 0);
    if(peerfd == -1)
        ERR_EXIT("socket");

//connect请求与服务器连接
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //localhost
    addr.sin_port = htons(8976);
    socklen_t len = sizeof addr;
    if(connect(peerfd, (struct sockaddr*)&addr, len) == -1)
        ERR_EXIT("Connect");

//send&recv
    do_service(peerfd);


    return 0;
}

//send&recv
void do_service(int sockfd)
{
    char recvbuf[1024] = {0}; //接收缓冲区
    char sendbuf[1024] = {0};//发送缓冲区
    while(1)
    {
        fgets(sendbuf, sizeof sendbuf, stdin);//从键盘中输入进发送缓冲区数据
        write(sockfd, sendbuf, strlen(sendbuf)); //向服务器发送数据

        //read
        int nread = read(sockfd, recvbuf, sizeof recvbuf); //读取服务器发来的数据
        if(nread == -1)//err
        {
            if(errno == EINTR)
                continue;
            ERR_EXIT("read");
        }
        else if(nread == 0)//EOF
        {
            printf("server close!
");
            close(sockfd);
            exit(EXIT_SUCCESS);
        }

        printf("receive msg : %s", recvbuf); //打印所接收的数据

        memset(recvbuf, 0, sizeof recvbuf);
        memset(sendbuf, 0, sizeof sendbuf);
    }
}

这样我们就简单实现了tcp通信。

注意:本程序不能解决 字节流的 粘包问题

如下程序:服务器与客户端发生改变的代码仅仅是 do_serve 程序;

服务器端改动的部分如下:

void do_service(int sockfd)
{
    int cnt = 0;
    char recvbuf[1024000] = {0}; //从缓冲区一次读取的数据远远大于客户端一次发送的数据(1024)
    while(1)
    {
        int nread = read(sockfd, recvbuf, sizeof recvbuf); //从缓冲区读数据
        if(nread == -1)
        {
            if(errno == EINTR)
                continue;
            ERR_EXIT("read");
        }
        else if(nread == 0)
        {
            printf("close ...
");
            exit(EXIT_SUCCESS);
        }

        printf("count = %d, receive size = %d
", ++cnt, nread);
        memset(recvbuf, 0, sizeof recvbuf);
    }
}

客户端所改动的数据如下:

void do_service(int sockfd)
{
    #define SIZE 1024
    char sendbuf[SIZE + 1] = {0};
    int i;
    for(i = 0; i < SIZE; ++i) //缓冲区中的每个字符的值
        sendbuf[i] = 'a';

    int cnt = 0; //次数
    while(1)
    {
        int i;
        for(i = 0; i < 10; ++i) //每次发送SIZE个字符,总共发送十次
        {
            write(sockfd, sendbuf, SIZE);
            printf("count = %d, write %d bytes
", ++cnt, SIZE);
        }
        nano_sleep(4); //暂停客户端4s钟

        memset(sendbuf, 0, sizeof sendbuf);
    }
}

void nano_sleep(double val)
{
    struct timespec tv;
    tv.tv_sec = val; //取整
    tv.tv_nsec = (val - tv.tv_sec) * 1000 * 1000 * 1000;

    int ret;
    do
    {
        ret = nanosleep(&tv, &tv);
    }while(ret == -1 && errno == EINTR); //只要是由中断信号所产生的错误,就继续执行该循环体,直至睡眠所要求的时间
}

 该程序会发生粘包问题
问题根源在于,客户端没有与服务器协商一次发送多少信息,
若服务器端缓冲区足够大或者很小,很可能会发生接收不足或两次所发送的信息粘在一起。
解决方案:
1)每次发送固定数据,服务器接收固定数据;
2)事先发送一个int32_t数据,告诉服务器,客户端要发送数据的长度;解决方案见:TCP之简单回传(二)
3)采用特定标示,如遇到 ,则默认为一次接收完成。 解决方案见:TCP之简单回传(三)  和  TCP之简单回传(四)

原文地址:https://www.cnblogs.com/xfxu/p/4025514.html