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;

服务器端:

#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");
    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");
        }
        else if(nread == 0)//客户端已发送完毕,即客户端的写端关闭
        {
            printf("close ...\n");
            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!\n");
            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 ...\n");
            exit(EXIT_SUCCESS);
        }

        printf("count = %d, receive size = %d\n", ++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\n", ++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);
}
时间: 2024-08-05 09:55:11

TCP之简单回传的相关文章

TCP之简单回传(三)

鉴于TCP之简单回传(一)   中所出现的问题,本博文所要采取的一种方法是: 服务器端和客户端共同遵守如下约定: 接收的字节流中,若遇到'\n',表示一次传送完毕. 具体为: 客户端把每次欲发送的数据的最后一个字符设置为 '\n': 而服务器每次接收时,一个一个字符的从缓冲区中取出一个字符,然后再判断该字符是否为'\n',若不是,则继续读取:若是,则退出循环,表示本次接收结束: 实现代码如下: //按字符读取--->由于每读取一个字符就产生一次系统调用,故效率较低 ssize_t readlin

TCP之简单回传(二)

鉴于TCP之简单回传(一)   中所出现的问题,本博文所要采取的一种方法是: 客户端:先向服务器传送一个 int32_t 大小的数据,表示随后所要发送数据的真实长度: 服务器:先接收一个int32_t 大小的数据,再接收真实的数据: 本程序中所用到的函数都可以在 TCP之函数封装 中找到: server服务器端具体实现: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <uni

TCP之简单回传(四)

继续采用 TCP之简单回传(三) 的思路,不过由于其转型时,每次读取一个字符都要调用系统函数一次,故其效率较低: 本次我们采用系统中一个函数recv实现预读取: int PASCAL FAR recv( SOCKET s, char FAR* buf, int len, int flags); s:一个标识已连接套接口的描述字. buf:用于接收数据的缓冲区. len:缓冲区长度. flags:指定调用方式. MSG_PEEK 查看当前数据.数据将被复制到缓冲区中,但并不从输入队列中删除. 通过

Http协议与TCP协议简单理解

在C#编写代码,很多时候会遇到Http协议或者TCP协议,这里做一个简单的理解. TCP协议对应于传输层,而HTTP协议对应于应用层,从本质上来说,二者没有可比性.Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求.Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据完毕后,Http会立即将TCP连接断开,这个过程是很短的.所以Http连接是一种短连接,是一种无状态的连接.所谓的无状态,是指浏览器每次向服务器发起请求的时候,

[Java] Tcp/udp 简单通信

本文转自  我自己的博客guozeyiblog.cn 欢迎来访 效果图: //UDP通信 import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.net.*; import javax.swing.*; class send extends JFrame implements ActionL

iOS Http协议与TCP协议简单理解

在C#编写代码,很多时候会遇到Http协议或者TCP协议,这里做一个简单的理解. TCP协议对应于传输层,而HTTP协议对应于应用层,从本质上来说,二者没有可比性.Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求.Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据完毕后,Http会立即将TCP连接断开,这个过程是很短的.所以Http连接是一种短连接,是一种无状态的连接.所谓的无状态,是指浏览器每次向服务器发起请求的时候,

Http协议与TCP协议简单理解(转)

在C#编写代码,很多时候会遇到Http协议或者TCP协议,这里做一个简单的理解. TCP协议对应于传输层,而HTTP协议对应于应用层,从本质上来说,二者没有可比性.Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求.Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据完毕后,Http会立即将TCP连接断开,这个过程是很短的.所以Http连接是一种短连接,是一种无状态的连接.所谓的无状态,是指浏览器每次向服务器发起请求的时候,

tcp的简单介绍

为什么会有TCP/IP协议 在 世界上各地,各种各样的电脑运行着各自不同的操作系统为大家服务,这些电脑在表达同一种信息的时候所使用的方法是千差万别.就好像圣经中上帝打乱了各地人 的口音,让他们无法合作一样.计算机使用者意识到,计算机只是单兵作战并不会发挥太大的作用.只有把它们联合起来,电脑才会发挥出它最大的潜力.于是人们 就想方设法的用电线把电脑连接到了一起. 但是简单的连到一起是远远不够的,就好像语言不同的两个人互相见了面,完全不能交流信息.因而他们需要定义一些共通的东西来进行交流,TCP/I

Http与协议TCP协议简单易懂

于C#编写代码,很多时候会遇到Http协议或TCP合约,这里做一个简单的了解. TCP对应于该传送层协议,和HTTP对应于应用层协议,从本质上讲,两者是没有可比性.Http该协议是基于TCP之上的,当浏览器须要从server获取网页数据的时候,会发出一次Http请求. Http会通过TCP建立起一个到server的连接通道.当本次请求须要的数据完成后,Http会马上将TCP连接断开,这个过程是非常短的.所以Http连接是一种短连接.是一种无状态的连接.所谓的无状态,是指浏览器每次向server发